Inizia il thread con la funzione membro


294

Sto cercando di costruire un std::threadcon una funzione membro che non accetta argomenti e restituisce void. Non riesco a capire alcuna sintassi che funzioni: il compilatore si lamenta in ogni caso. Qual è il modo corretto di implementare in spawn()modo che restituisca un std::threadche esegue test()?

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

1
Vuoi dire che la funzione restituisce void, chiamata void o semplicemente non ha parametri. Puoi aggiungere il codice per quello che stai cercando di fare?
Zaid Amir,

Hai provato? (Non l'ho ancora fatto.) Il tuo codice sembra basarsi sull'RVO (return-value-optimzation), ma non credo che dovresti farlo. Penso che usare std::move( std::thread(func) );sia meglio, perché std::threadnon ha un costruttore di copie.
RnMss,

4
@RnMss: puoi fare affidamento su RVO , usando std::moveè ridondante in questo caso - se questo non fosse vero, e non esistesse un costruttore di copie, il compilatore darebbe comunque un errore.
Qualia,

Risposte:


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();
}

EDIT: contabilità della tua modifica, devi farlo in questo modo:

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

AGGIORNAMENTO: Voglio spiegare alcuni punti in più, alcuni di questi sono stati discussi anche nei commenti.

La sintassi sopra descritta è definita in termini di definizione INVOKE (§20.8.2.1):

Definire INVOKE (f, t1, t2, ..., tN) come segue:

  • (t1. * f) (t2, ..., tN) quando f è un puntatore a una funzione membro di una classe T e t1 è un oggetto di tipo T o un riferimento a un oggetto di tipo T o un riferimento a un oggetto di un tipo derivato da T;
  • ((* t1). * f) (t2, ..., tN) quando f è un puntatore a una funzione membro di una classe T e t1 non è uno dei tipi descritti nell'elemento precedente;
  • t1. * f quando N == 1 e f è un puntatore ai dati dei membri di una classe T et 1 è un oggetto di tipo T o un
    riferimento a un oggetto di tipo T o un riferimento a un oggetto di un
    tipo derivato da T;
  • (* t1). * f quando N == 1 e f è un puntatore ai dati dei membri di una classe T et 1 non è uno dei tipi descritti nell'elemento precedente;
  • f (t1, t2, ..., tN) in tutti gli altri casi.

Un altro fatto generale che voglio sottolineare è che per impostazione predefinita il costruttore del thread copierà tutti gli argomenti passati ad esso. La ragione di ciò è che gli argomenti potrebbero dover sopravvivere al thread chiamante, copiando gli argomenti lo garantisce. Invece, se vuoi davvero passare un riferimento, puoi usare un std::reference_wrappercreato da std::ref.

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

In questo modo, stai promettendo che ti occuperai di garantire che gli argomenti esistano ancora quando il thread opera su di essi.


Si noti che tutte le cose sopra menzionate possono essere applicate anche a std::asynce std::bind.


1
Almeno in questo modo si compila. Anche se non ho idea del perché passi l'istanza come secondo argomento.
abergmeier,

15
@LCID: la versione multi-argomento del std::threadcostruttore funziona come se gli argomenti fossero passati std::bind. Per chiamare una funzione membro, il primo argomento std::binddeve essere un puntatore, un riferimento o un puntatore condiviso a un oggetto del tipo appropriato.
Dave S,

Da dove lo prendi che il costruttore si comporta come un implicito bind? Non riesco a trovarlo da nessuna parte.
Kerrek SB,

3
@KerrekSB, confronta [thread.thread.constr] p4 con [func.bind.bind] p3, la semantica è abbastanza simile, definita in termini di pseudocodice INVOKE, che definisce come vengono chiamate le funzioni membro
Jonathan Wakely,

4
ricorda che non le funzioni membro statiche come primo parametro prendono l'istanza della classe (non è visibile per il programmatore), quindi quando passi questo metodo come funzione raw incontrerai sempre un problema durante la compilazione e la mancata corrispondenza della dichiarazione.
zoska,

100

Dal momento che stai usando C ++ 11, lambda-expression è una soluzione piacevole e pulita.

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

poiché this->può essere omesso, potrebbe essere abbreviato in:

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

o solo

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

8
In generale, non si dovrebbe usare std::movequando si restituisce una variabile locale per valore. Questo effettivamente inibisce l'RVO. Se si ritorna semplicemente in base al valore (senza lo spostamento), il compilatore può utilizzare RVO e, in caso contrario, lo standard dice che deve invocare la semantica dello spostamento.
zmb,

@zmb, con l'eccezione che si desidera compilare il codice su VC10, è necessario spostarsi se il tipo restituito non è CopyConstructable.
abergmeier,

6
RVO genera ancora un codice migliore rispetto allo spostamento della semantica e non sta andando via.
Jonathan Wakely,

2
Stai attento con [=]. Con ciò puoi inavvertitamente copiare un oggetto enorme. In generale, è un odore di codice da usare [&]o [=].
Rustyx,

3
@Ogni persona Non dimenticare che è un thread qui. Ciò significa che la funzione lambda potrebbe sopravvivere al suo ambito di contesto. Quindi, utilizzando l'acquisizione per riferimento ( [&]), è possibile introdurre bug come alcuni riferimenti penzolanti. (Ad esempio, std::thread spawn() { int i = 10; return std::thread( [&] { std::cout<<i<<"\n"; } ); })
RnMss

29

Ecco un esempio 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;
}

La compilazione con g ++ produce il seguente risultato

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)

10
non pertinente alla domanda del PO, ma perché allocare Wrapper sull'heap (e non deallocare)? hai java / c # background?
Alessandro Teruzzi,

Non dimenticare deletela memoria dall'heap :)
Slack Bot

19

@ hop5 e @RnMss hanno suggerito di usare lambda C ++ 11, ma se hai a che fare con i puntatori, puoi usarli direttamente:

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

uscite

2

Il campione riscritto da questa risposta sarebbe quindi:

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

0

Alcuni utenti hanno già dato la loro risposta e l'hanno spiegata molto bene.

Vorrei aggiungere alcune altre cose relative al thread.

  1. Come lavorare con functor e thread. Fare riferimento all'esempio seguente.

  2. Il thread creerà la propria copia dell'oggetto mentre passa l'oggetto.

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

Un altro modo per ottenere la stessa cosa è come:

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

    t.join();
}

Ma se si desidera passare l'oggetto per riferimento, utilizzare la sintassi seguente:

void main()
{
    CB obj;
    //thread t(obj);
    thread t(std::ref(obj));
    t.join();
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.