Bắt đầu chủ đề với chức năng thành viên


294

Tôi đang cố gắng xây dựng một std::threadhàm thành viên không có đối số và trả về void. Tôi không thể tìm ra bất kỳ cú pháp nào hoạt động - trình biên dịch phàn nàn không có vấn đề gì. Cách chính xác để thực hiện spawn()để nó trả về một std::threadthực thi là test()gì?

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

1
Bạn có nghĩa là hàm trả về void, được gọi là void hoặc nó không có bất kỳ tham số nào. Bạn có thể thêm mã cho những gì bạn đang cố gắng làm?
Zaid Amir

Bạn đã thử chưa? (Tôi chưa có.) Mã của bạn dường như dựa vào RVO (tối ưu hóa giá trị trả về), nhưng tôi không nghĩ rằng bạn phải làm như vậy. Tôi nghĩ rằng sử dụng std::move( std::thread(func) );là tốt hơn, vì std::threadkhông có một nhà xây dựng sao chép.
RnMss

4
@RnMss: bạn có thể dựa vào RVO , sử dụng std::movelà không cần thiết trong trường hợp này - điều này không đúng và không có trình tạo sao chép, trình biên dịch sẽ báo lỗi.
Qualia

Câu trả lời:


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: Kế toán chỉnh sửa của bạn, bạn phải làm như thế này:

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

CẬP NHẬT: Tôi muốn giải thích thêm một số điểm, một số trong số họ cũng đã được thảo luận trong các ý kiến.

Cú pháp được mô tả ở trên được định nghĩa theo định nghĩa của INVOKE (§20.8.2.1):

Xác định INVOKE (f, t1, t2, ..., tN) như sau:

  • (t1. * f) (t2, ..., tN) khi f là con trỏ tới hàm thành viên của lớp T và t1 là một đối tượng thuộc loại T hoặc tham chiếu đến một đối tượng thuộc loại T hoặc tham chiếu đến một đối tượng đối tượng của một loại có nguồn gốc từ T;
  • ((* t1). * f) (t2, ..., tN) khi f là con trỏ tới hàm thành viên của lớp T và t1 không phải là một trong các loại được mô tả trong mục trước;
  • t1. * f khi N == 1 và f là con trỏ tới dữ liệu thành viên của lớp T và t 1 là đối tượng thuộc loại T hoặc
    tham chiếu đến đối tượng thuộc loại T hoặc tham chiếu đến đối tượng của a
    loại có nguồn gốc từ T;
  • (* t1). * f khi N == 1 và f là con trỏ tới dữ liệu thành viên của lớp T và t 1 không phải là một trong các loại được mô tả trong mục trước;
  • f (t1, t2, ..., tN) trong tất cả các trường hợp khác.

Một thực tế chung khác mà tôi muốn chỉ ra là theo mặc định, hàm tạo của luồng sẽ sao chép tất cả các đối số được truyền cho nó. Lý do cho điều này là các đối số có thể cần tồn tại lâu hơn chuỗi gọi, sao chép các đối số đảm bảo điều đó. Thay vào đó, nếu bạn muốn thực sự vượt qua một tài liệu tham khảo, bạn có thể sử dụng một tài liệu std::reference_wrapperđược tạo bởi std::ref.

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

Bằng cách này, bạn hứa rằng bạn sẽ đảm bảo rằng các đối số sẽ vẫn tồn tại khi luồng hoạt động trên chúng.


Lưu ý rằng tất cả những điều được đề cập ở trên cũng có thể được áp dụng cho std::asyncstd::bind.


1
Ít nhất theo cách này nó biên dịch. Mặc dù tôi không biết tại sao bạn lại truyền ví dụ làm đối số thứ hai.
abergmeier

15
@LCID: Phiên bản đa đối số của hàm std::threadtạo hoạt động như thể các đối số được truyền vào std::bind. Để gọi một hàm thành viên, đối số đầu tiên std::bindphải là một con trỏ, tham chiếu hoặc con trỏ dùng chung cho một đối tượng thuộc loại thích hợp.
Dave S

