用成员函数启动线程


294

我正在尝试std::thread使用不带参数和返回值的成员函数构造一个void。我无法找出任何有效的语法-编译器会抱怨。什么是正确的实现方式spawn(),使其返回std::thread执行结果test()

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

1
您的意思是该函数返回void,称为void还是没有任何参数。您可以添加您要执行的操作的代码吗?
Zaid Amir 2012年

你测试了吗 (我还没有。)您的代码似乎依赖于RVO(返回值优化),但是我不认为您应该这样做。我认为使用std::move( std::thread(func) );更好,因为std::thread没有复制构造函数。
RnMss

4
@RnMss:您可以依赖RVOstd::move在这种情况下使用是多余的-如果不是这样,并且没有复制构造函数,则编译器仍然会给出错误。
Qualia 2015年

Answers:


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

编辑:记帐您的编辑,您必须这样做:

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

更新:我想解释更多的要点,其中的一些要点也在评论中进行了讨论。

上述语法是根据INVOKE定义(第2.0.8.2.1节)定义的:

如下定义INVOKE(f,t1,t2,...,tN):

  • (t1。* f)(t2,...,tN)当f是指向类T的成员函数的指针并且t1是类型T的对象或对类型T的对象的引用或对t的引用从T派生的类型的对象;
  • ((* t1)。* f)(t2,...,tN)当f是指向类T的成员函数的指针并且t1不是上一项中所述的类型之一时;
  • t1。* f,当N == 1且f是指向类T的成员数据的指针,并且t 1是类型T
    的对象或对类型T的对象的引用或对类型的对象的引用时
    , T;
  • (* t1)。* f,当N == 1且f是指向类T的成员数据的指针,并且t 1不是上一项所述的类型之一时;
  • 在所有其他情况下f(t1,t2,...,tN)

我要指出的另一个普遍事实是,默认情况下,线程构造函数将复制传递给它的所有参数。这样做的原因是参数可能需要更长的调用线程的寿命,复制参数可以保证这一点。相反,如果您想真正传递引用,则可以使用std::reference_wrapper创建者std::ref

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

通过这样做,您可以保证将在线程对其进行操作时保证参数仍然存在。


请注意,上述所有内容也可以应用于std::asyncstd::bind


1
至少以这种方式进行编译。虽然我不知道为什么您要将实例作为第二个参数传递。
abergmeier 2012年

15
@LCID:std::thread构造函数的多参数版本就像将参数传递给一样std::bind。要调用成员函数,的第一个参数std::bind必须是指向适当类型对象的指针,引用或共享指针。
Dave S

构造函数的作用就像一个隐式函数,您从哪里得到bind呢?我在任何地方都找不到。
Kerrek SB 2012年

3
@KerrekSB,将[thread.thread.constr] p4与[func.bind.bind] p3进行比较,其语义非常相似,使用INVOKE伪代码定义,该伪代码定义了如何调用成员函数
Jonathan Wakely,2012年

4
请记住,不是将静态成员函数作为第一个参数作为类的实例(对于程序员而言这是不可见的),因此当将此方法作为原始函数传递时,在编译和声明不匹配时总是会遇到问题。
zoska

100

由于您使用的是C ++ 11,因此lambda-expression是一个不错的解决方案。

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

由于this->可以省略,因此可以简化为:

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

要不就

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

8
通常,std::move按值返回局部变量时不应使用。这实际上抑制了RVO。如果仅按值返回(不移动),则编译器可能会使用RVO,如果不是,则标准会说必须调用移动语义。
2013年

@zmb,除了要在VC10上编译代码之外,如果返回类型不是CopyConstructable,则必须移动。
abergmeier 2013年

6
RVO仍然比移动语义生成更好的代码,并且不会消失。
Jonathan Wakely 2014年

2
请注意[=]。这样一来,您可能会无意间复制一个巨大的对象。通常,使用或会产生代码异味[&][=]
rustyx

3
@每个人都不要忘记这里是一个线程。这意味着lambda函数可能会超出其上下文范围。因此,通过使用按引用捕获([&]),您可能会引入一些悬挂引用等错误。(例如,std::thread spawn() { int i = 10; return std::thread( [&] { std::cout<<i<<"\n"; } ); }
RnMss

29

这是一个完整的例子

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

用g ++编译会产生以下结果

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
与OP问题不是很相关,但是为什么要在堆上分配Wrapper(而不是取消分配)?你有Java / C#背景吗?
亚历山德罗·特鲁齐

不要忘记delete堆中的内存:)
Slack Bot

19

@ hop5和@RnMss建议使用C ++ 11 lambda,但是如果处理指针,则可以直接使用它们:

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

输出

2

这个答案改写的样本将是:

#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

一些用户已经给出了答案并很好地解释了它。

我想添加一些与线程相关的东西。

  1. 如何使用函子和线程。请参考以下示例。

  2. 线程在传递对象时将创建其自己的对象副本。

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

实现相同目标的另一种方式是:

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

    t.join();
}

但是,如果要通过引用传递对象,请使用以下语法:

void main()
{
    CB obj;
    //thread t(obj);
    thread t(std::ref(obj));
    t.join();
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.