Iniciar hilo con función miembro

9 minutos de lectura

Iniciar hilo con funcion miembro
abergmeier

Estoy tratando de construir un std::thread con una función miembro que no toma argumentos y devuelve void. No puedo encontrar ninguna sintaxis que funcione: el compilador se queja sin importar qué. ¿Cuál es la forma correcta de implementar spawn() para que devuelva un std::thread que ejecuta test()?

#include <thread>
class blub {
  void test() {
  }
public:
  std::thread spawn() {
    return { test };
  }
};

  • ¿Quiere decir que la función devuelve vacío, llamado vacío o simplemente no tiene ningún parámetro? ¿Puedes agregar el código de lo que estás tratando de hacer?

    – Zaid Amir

    20 de mayo de 2012 a las 13:02


  • ¿Has probado? (Todavía no lo he hecho). Su código parece depender de la RVO (optimización del valor de retorno), pero no creo que deba hacerlo. creo que usando std::move( std::thread(func) ); es mejor, por std::thread no tiene un constructor de copia.

    – RnMss

    10 de octubre de 2013 a las 11:28


  • @RnMss: puede confiar en RVO, usando std::move es redundante en este caso; si esto no fuera cierto y no hubiera un constructor de copias, el compilador daría un error de todos modos.

    – Qualia

    22 de octubre de 2015 a las 13:19

Iniciar hilo con funcion miembro
Stephan Dollberg

#include <thread>
#include <iostream>

class bar {
public:
  void foo() {
    std::cout << "hello from member function" << std::endl;
  }
};

int main()
{
  std::thread t(&bar::foo, bar());
  t.join();
}

EDITAR: contabilizando su edición, debe hacerlo así:

  std::thread spawn() {
    return std::thread(&blub::test, this);
  }

ACTUALIZAR: Quiero explicar algunos puntos más, algunos de ellos también han sido discutidos en los comentarios.

La sintaxis descrita anteriormente se define en términos de la definición INVOKE (§20.8.2.1):

Defina INVOKE (f, t1, t2, …, tN) de la siguiente manera:

  • (t1.*f)(t2, …, tN) cuando f es un puntero a una función miembro de una clase T y t1 es un objeto de tipo T o una referencia a un objeto de tipo T o una referencia a un objeto de un tipo derivado de T;
  • ((*t1).*f)(t2, …, tN) cuando f es un apuntador a una función miembro de una clase T y t1 no es de los tipos descritos en el ítem anterior;
  • t1.*f cuando N == 1 y f es un puntero a datos de miembros de una clase T y t 1 es un objeto de tipo T o un
    referencia a un objeto de tipo T o una referencia a un objeto de un
    tipo derivado de T;
  • (*t1).*f cuando N == 1 y f es un puntero a datos miembro de una clase T y t 1 no es uno de los tipos descritos en el ítem anterior;
  • f(t1, t2, …, tN) en todos los demás casos.

Otro hecho general que quiero señalar es que, por defecto, el constructor de subprocesos copiará todos los argumentos que se le pasen. La razón de esto es que los argumentos pueden necesitar sobrevivir al hilo de llamada, copiar los argumentos lo garantiza. En cambio, si realmente quiere pasar una referencia, puede usar un std::reference_wrapper creado por std::ref.

std::thread (foo, std::ref(arg1));

Al hacer esto, promete que se encargará de garantizar que los argumentos seguirán existiendo cuando el subproceso opere sobre ellos.


Tenga en cuenta que todas las cosas mencionadas anteriormente también se pueden aplicar a std::async y std::bind.

  • Al menos así se compila. Aunque no tengo idea de por qué está pasando la instancia como segundo argumento.

    – abergmeier

    20 de mayo de 2012 a las 13:37


  • @LCID: La versión de múltiples argumentos del std::thread constructor funciona como si los argumentos se pasaran a std::bind. Para llamar a una función miembro, el primer argumento para std::bind debe ser un puntero, una referencia o un puntero compartido a un objeto del tipo apropiado.

    – David S.

    20 de mayo de 2012 a las 13:49


  • ¿De dónde sacas que el constructor actúa como un implícito? bind? No puedo encontrar eso en ningún lado.

    – KerrekSB

    20 de mayo de 2012 a las 13:58

  • @KerrekSB, comparar [thread.thread.constr]p4 con [func.bind.bind]p3, la semántica es bastante similar, definida en términos del pseudocódigo INVOKE, que define cómo se llaman las funciones miembro

    –Jonathan Wakely

    20 de mayo de 2012 a las 14:39


  • recuerde que las funciones miembro no estáticas como primer parámetro toman instancia de la clase (no es visible para el programador), por lo que al pasar este método como función sin formato, siempre encontrará un problema durante la compilación y la declaración no coinciden.

    – zoska

    10/10/2013 a las 11:56

Iniciar hilo con funcion miembro
RnMss

Dado que está utilizando C ++ 11, la expresión lambda es una solución agradable y limpia.

class blub {
    void test() {}
  public:
    std::thread spawn() {
      return std::thread( [this] { this->test(); } );
    }
};

ya que this-> puede omitirse, podría acortarse a:

std::thread( [this] { test(); } )

o solo (obsoleto)

