Thread mit Mitgliedsfunktion starten

294

Ich versuche, eine std::threadFunktion mit einem Element zu erstellen, die keine Argumente akzeptiert und zurückgibt void. Ich kann keine Syntax finden, die funktioniert - der Compiler beschwert sich, egal was passiert. Was ist der richtige Weg, um zu implementieren spawn(), dass ein std::threadausgeführtes zurückgegeben wird test()?

#include <thread>
class blub {
  void test() {
  }
public:
  std::thread spawn() {
    return { test };
  }
};
abergmeier
quelle
1
Meinen Sie damit, dass die Funktion void zurückgibt, void genannt, oder einfach keine Parameter hat? Können Sie den Code für das hinzufügen, was Sie versuchen zu tun?
Zaid Amir
Hast du getestet? (Ich habe es noch nicht getan.) Ihr Code scheint auf dem RVO (Return-Value-Optimzation) zu beruhen, aber ich denke nicht, dass Sie dies tun sollen. Ich denke, die Verwendung std::move( std::thread(func) );ist besser, da std::threades keinen Kopierkonstruktor gibt.
RnMss
4
@RnMss: Sie können sich auf RVO verlassen , die Verwendung std::moveist in diesem Fall redundant. Wäre dies nicht der Fall und es gäbe keinen Kopierkonstruktor, würde der Compiler trotzdem einen Fehler ausgeben.
Qualia

Antworten:

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

BEARBEITEN: Wenn Sie Ihre Bearbeitung berücksichtigen, müssen Sie dies folgendermaßen tun:

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

UPDATE: Ich möchte noch einige Punkte erläutern, von denen einige auch in den Kommentaren besprochen wurden.

Die oben beschriebene Syntax wird in Bezug auf die INVOKE-Definition (§20.8.2.1) definiert:

Definieren Sie INVOKE (f, t1, t2, ..., tN) wie folgt:

  • (t1. * f) (t2, ..., tN) wenn f ein Zeiger auf eine Elementfunktion einer Klasse T ist und t1 ein Objekt vom Typ T oder eine Referenz auf ein Objekt vom Typ T oder eine Referenz auf ein ist Objekt eines von T abgeleiteten Typs;
  • ((* t1). * f) (t2, ..., tN) wenn f ein Zeiger auf eine Mitgliedsfunktion einer Klasse T ist und t1 nicht zu den im vorherigen Punkt beschriebenen Typen gehört;
  • t1. * f, wenn N == 1 und f ein Zeiger auf Mitgliedsdaten einer Klasse T ist und t 1 ein Objekt vom Typ T oder eine
    Referenz auf ein Objekt vom Typ T oder eine Referenz auf ein Objekt eines von
    abgeleiteten Typs ist T;
  • (* t1). * f wenn N == 1 und f ein Zeiger auf Mitgliedsdaten einer Klasse T ist und t 1 nicht einer der im vorherigen Punkt beschriebenen Typen ist;
  • f (t1, t2, ..., tN) in allen anderen Fällen.

Eine weitere allgemeine Tatsache, auf die ich hinweisen möchte, ist, dass der Thread-Konstruktor standardmäßig alle an ihn übergebenen Argumente kopiert. Der Grund dafür ist, dass die Argumente möglicherweise den aufrufenden Thread überleben müssen. Das Kopieren der Argumente garantiert dies. Stattdessen, wenn Sie wirklich wollen , um eine Referenz zu übergeben, können Sie ein verwenden std::reference_wrappererstellt std::ref.

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

Auf diese Weise versprechen Sie, dass Sie sicherstellen, dass die Argumente weiterhin vorhanden sind, wenn der Thread sie bearbeitet.


Beachten Sie, dass alle oben genannten Dinge auch auf std::asyncund angewendet werden können std::bind.

Stephan Dollberg
quelle
1
Zumindest so kompiliert es. Obwohl ich keine Ahnung habe, warum Sie die Instanz als zweites Argument übergeben.
Abergmeier
15
@LCID: Die Version des std::threadKonstruktors mit mehreren Argumenten funktioniert so, als ob die Argumente an übergeben würden std::bind. Um eine Mitgliedsfunktion aufzurufen, muss das erste Argument std::bindein Zeiger, eine Referenz oder ein gemeinsam genutzter Zeiger auf ein Objekt des entsprechenden Typs sein.
Dave S
Woher nehmen Sie, dass sich der Konstruktor wie ein Implizites verhält bind? Ich kann das nirgendwo finden.
Kerrek SB
3
@KerrekSB, vergleiche [thread.thread.constr] p4 mit [func.bind.bind] p3, die Semantik ist ziemlich ähnlich, definiert durch den INVOKE-Pseudocode, der definiert, wie Mitgliedsfunktionen aufgerufen werden
Jonathan Wakely
4
Denken Sie daran, dass nicht statische Elementfunktionen als erster Parameter eine Instanz der Klasse übernehmen (für Programmierer nicht sichtbar). Wenn Sie diese Methode als Rohfunktion übergeben, treten beim Kompilieren und Nichtübereinstimmen der Deklaration immer Probleme auf.
Zoska
100

Da Sie C ++ 11 verwenden, ist Lambda-Ausdruck eine schöne und saubere Lösung.

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

da this->weggelassen werden kann, könnte es verkürzt werden auf:

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

oder nur

std::thread( [=] { test(); } )
RnMss
quelle
8
Im Allgemeinen sollten Sie nicht verwenden, std::movewenn Sie eine lokale Variable nach Wert zurückgeben. Dies hemmt tatsächlich RVO. Wenn Sie nur nach Wert (ohne Verschieben) zurückkehren, verwendet der Compiler möglicherweise RVO, und wenn dies nicht der Fall ist, muss er laut Standard die Verschiebungssemantik aufrufen.
zmb
@zmb, mit der Ausnahme, dass Code auf VC10 kompiliert werden soll, müssen Sie verschieben, wenn der Rückgabetyp nicht CopyConstructable ist.
Abergmeier
6
RVO generiert immer noch besseren Code als die Verschiebungssemantik und verschwindet nicht.
Jonathan Wakely
2
Sei vorsichtig mit [=]. Damit können Sie versehentlich ein riesiges Objekt kopieren. Im Allgemeinen ist es ein Code-Geruch zu verwenden [&]oder [=].
Rustyx
3
@Everyone Vergiss nicht, dass es hier ein Thread ist. Dies bedeutet, dass die Lambda-Funktion ihren Kontextbereich möglicherweise überlebt. Wenn Sie also Capturing-by-Reference ( [&]) verwenden, können Sie Fehler wie einige baumelnde Referenzen einführen. (Zum Beispiel std::thread spawn() { int i = 10; return std::thread( [&] { std::cout<<i<<"\n"; } ); })
RnMss
29

Hier ist ein vollständiges Beispiel

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

Das Kompilieren mit g ++ führt zu folgendem Ergebnis

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
quelle
10
Nicht wirklich relevant für die OP-Frage, aber warum ordnen Sie Wrapper dem Heap zu (und geben ihn nicht frei)? Hast du Java / C # Hintergrund?
Alessandro Teruzzi
Vergiss nicht deletedie Erinnerung vom Haufen :)
Slack Bot
19

@ hop5 und @RnMss schlugen vor, C ++ 11-Lambdas zu verwenden. Wenn Sie sich jedoch mit Zeigern befassen, können Sie diese direkt verwenden:

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

Ausgänge

2

Ein umgeschriebenes Beispiel aus dieser Antwort wäre dann:

#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
quelle
0

Einige Benutzer haben ihre Antwort bereits gegeben und sehr gut erklärt.

Ich möchte noch ein paar Dinge hinzufügen, die mit Thread zu tun haben.

  1. Wie man mit Funktor und Faden arbeitet. Bitte beziehen Sie sich auf das folgende Beispiel.

  2. Der Thread erstellt beim Übergeben des Objekts eine eigene Kopie des Objekts.

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

Ein anderer Weg, um dasselbe zu erreichen, ist wie folgt:

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

    t.join();
}

Wenn Sie das Objekt jedoch als Referenz übergeben möchten, verwenden Sie die folgende Syntax:

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