Bạn lấy nó từ đâu mà hàm tạo hoạt động như một ẩn bind? Tôi không thể tìm thấy bất cứ nơi nào.
Kerrek SB

3
@KerrekSB, so sánh [thread.thread.constr] p4 với [func.bind.bind] p3, ngữ nghĩa khá giống nhau, được định nghĩa theo thuật ngữ mã hóa INVOKE, định nghĩa cách gọi các hàm thành viên
Jonathan Wakely

4
hãy nhớ rằng không phải các hàm thành viên tĩnh như tham số đầu tiên lấy ví dụ của lớp (nó không hiển thị cho lập trình viên), vì vậy khi chuyển phương thức này thành hàm thô, bạn sẽ luôn gặp phải một vấn đề trong quá trình biên dịch và khai báo không khớp.
zoska

100

Vì bạn đang sử dụng C ++ 11, lambda-express là một giải pháp tốt và sạch.

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

this->có thể bỏ qua, nên có thể rút ngắn thành:

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

hoặc chỉ

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

8
Nói chung, bạn không nên sử dụng std::movekhi trả về một biến cục bộ theo giá trị. Điều này thực sự ức chế RVO. Nếu bạn chỉ trả về theo giá trị (không có di chuyển), trình biên dịch có thể sử dụng RVO và nếu không có tiêu chuẩn thì nó phải gọi ngữ nghĩa di chuyển.
zmb

@zmb, ngoại trừ việc bạn muốn mã biên dịch trên VC10, bạn phải di chuyển nếu loại trả về không phải là CopyConstructable.
abergmeier

6
RVO vẫn tạo mã tốt hơn so với di chuyển ngữ nghĩa và sẽ không biến mất.
Jonathan Wakely

2
Cẩn thận với [=]. Cùng với đó bạn có thể vô tình sao chép một đối tượng khổng lồ. Nói chung, đó là một mùi mã để sử dụng [&]hoặc [=].
rustyx

3
@Everyone Đừng quên nó là một chủ đề ở đây. Điều này có nghĩa là hàm lambda có thể tồn tại lâu hơn phạm vi ngữ cảnh của nó. Vì vậy, bằng cách sử dụng capturing-by-Reference ( [&]), bạn có thể đưa ra các lỗi như một số tham chiếu lơ lửng. (Ví dụ std::thread spawn() { int i = 10; return std::thread( [&] { std::cout<<i<<"\n"; } ); }:)
RnMss

29

Đây là một ví dụ đầy đủ

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

Biên dịch với g ++ tạo ra kết quả như sau

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
không thực sự liên quan đến câu hỏi OP, nhưng tại sao bạn lại phân bổ Wrapper trên heap (và không giải quyết nó)? Bạn có nền java / c # không?
Alessandro Teruzzi

Đừng quên deletebộ nhớ từ đống :)
Slack Bot

19

@ hop5 và @RnMss đề xuất sử dụng lambdas C ++ 11, nhưng nếu bạn xử lý các con trỏ, bạn có thể sử dụng chúng trực tiếp:

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

đầu ra

2

Mẫu viết lại từ câu trả lời này sẽ là:

#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

Một số người dùng đã đưa ra câu trả lời của họ và giải thích nó rất tốt.

Tôi muốn thêm một vài điều liên quan đến chủ đề.

  1. Làm thế nào để làm việc với functor và chủ đề. Vui lòng tham khảo ví dụ dưới đây.

  2. Các luồng sẽ tạo bản sao riêng của đối tượng trong khi truyền đối tượng.

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

Một cách khác để đạt được điều tương tự là:

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

    t.join();
}

Nhưng nếu bạn muốn truyền đối tượng bằng tham chiếu thì hãy sử dụng cú pháp dưới đây:

void main()
{
    CB obj;
    //thread t(obj);
    thread t(std::ref(obj));
    t.join();
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.