Iniciar hilo con función miembro

294

Estoy tratando de construir una función std::threadcon un miembro que no tome argumentos y devuelva void. No puedo entender 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::threadque se ejecuta test()?

#include <thread>
class blub {
  void test() {
  }
public:
  std::thread spawn() {
    return { test };
  }
};
Abergmeier
fuente
1
¿Quieres decir que la función devuelve void, llamada void o simplemente no tiene ningún parámetro? ¿Puedes agregar el código de lo que estás tratando de hacer?
Zaid Amir
¿Has probado? (Todavía no lo he hecho). Parece que su código depende del RVO (retorno-valor-optimización), pero no creo que deba hacerlo. Creo que usar std::move( std::thread(func) );es mejor, porque std::threadno tiene un constructor de copias.
RnMss
44
@RnMss: puede confiar en RVO , el uso std::movees redundante en este caso; si esto no fuera cierto y no hubiera un constructor de copia, el compilador daría un error de todos modos.
Qualia

Respuestas:

367
#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);
  }

ACTUALIZACIÓN: Quiero explicar algunos puntos más, algunos de ellos también se han discutido en los comentarios.

La sintaxis descrita anteriormente se define en términos de la definición de INVOCACIÓN (§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 puntero a una función miembro de una clase T y t1 no es uno de los tipos descritos en el elemento anterior;
  • t1. * f cuando N == 1 yf es un puntero a datos de miembros de una clase T y t 1 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 cuando N == 1 yf es un puntero a datos de miembros de una clase T y t 1 no es uno de los tipos descritos en el elemento 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 hilos copiará todos los argumentos que se le hayan pasado. La razón de esto es que los argumentos pueden necesitar sobrevivir al hilo de la llamada, copiar los argumentos garantiza eso. En cambio, si realmente quieres pasar una referencia, puedes usar una std::reference_wrappercreada por std::ref.

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

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


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

Stephan Dollberg
fuente
1
Al menos de esta manera se compila. Aunque no tengo idea de por qué está pasando la instancia como segundo argumento.
abergmeier
15
@LCID: la versión de múltiples argumentos del std::threadconstructor funciona como si se pasaran los argumentos std::bind. Para llamar a una función miembro, el primer argumento std::binddebe ser un puntero, una referencia o un puntero compartido a un objeto del tipo apropiado.
Dave S
¿De dónde sacas que el constructor actúa como un implícito bind? No puedo encontrar eso en ningún lado.
Kerrek SB
3
@KerrekSB, compare [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
44
recuerde que las funciones miembro no estáticas como primer parámetro toman la 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 falta de coincidencia de declaraciones.
zoska
100

Como 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 para:

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

o solo

std::thread( [=] { test(); } )
RnMss
fuente
8
En general, no debe usarlo std::moveal devolver una variable local por valor. Esto realmente inhibe 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/1013
@zmb, con la excepción de que desea compilar el código en VC10, debe moverse si el tipo de retorno no es CopyConstructable.
abergmeier
66
RVO todavía genera un código mejor que la semántica de movimiento, y no va a desaparecer.
Jonathan Wakely
2
Tenga cuidado con el [=]. Con eso puedes inadvertidamente copiar un objeto enorme. En general, es un código de olor para usar [&]o [=].
rustyx
3
@ Todos No olviden que es un hilo aquí. Esto significa que la función lambda puede sobrevivir a su alcance de contexto. Entonces, al usar capturar 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
29

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)
hop5
fuente
10
no es realmente relevante para la pregunta de OP, pero ¿por qué asigna Wrapper en el montón (y no lo desasigna)? ¿tienes java / c # fondo?
Alessandro Teruzzi
No te olvides de deletela memoria del montón :)
Slack Bot
19

@ hop5 y @RnMss sugirieron usar lambdas de C ++ 11, pero si manejas punteros, puedes 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;
}
Andrey Starodubtsev
fuente
0

Algunos usuarios ya dieron su respuesta y la explicaron muy bien.

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

  1. Cómo trabajar con functor e hilo. Por favor, consulte el siguiente ejemplo.

  2. El hilo hará su propia copia del objeto al pasar 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, utilice la siguiente sintaxis:

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