【问题标题】:Start thread with member function使用成员函数启动线程
【发布时间】:2021-09-25 13:51:44
【问题描述】:

我正在尝试使用不带参数并返回 void 的成员函数构造一个 std::thread。我无法弄清楚任何有效的语法 - 编译器无论如何都会抱怨。实现spawn() 以使其返回执行test()std::thread 的正确方法是什么?

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

【问题讨论】:

  • 你的意思是函数返回 void,称为 void 或者它只是没有任何参数。你可以添加你想要做的代码吗?
  • 你测试了吗? (我还没有。)您的代码似乎依赖于 RVO(返回值优化),但我认为您不应该这样做。我认为使用std::move( std::thread(func) ); 更好,因为std::thread 没有复制构造函数。
  • @RnMss: you can rely on RVO,在这种情况下使用 std::move 是多余的 - 如果这不是真的,并且没有复制构造函数,编译器无论如何都会出错。

标签: c++ multithreading c++11


【解决方案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);
  }

更新:我想解释更多点,其中一些在 cmets 中也已经讨论过。

上述语法是根据 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 不是前面描述的类型之一时 项目;
  • t1.*f 当 N == 1 且 f 是指向类 T 的成员数据的指针且 t 1 是 T 或 a 类型的对象
    对 T 类型对象的引用或对 a 对象的引用
    从 T 派生的类型;
  • (*t1).*f 当 N == 1 并且 f 是指向类 T 的成员数据的指针并且 t 1 不是上一项中描述的类型之一;
  • f(t1, t2, ..., tN) 在所有其他情况下。

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

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

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


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

【讨论】:

  • 至少这样编译。虽然我不知道你为什么将实例作为第二个参数传递。
  • @LCID:std::thread 构造函数的多参数版本的工作方式与将参数传递给 std::bind 一样。要调用成员函数,std::bind 的第一个参数必须是指向适当类型对象的指针、引用或共享指针。
  • 你从哪里看出构造函数的行为就像一个隐含的bind?我在任何地方都找不到。
  • @KerrekSB,比较 [thread.thread.constr]p4 和 [func.bind.bind]p3,语义非常相似,根据 INVOKE 伪代码定义,它定义了成员函数的方式叫
  • 请记住,不是静态成员函数作为第一个参数采用类的实例(程序员看不到),因此当将此方法作为原始函数传递时,您总是会在编译和声明不匹配时遇到问题。
【解决方案2】:

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

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

由于this-&gt;可以省略,所以可以简写为:

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

或只是(已弃用)

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

【讨论】:

  • 一般情况下,在按值返回局部变量时不应使用std::move。这实际上抑制了 RVO。如果你只是按值返回(没有移动),编译器可能会使用 RVO,如果不是,标准说它必须调用移动语义。
  • @zmb,除了你希望代码在VC10上编译,如果返回类型不是CopyConstructable,你必须移动。
  • RVO 仍然生成比移动语义更好的代码,并且不会消失。
  • 小心[=]。有了它,您可能会无意中复制一个巨大的对象。一般来说,使用[&amp;][=] 是一种代码味道
  • @Everyone 不要忘记这是一个线程。这意味着 lambda 函数的寿命可能会超过其上下文范围。因此,通过使用按引用捕获 ([&amp;]),您可能会引入一些错误,例如一些悬空引用。 (例如,std::thread spawn() { int i = 10; return std::thread( [&amp;] { std::cout&lt;&lt;i&lt;&lt;"\n"; } ); }
【解决方案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)

【讨论】:

  • 与 OP 问题并不真正相关,但为什么要在堆上分配 Wrapper(而不是释放它)?你有 java/c# 背景吗?
  • 别忘了delete堆内存:)
  • 没有理由在程序结束之前删除一个对象。不要无缘无故地羞辱别人。
【解决方案4】:

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

#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

来自this answer 的重写样本将是:

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

【讨论】:

    【解决方案5】:

    有些用户已经给出了答案,并且解释得很好。

    我想再添加一些与线程相关的内容。

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

    【讨论】:

      猜你喜欢
      相关资源
      最近更新 更多