Mulai utas dengan fungsi anggota

294

Saya mencoba untuk membangun std::threaddengan fungsi anggota yang tidak memerlukan argumen dan pengembalian void. Saya tidak dapat menemukan sintaks yang berfungsi - kompiler mengeluh tidak peduli apa. Apa cara yang benar untuk diterapkan spawn()sehingga mengembalikan std::threadyang dijalankan test()?

#include <thread>
class blub {
  void test() {
  }
public:
  std::thread spawn() {
    return { test };
  }
};
Abergmeier
sumber
1
Apakah maksud Anda fungsi mengembalikan void, disebut void atau hanya tidak memiliki parameter. Bisakah Anda menambahkan kode untuk apa yang Anda coba lakukan?
Zaid Amir
Sudahkah Anda menguji? (Saya belum.) Kode Anda tampaknya bergantung pada RVO (pengembalian-nilai-optimisasi), tetapi saya tidak berpikir Anda seharusnya melakukannya. Saya pikir menggunakan std::move( std::thread(func) );lebih baik, karena std::threadtidak memiliki copy-constructor.
RnMss
4
@RnMss: Anda dapat mengandalkan RVO , penggunaannya std::moveberlebihan dalam hal ini - jika ini tidak benar, dan tidak ada copy constructor, kompiler akan tetap memberikan kesalahan.
Qualia

Jawaban:

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: Menghitung hasil edit Anda, Anda harus melakukannya seperti ini:

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

UPDATE: Saya ingin menjelaskan beberapa poin lagi, beberapa dari mereka juga telah dibahas dalam komentar.

Sintaks yang diuraikan di atas didefinisikan dalam istilah definisi INVOKE (§20.8.2.1):

Tentukan INVOKE (f, t1, t2, ..., tN) sebagai berikut:

  • (t1. * f) (t2, ..., tN) ketika f adalah penunjuk ke fungsi anggota kelas T dan t1 adalah objek tipe T atau referensi ke objek tipe T atau referensi ke objek jenis yang berasal dari T;
  • ((* t1). * f) (t2, ..., tN) ketika f adalah pointer ke fungsi anggota kelas T dan t1 bukan salah satu dari tipe yang dijelaskan dalam item sebelumnya;
  • t1. * f ketika N == 1 dan f adalah penunjuk ke data anggota kelas T dan t 1 adalah objek tipe T atau
    referensi ke objek tipe T atau referensi ke objek
    tipe yang berasal dari T;
  • (* t1). * f ketika N == 1 dan f adalah pointer ke data anggota kelas T dan t 1 bukan salah satu dari tipe yang dijelaskan dalam item sebelumnya;
  • f (t1, t2, ..., tN) dalam semua kasus lainnya.

Fakta umum lain yang ingin saya tunjukkan adalah bahwa secara default konstruktor utas akan menyalin semua argumen yang diteruskan ke sana. Alasan untuk ini adalah bahwa argumen mungkin perlu hidup lebih lama dari utas panggilan, menyalin argumen menjamin itu. Sebaliknya, jika Anda ingin benar-benar melewati referensi, Anda dapat menggunakan yang std::reference_wrapperdibuat oleh std::ref.

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

Dengan melakukan ini, Anda berjanji bahwa Anda akan memastikan bahwa argumen akan tetap ada ketika utas beroperasi pada mereka.


Perhatikan bahwa semua hal yang disebutkan di atas juga dapat diterapkan ke std::asyncdan std::bind.

Stephan Dollberg
sumber
1
Setidaknya dengan cara ini dikompilasi. Meskipun saya tidak tahu mengapa Anda memberikan contoh sebagai argumen kedua.
abergmeier
15
@LCID: Versi multi-argumen dari std::threadkonstruktor berfungsi seolah argumen diteruskan std::bind. Untuk memanggil fungsi anggota, argumen pertama std::bindharus berupa pointer, referensi, atau pointer bersama ke objek dengan tipe yang sesuai.
Dave S
Di mana Anda mengambilnya dari bahwa konstruktor bertindak seperti implisit bind? Saya tidak dapat menemukannya di mana pun.
Kerrek SB
3
@ GerrekSB, bandingkan [thread.thread.constr] p4 dengan [func.bind.bind] p3, semantiknya sangat mirip, didefinisikan dalam istilah pseudocode INVOKE, yang mendefinisikan bagaimana fungsi anggota dipanggil
Jonathan Wakely
4
ingat bahwa fungsi anggota yang tidak statis sebagai parameter pertama mengambil instance dari kelas (itu tidak terlihat untuk programmer), jadi ketika melewati metode ini sebagai fungsi mentah Anda akan selalu menghadapi masalah selama kompilasi dan deklarasi ketidakcocokan.
zoska
100

Karena Anda menggunakan C ++ 11, ekspresi lambda adalah solusi yang bagus & bersih.

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

karena this->dapat dihilangkan, bisa disingkat menjadi:

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

atau hanya

std::thread( [=] { test(); } )
RnMss
sumber
8
Secara umum, Anda tidak boleh menggunakan std::moveketika mengembalikan variabel lokal dengan nilai. Ini sebenarnya menghambat RVO. Jika Anda hanya kembali dengan nilai (tanpa bergerak) kompiler dapat menggunakan RVO, dan jika tidak standar mengatakan itu harus memanggil semantik bergerak.
zmb
@ zmb, dengan pengecualian bahwa Anda ingin kode dikompilasi di VC10, Anda harus pindah jika tipe pengembalian tidak CopyConstructable.
abergmeier
6
RVO masih menghasilkan kode yang lebih baik daripada memindahkan semantik, dan tidak akan hilang.
Jonathan Wakely
2
Hati-hati dengan [=]. Dengan itu Anda dapat secara tidak sengaja menyalin objek besar. Secara umum, ini bau kode untuk digunakan [&]atau [=].
rustyx
3
@Setiap orang Jangan lupa itu utas di sini. Ini berarti fungsi lambda mungkin hidup lebih lama dari lingkup konteksnya. Jadi dengan menggunakan capturing-by-reference ( [&]), Anda dapat memperkenalkan bug seperti beberapa referensi yang menggantung. (Misalnya, std::thread spawn() { int i = 10; return std::thread( [&] { std::cout<<i<<"\n"; } ); })
RnMss
29

Ini adalah contoh lengkapnya

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

Kompilasi dengan g ++ menghasilkan hasil berikut

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
sumber
10
tidak benar-benar relevan dengan pertanyaan OP, tetapi mengapa Anda mengalokasikan Wrapper di heap (dan tidak membatalkan alokasi itu)? apakah Anda memiliki latar belakang java / c #?
Alessandro Teruzzi
Jangan lupa ke deletememori dari tumpukan :)
Slack Bot
19

@ hop5 dan @RnMss menyarankan untuk menggunakan C ++ 11 lambdas, tetapi jika Anda berurusan dengan pointer, Anda dapat menggunakannya secara langsung:

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

output

2

Sampel yang ditulis ulang dari jawaban ini adalah:

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

Beberapa pengguna telah memberikan jawaban mereka dan menjelaskannya dengan sangat baik.

Saya ingin menambahkan beberapa hal lagi yang terkait dengan utas.

  1. Cara bekerja dengan functor dan utas. Silakan lihat contoh di bawah ini.

  2. Thread akan membuat salinannya sendiri dari objek saat melewati objek.

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

Cara lain untuk mencapai hal yang sama adalah seperti:

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

    t.join();
}

Tetapi jika Anda ingin melewatkan objek dengan referensi maka gunakan sintaks di bawah ini:

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