使用成员函数启动线程

Start thread with member function

提问人:abergmeier 提问时间:5/20/2012 最后编辑:Barryabergmeier 更新时间:7/13/2020 访问量:474142

问:

我正在尝试构造一个带有成员函数的函数,该函数不接受参数并返回。我无法找出任何有效的语法 - 编译器无论如何都会抱怨。正确的实现方法是什么,以便它返回一个执行?std::threadvoidspawn()std::threadtest()

#include <thread>
class blub {
  void test() {
  }
public:
  std::thread spawn() {
    return { test };
  }
};
C++ 多线程 C++11

评论

1赞 Zaid Amir 5/20/2012
您的意思是该函数返回 void,称为 void,还是它只是没有任何参数。你能为你正在尝试做的事情添加代码吗?
0赞 RnMss 10/10/2013
你测试过吗?(我还没有。您的代码似乎依赖于 RVO(返回值优化),但我认为您不应该这样做。我认为使用更好,因为没有复制构造函数。std::move( std::thread(func) );std::thread
4赞 Qualia 10/22/2015
@RnMss:您可以依赖 RVO,在这种情况下使用 是多余的 - 如果这不是真的,并且没有复制构造函数,编译器无论如何都会给出错误。std::move

答:

491赞 Stephan Dollberg 5/20/2012 #1
#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 定义 (§20.8.2.1) 定义的:

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

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

我想指出的另一个一般事实是,默认情况下,线程构造函数将复制传递给它的所有参数。这样做的原因是参数可能需要比调用线程更长,复制参数可以保证这一点。相反,如果要真正传递引用,可以使用 created by .std::reference_wrapperstd::ref

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

通过这样做,您承诺您将负责保证当线程对参数进行操作时参数仍然存在。


请注意,上面提到的所有内容也可以应用于 和 。std::asyncstd::bind

评论

1赞 abergmeier 5/20/2012
至少它是这样编译的。虽然我不知道你为什么要将实例作为第二个参数传递。
16赞 Dave S 5/20/2012
@LCID:构造函数的多参数版本的工作方式就像参数被传递给 一样。若要调用成员函数,第一个参数必须是指向相应类型的对象的指针、引用或共享指针。std::threadstd::bindstd::bind
0赞 Kerrek SB 5/20/2012
你从哪里得到它,构造函数就像一个隐式?我在任何地方都找不到。bind
3赞 Jonathan Wakely 5/20/2012
@KerrekSB,将 [thread.thread.constr]p4 与 [func.bind.bind]p3 进行比较,语义非常相似,根据 INVOKE 伪代码定义,该伪代码定义了成员函数的调用方式
4赞 zoska 10/10/2013
请记住,不是静态成员函数作为第一个参数会获取类的实例(它对程序员来说是不可见的),因此当将此方法作为原始函数传递时,您总是会在编译和声明不匹配期间遇到问题。
133赞 RnMss 10/10/2013 #2

由于您使用的是 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(); } )

评论

9赞 zmb 10/10/2013
通常,在按值返回局部变量时不应使用。这实际上抑制了 RVO。如果只是按值返回(不带移动),编译器可能会使用 RVO,如果不这样做,标准会说它必须调用移动语义。std::move
0赞 abergmeier 10/10/2013
@zmb,除了希望在 VC10 上编译代码外,如果返回类型不是 CopyConstructable,则必须移动。
7赞 Jonathan Wakely 10/9/2014
RVO 仍然生成比移动语义更好的代码,并且不会消失。
3赞 rustyx 9/9/2016
小心 .有了它,您可以无意中复制一个巨大的对象。一般来说,使用或 是一种代码气味[=][&][=]
3赞 RnMss 1/9/2018
@Everyone 别忘了这是一条线。这意味着 lambda 函数的寿命可能超过其上下文范围。因此,通过使用引用捕获 (),您可能会引入一些错误,例如一些悬空的引用。(例如,[&]std::thread spawn() { int i = 10; return std::thread( [&] { std::cout<<i<<"\n"; } ); })
35赞 hop5 8/24/2015 #3

这是一个完整的例子

#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)

评论

13赞 Alessandro Teruzzi 10/13/2016
与 OP 问题无关,但为什么要在堆上分配 Wrapper(而不是解除分配它)?你有 Java/C# 背景吗?
2赞 Slack Bot 3/22/2020
别忘了从堆:)的记忆delete
6赞 CaptainCodeman 8/16/2020
没有理由在程序结束之前删除对象。不要无缘无故地羞辱别人。
1赞 Mohit 2/21/2017 #4

一些用户已经给出了他们的答案,并很好地解释了这一点。

我想再补充一些与线程有关的东西。

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

评论

0赞 Sad egg 2/17/2022
嗨,您能解释一下如何在不创建对象的情况下从成员函数创建线程吗?如果未创建对象?你能在这里回答我的问题吗 stackoverflow.com/q/71152949/7264131thread t((CB()));CB
32赞 Andrey Starodubtsev 11/18/2017 #5

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