std::thread( [=] { test(); } )

  • En general, no deberías usar std::move al devolver una variable local por valor. Esto realmente inhibe la RVO. Si solo regresa por valor (sin el movimiento), el compilador puede usar RVO, y si no lo hace, el estándar dice que tiene que invocar la semántica de movimiento.

    – zmb

    10/10/2013 a las 11:53

  • @zmb, con la excepción de que desea que el código se compile en VC10, debe moverse si el tipo de devolución no es CopyConstructable.

    – abergmeier

    10/10/2013 a las 13:20

  • RVO aún genera mejor código que mover la semántica y no va a desaparecer.

    –Jonathan Wakely

    9 oct 2014 a las 10:41


  • Ten cuidado con [=]. Con eso puedes copiar sin darte cuenta un objeto enorme. En general, es un olor a código usar [&] o [=].

    – oxidado

    9 de septiembre de 2016 a las 8:37

  • @Todos No olviden que es un hilo aquí. Esto significa que la función lambda puede sobrevivir a su ámbito de contexto. Entonces, al usar la captura por referencia ([&]), puede introducir errores como algunas referencias colgantes. (Por ejemplo, std::thread spawn() { int i = 10; return std::thread( [&] { std::cout<<i<<"\n"; } ); })

    – RnMss

    9 de enero de 2018 a las 8:13


Aquí hay un ejemplo completo

#include <thread>
#include <iostream>

class Wrapper {
   public:
      void member1() {
          std::cout << "i am member1" << std::endl;
      }
      void member2(const char *arg1, unsigned arg2) {
          std::cout << "i am member2 and my first arg is (" << arg1 << ") and second arg is (" << arg2 << ")" << std::endl;
      }
      std::thread member1Thread() {
          return std::thread([=] { member1(); });
      }
      std::thread member2Thread(const char *arg1, unsigned arg2) {
          return std::thread([=] { member2(arg1, arg2); });
      }
};
int main(int argc, char **argv) {
   Wrapper *w = new Wrapper();
   std::thread tw1 = w->member1Thread();
   std::thread tw2 = w->member2Thread("hello", 100);
   tw1.join();
   tw2.join();
   return 0;
}

Compilar con g++ produce el siguiente resultado

g++ -Wall -std=c++11 hello.cc -o hello -pthread

i am member1
i am member2 and my first arg is (hello) and second arg is (100)

  • no es realmente relevante para la pregunta OP, pero ¿por qué asigna Wrapper en el montón (y no lo desasigna)? ¿tienes fondo java/c#?

    – Alessandro Teruzzi

    13 oct 2016 a las 13:34

  • no te olvides de delete la memoria del montón 🙂

    – Robot flojo

    21 de marzo de 2020 a las 21:03

  • No hay motivo para eliminar un objeto justo antes de que finalice el programa. No recuerdes avergonzar a la gente sin razón.

    – Capitán Codeman

    15 de agosto de 2020 a las 23:19

@hop5 y @RnMss sugirieron usar C++11 lambdas, pero si maneja punteros, puede usarlos directamente:

#include <thread>
#include <iostream>

class CFoo {
  public:
    int m_i = 0;
    void bar() {
      ++m_i;
    }
};

int main() {
  CFoo foo;
  std::thread t1(&CFoo::bar, &foo);
  t1.join();
  std::thread t2(&CFoo::bar, &foo);
  t2.join();
  std::cout << foo.m_i << std::endl;
  return 0;
}

salidas

2

La muestra reescrita de esta respuesta sería entonces:

#include <thread>
#include <iostream>

class Wrapper {
  public:
      void member1() {
          std::cout << "i am member1" << std::endl;
      }
      void member2(const char *arg1, unsigned arg2) {
          std::cout << "i am member2 and my first arg is (" << arg1 << ") and second arg is (" << arg2 << ")" << std::endl;
      }
      std::thread member1Thread() {
          return std::thread(&Wrapper::member1, this);
      }
      std::thread member2Thread(const char *arg1, unsigned arg2) {
          return std::thread(&Wrapper::member2, this, arg1, arg2);
      }
};

int main() {
  Wrapper *w = new Wrapper();
  std::thread tw1 = w->member1Thread();
  tw1.join();
  std::thread tw2 = w->member2Thread("hello", 100);
  tw2.join();
  return 0;
}

Algunos usuarios ya han dado su respuesta y la han explicado muy bien.

Me gustaría agregar algunas cosas más relacionadas con el hilo.

  1. Cómo trabajar con functor y thread. Consulte el siguiente ejemplo.

  2. El hilo hará su propia copia del objeto mientras pasa el objeto.

    #include<thread>
    #include<Windows.h>
    #include<iostream>
    
    using namespace std;
    
    class CB
    {
    
    public:
        CB()
        {
            cout << "this=" << this << endl;
        }
        void operator()();
    };
    
    void CB::operator()()
    {
        cout << "this=" << this << endl;
        for (int i = 0; i < 5; i++)
        {
            cout << "CB()=" << i << endl;
            Sleep(1000);
        }
    }
    
    void main()
    {
        CB obj;     // please note the address of obj.
    
        thread t(obj); // here obj will be passed by value 
                       //i.e. thread will make it own local copy of it.
                        // we can confirm it by matching the address of
                        //object printed in the constructor
                        // and address of the obj printed in the function
    
        t.join();
    }
    

Otra forma de lograr lo mismo es como:

void main()
{
    thread t((CB()));

    t.join();
}

Pero si desea pasar el objeto por referencia, use la siguiente sintaxis:

void main()
{
    CB obj;
    //thread t(obj);
    thread t(std::ref(obj));
    t.join();
}

  • Hola, ¿podría explicar cómo es posible crear un hilo a partir de una función miembro sin crear un objeto? en caso thread t((CB())); no CB se crea el objeto? ¿Puede responder mi pregunta aquí? stackoverflow.com/q/71152949/7264131

    – huevo triste

    17 de febrero a las 5:56

  • Hola, ¿podría explicar cómo es posible crear un hilo a partir de una función miembro sin crear un objeto? en caso thread t((CB())); no CB se crea el objeto? ¿Puede responder mi pregunta aquí? stackoverflow.com/q/71152949/7264131

    – huevo triste

    17 de febrero a las 5:56

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad