Rozpocznij wątek za pomocą funkcji członka

294

Próbuję zbudować std::threadz funkcją członka, która nie przyjmuje argumentów i zwraca void. Nie mogę znaleźć żadnej składni, która działa - kompilator narzeka bez względu na wszystko. Jaki jest prawidłowy sposób zaimplementowania spawn(), std::threadaby zwracał wykonanie test()?

#include <thread>
class blub {
  void test() {
  }
public:
  std::thread spawn() {
    return { test };
  }
};
abergmeier
źródło
1
Czy masz na myśli, że funkcja zwraca void, nazywana void lub po prostu nie ma żadnych parametrów? Czy możesz dodać kod tego, co próbujesz zrobić?
Zaid Amir
Testowałeś? (Jeszcze tego nie zrobiłem). Twój kod wydaje się polegać na RVO (optymalizacja wartości zwracanej), ale nie sądzę, że powinieneś to zrobić. Myślę, że używanie std::move( std::thread(func) );jest lepsze, std::threadponieważ nie ma konstruktora kopiowania.
RnMss,
4
@RnMss: możesz polegać na RVO , std::movew tym przypadku użycie jest redundantne - gdyby nie było to prawdą i nie istniałby konstruktor kopiowania, kompilator i tak dałby błąd.
Qualia,

Odpowiedzi:

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

EDYCJA: Uwzględniając swoją edycję, musisz to zrobić w następujący sposób:

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

AKTUALIZACJA: Chcę wyjaśnić kilka innych kwestii, niektóre z nich zostały również omówione w komentarzach.

Opisana powyżej składnia jest zdefiniowana w kategoriach INVOKE (§20.8.2.1):

Zdefiniuj INVOKE (f, t1, t2, ..., tN) w następujący sposób:

  • (t1. * f) (t2, ..., tN), gdy f jest wskaźnikiem funkcji składowej klasy T, a t1 jest obiektem typu T lub odniesieniem do obiektu typu T lub odwołaniem do obiekt typu pochodzący od T;
  • ((* t1). * f) (t2, ..., tN), gdy f jest wskaźnikiem funkcji składowej klasy T, a t1 nie jest jednym z typów opisanych w poprzednim punkcie;
  • t1. * f gdy N == 1 if jest wskaźnikiem do danych elementu klasy T, a t 1 jest obiektem typu T lub
    odniesieniem do obiektu typu T lub odniesieniem do obiektu
    typu uzyskanego z T;
  • (* t1). * f gdy N == 1 if jest wskaźnikiem do danych elementu klasy T, a t 1 nie jest jednym z typów opisanych w poprzednim punkcie;
  • f (t1, t2, ..., tN) we wszystkich pozostałych przypadkach.

Innym ogólnym faktem, na który chcę zwrócić uwagę, jest to, że domyślnie konstruktor wątków kopiuje wszystkie przekazane mu argumenty. Powodem tego jest to, że argumenty mogą potrzebować przeżyć wątek wywołujący, kopiowanie argumentów gwarantuje to. Zamiast tego, jeśli naprawdę chcesz przekazać referencję, możesz użyć std::reference_wrapperutworzonego przez std::ref.

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

Robiąc to, obiecujesz, że zadbasz o to, aby argumenty nadal istniały, gdy wątek na nich działa.


Pamiętaj, że wszystkie wyżej wymienione rzeczy można również zastosować do std::asynci std::bind.

Stephan Dollberg
źródło
1
Przynajmniej w ten sposób się kompiluje. Chociaż nie mam pojęcia, dlaczego przekazujesz instancję jako drugi argument.
abergmeier
15
@LCID: Wersja z wieloma argumentami std::threadkonstruktora działa tak, jakby argumenty zostały przekazane std::bind. Aby wywołać funkcję członka, pierwszym argumentem std::bindmusi być wskaźnik, odwołanie lub wskaźnik współdzielony do obiektu odpowiedniego typu.
Dave S
Skąd bierzesz to, że konstruktor działa jak domniemany bind? Nigdzie nie mogę tego znaleźć.
Kerrek SB,
3
@KerrekSB, porównaj [thread.thread.constr] p4 z [func.bind.bind] p3, semantyka jest dość podobna, zdefiniowana w kategoriach pseudokodu INVOKE, który określa, jak nazywane są funkcje składowe
Jonathan Wakely
4
pamiętaj, że niestacjonarne funkcje składowe jako pierwszy parametr biorą instancję klasy (nie jest to widoczne dla programisty), więc podczas przekazywania tej metody jako funkcji surowej zawsze napotkasz problem podczas kompilacji i niezgodności deklaracji.
zoska
100

Ponieważ używasz C ++ 11, wyrażenie lambda jest ładnym i czystym rozwiązaniem.

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

ponieważ this->można go pominąć, można go skrócić do:

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

Lub tylko

std::thread( [=] { test(); } )
RnMss
źródło
8
Zasadniczo nie należy używać do std::movezwracania zmiennej lokalnej według wartości. To faktycznie hamuje RVO. Jeśli po prostu zwrócisz wartość (bez przeniesienia), kompilator może użyć RVO, a jeśli nie, standard mówi, że musi wywoływać semantykę przenoszenia.
zmb 10.10.13
@zmb, z wyjątkiem tego, że chcesz skompilować kod na VC10, musisz przenieść, jeśli typ zwracany nie jest CopyConstructable.
abergmeier,
6
RVO wciąż generuje lepszy kod niż semantyka przenoszenia i nie odchodzi.
Jonathan Wakely
2
Ostrożnie z [=]. Dzięki temu możesz przypadkowo skopiować ogromny obiekt. Ogólnie rzecz biorąc, jest to zapach zapachowy do użycia [&]lub [=].
rustyx
3
@ Każdy Nie zapomnij, że jest to wątek tutaj. Oznacza to, że funkcja lambda może przekroczyć zakres kontekstu. Używając przechwytywania przez referencję ( [&]), możesz wprowadzić błędy, takie jak niektóre wiszące referencje. (Na przykład std::thread spawn() { int i = 10; return std::thread( [&] { std::cout<<i<<"\n"; } ); })
RnMss
29

Oto kompletny przykład

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

Kompilacja z g ++ daje następujący wynik

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
źródło
10
nie bardzo istotne dla pytania PO, ale dlaczego przydzielasz Wrapper na stercie (a nie cofasz przydział)? czy masz java / c # w tle?
Alessandro Teruzzi
Nie zapomnij o deletepamięci ze stosu :)
Slack Bot
19

@ hop5 i @RnMss sugerują użycie lambd C ++ 11, ale jeśli masz do czynienia ze wskaźnikami, możesz użyć ich bezpośrednio:

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

wyjścia

2

Przepisana próbka z tej odpowiedzi będzie wtedy:

#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
źródło
0

Niektórzy użytkownicy już udzielili odpowiedzi i wyjaśnili ją bardzo dobrze.

Chciałbym dodać jeszcze kilka rzeczy związanych z wątkiem.

  1. Jak pracować z funktorem i wątkiem. Proszę odnieść się do poniższego przykładu.

  2. Wątek utworzy własną kopię obiektu podczas przekazywania obiektu.

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

Innym sposobem osiągnięcia tego samego jest:

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

    t.join();
}

Ale jeśli chcesz przekazać obiekt przez referencję, użyj poniższej składni:

void main()
{
    CB obj;
    //thread t(obj);
    thread t(std::ref(obj));
    t.join();
}
Mohit
źródło