【问题标题】:c++11 Thread class how to use a class member functionc++11线程类如何使用类成员函数
【发布时间】:2017-04-05 07:43:22
【问题描述】:

我的程序如下所示

#include <iostream>
#include <thread>

class A {
public:
    void foo(int n ) { std::cout << n << std::endl; }
};

int main()
{
    A a;

    std::thread t1(&A::foo, std::ref(a), 100);

    t1.join();
    return 0;
}

当我使用以下命令编译它时,我得到了错误

g++ -o main main.cc -lpthread -std=c++11

错误:

In file included from /usr/local/include/c++/4.8.2/thread:39:0,
                  from check.cc:2:
/usr/local/include/c++/4.8.2/functional: In instantiation of ‘struct std::_Bind_simple<std::_Mem_fn<void (A::*)(int)>(std::reference_wrapper<A>, int)>’:
/usr/local/include/c++/4.8.2/thread:137:47:   required from ‘std::thread::thread(_Callable&&, _Args&& ...) [with _Callable = void (A::*)(int); _Args = {std::reference_wrapper<A>, int}]’
check.cc:13:42:   required from here
/usr/local/include/c++/4.8.2/functional:1697:61: error:no type named ‘type’ in ‘class std::result_of<std::_Mem_fn<void (A::*)(int)>(std::reference_wrapper<A>, int)>’
        typedef typename result_of<_Callable(_Args...)>::type result_type;
                                                              ^
/usr/local/include/c++/4.8.2/functional:1727:9: error:no type named ‘type’ in ‘class std::result_of<std::_Mem_fn<void (A::*)(int)>(std::reference_wrapper<A>, int)>’
          _M_invoke(_Index_tuple<_Indices...>)
          ^

【问题讨论】:

    标签: c++ c++11


    【解决方案1】:

    这不是引用包装器的正确位置。不过,一个简单的指针就足够了,并且可以达到预期的效果:

    std::thread t1(&A::foo, &a, 100);
    

    【讨论】:

    • +1 因为有正确的答案,而不是像某种白痴一样在 GCC bugzilla 上提交错误的错误。
    【解决方案2】:

    编辑:撤回

    Kerrek 在这里是正确的:我错误地认为std::thread 构造函数和std::bind 在设计上是相同的接口。但是,在 [func.bind.bind]/10 中仅为 std::bind 指定了参数从 reference_wrapper&lt;A&gt;A&amp; 的自动转换:

    绑定参数 v1, v2, ..., vN 的值及其对应的类型 V1, V2, ..., VN 取决于从调用 bindcv中派生的类型 TiD >-qualifiers cv 的调用包装g 如下:

    • 如果TiDreference_wrapper&lt;T&gt;,则参数为tid.get(),其类型ViT&amp;
    • ...

    所以reference_wrapper&lt;A&gt; 的这种特殊用法 不受std::thread 的支持,但std::bind 的支持。在这种情况下,std::thread 在其他/较旧的编译器中的行为与 std::bind 相同的事实是错误,而不是 4.8 行 GCC 版本的行为。

    我将在此解释中留下不正确的答案,希望其他人将来不会犯同样的错误。

    简短(但不正确)的答案

    这显然是 GCC 4.8 包含的标准库中的一个错误。代码由以下人员正确编译:

    答案很长(而且也是不正确的):

    std::thread 构造函数的作用

    template <class F, class ...Args>
    explicit thread(F&& f, Args&&... args);
    

    在C++11 30.3.1.2 [thread.thread.constr]/4中有详细说明:

    新的执行线程执行

    INVOKE(DECAY_COPY(std::forward<F>(f)),
           DECAY_COPY(std::forward<Args>(args))...)
    

    在构造线程中评估对DECAY_COPY 的调用。

    DECAY_COPY 在 30.2.6 [thread.decaycopy]/1 中有描述:

    在本条款的几个地方使用了DECAY_COPY(x) 操作。所有这些使用都意味着调用函数decay_copy(x)并使用结果,其中decay_copy定义如下:

    template <class T> typename decay<T>::type decay_copy(T&& v)
    { return std::forward<T>(v); }
    

    在 OP std::thread t1(&amp;A::foo, std::ref(a), 100); 的调用中,所有三个参数都是右值,DECAY_COPY 将在调用之前复制到新线程环境中的对象中,其效果在 20.8.2 [func.require]/1 中描述:

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

    • (t1.*f)(t2, ..., tN)f 是指向类T 的成员函数的指针并且t1T 类型的对象或对T 类型对象的引用或对T 类型对象的引用时派生自T 的类型;
    • ((*t1).*f)(t2, ..., tN)f 是指向T 类的成员函数的指针并且t1 不是上一项中描述的类型之一时;
    • ...

    对于 OP 中的代码,f 是指向类 A 的成员函数的指针,其值为 &amp;A::foot1 是一个左值 reference_wrapper&lt;A&gt;,其存储的引用指向 a,而 @ 987654375@ 是一个int,其值为100。 20.8.2/1 的第二个项目符号适用。由于t1reference_wrapper*t1 评估为存储的引用(根据 20.8.3.3/1)并且在新线程中的调用是有效的

    (a.*&A::foo)(100);
    

    所以是的,该标准完全按照预期描述了 OP 的行为。

    编辑:奇怪的是,GCC 4.8 correctly compiles the very similar example

    class A {
    public:
        void foo(int n) { std::cout << n << std::endl; }
    };
    
    int main()
    {
        A a;
        auto foo = std::bind(&A::foo, std::ref(a), 100);
        foo();
    }
    

    【讨论】:

    • 抱歉,您究竟是如何得出“*t1 评估为存储的引用”的结论的?我的 20.8.3.3 没这么说……
    • @KerrekSB 因为我错误地假设*t1“神奇地”调用operator T&amp;
    • 哈哈,为修复+1,并挖掘INVOKE 的所有细节。它相当复杂,任何类型的系统性阐述都值得赞赏。
    【解决方案3】:

    关于您的问题标题,我将使用 lambda 进行线程构造。有或没有引用,通过调用成员函数或绑定参数。

     std::thread t1([&] { a.foo(100); });
    

    【讨论】:

    • 这似乎是一个更“干净”的方法,即不要将参数或对象甚至函数暴露给线程,只要给线程它想要的一切,这是一个void (*)(void) (嗯,这是最简单的描述方式。)
    【解决方案4】:

    GCC 4.8 是正确的,std::thread 和其他用 INVOKE 定义的组件一定不能用std::bind 来实现。它们不得调用嵌套绑定表达式,并且必须对绑定参数使用完美转发(而不是像 std::bind 那样将它们作为左值转发),此外,正如您发现的那样,它们不会解开 reference_wrapper 对象。在 GCC 4.8 中,我引入了一个内部实现细节 __bind_simple,供 std::thread 等使用,它没有完整的 std::bind 行为。

    虽然与std::bind 的其他区别是可取的,但我认为INVOKE 操作仍应支持reference_wrapper 对象,因此我提交了缺陷报告,请参阅LWG 2219

    【讨论】:

    • std::thread t(&amp;A::foo, &amp;a, std::ref(b)); 这样的东西也会是非法的吗(例如假设一个成员A::foo(Bar&amp;)。我让something similar 在gcc 4.8.2 上编译,我想知道这是一个错误还是标准规定的行为。
    • @juanchopanza,这是工作所必需的。缺陷是您调用成员的类对象不支持reference_wrappers,包装器对函数的参数工作正常。
    【解决方案5】:

    只是想补充一点,我只是通过给 std::bind/std::thread 提供不兼容的参数而得到了同样的错误。就像在实际函数的签名中有更具体的指针时给出一个指向基类的指针。

    【讨论】:

      【解决方案6】:

      好的,问题是 ref(obj) 返回对对象的引用(别名)而不是指针(地址)!要使用线程,我们需要指针而不是引用!请参阅下面的一个方便的程序,以在线程中使用函数指针:

          #include <iostream>
          #include "vector"
          #include "string"
          #include "thread"
          #include "atomic"
          #include "functional"
      
          #include "stdlib.h"
          #include "stdio.h"
          #include "string.h"
          #include "assert.h"
      
          using namespace std;
          //__________________________Global variables_________________________________________________
      
          atomic<int> var(0);
      
          //__________________________class____________________________________________________________
      
          class C
          {
          public:
      
              C()
              {}
      
              static void addition (int a, int b)
              {
                  for(int i= 0; i< a+b; i++)
                      var++;
              }
      
              void subtraction (int a, int b)
              {
                  for(int i= 0; i< a+b; i++)
                      var--;
              }
          };
      
          class D : std::atomic<int>
          {
          public:
              D() : std::atomic<int>(0)
              {}
      
              void increase_member (int n)
              {
                  for (int i=0; i<n; ++i)
                      fetch_add(1);
              }
      
              int get_atomic_val()
              {
                  return this->load();
              }
          };
      
          //________________________________functions________________________________________________
      
          void non_member_add (int a, int b)
          {
              for(int i= 0; i< a+b; i++)
                  var++;
          }
      
          //__________________________________main____________________________________________________
      
          int main ()
          {
              int a=1, b=5;
      
          // (I)...........................................static public member function (with no inheritance).........................................
      
              void (* add_member_func_ptr)(int,int) = C::addition;            // pointer to a static public member function
      
              //defining thread pool for ststic public member_add_ptr
      
              vector<thread> thread_pool;
      
              for (int i=0; i<5; i++)
              {
                  thread_pool.push_back(thread(add_member_func_ptr,a,b));
              }
      
              for(thread& thr: thread_pool)
                  thr.join();
      
              cout<<"static public member function (with no inheritance)\t"<<var<<endl;
      
              //defining thread pool for ststic public member function
      
              var=0;
      
              thread_pool.clear();
      
              for (int i=0; i<5; i++)
              {
                  thread_pool.push_back(thread(C::addition,a,b));             //void (* add_member_func_ptr)(int,int) is equal to C::addition
              }
      
              for(thread& thr: thread_pool)
                  thr.join();
      
              cout<<"static public member function (with no inheritance)\t"<<var<<endl;
      
          // (II)..............................................non-static public member function (with no inheritance)...................................
      
              C bar;
      
              void (C::* sub_member_func_ptr)(int,int) = & C::subtraction;            // pointer to a non-static public member function
      
              var=0;
      
              //defining thread pool for non-ststic public member function
      
              thread_pool.clear();
      
              for (int i=0; i<5; i++)
              {
                  thread_pool.push_back(thread(sub_member_func_ptr,bar,a,b));
              }
      
              for(thread& thr: thread_pool)
                  thr.join();
      
              cout<<"non-static public member function (with no inheritance)\t"<<var<<endl;
      
              var=0;
      
              //defining thread pool for non-ststic public member function
      
              thread_pool.clear();
      
              for (int i=0; i<5; i++)
              {
                  thread_pool.push_back(thread(&C::subtraction,bar,a,b));         //void (C::* sub_member_func_ptr)(int,int) equals & C::subtraction;
              }
      
              for(thread& thr: thread_pool)
                  thr.join();
      
              cout<<"non-static public member function (with no inheritance)\t"<<var<<endl;
      
      
          // (III)................................................non-member function .................................................
      
              void (* non_member_add_ptr)(int,int) = non_member_add;              //pointer to a non-member function
      
              var=0;
      
              //defining thread pool for non_member_add
      
              thread_pool.clear();
      
              for (int i=0; i<5; i++)
              {
                  thread_pool.push_back(thread(non_member_add,a,b));
              }
      
              for(thread& thr: thread_pool)
                  thr.join();
      
              cout<<"non-member function\t"<<var<<endl<<endl;
      
          // (IV)...........................................non-static public member function (with inheritance).........................
      
              D foo;
      
              void (D::* member_func_ptr) (int) = & D::increase_member;                  //pointer to a non-static public member function of a derived class
      
              //defining thread pool for non-ststic public member function of a derived class
      
              thread_pool.clear();
      
              for (int i=0; i<5; i++)
              {
                  thread_pool.push_back(thread(member_func_ptr,&foo,10));                 //use &foo because this is derived class!
              }
      
              for(thread& thr: thread_pool)
                  thr.join();
      
              cout<<"non-static public member function (with inheritance)\t"<<foo.get_atomic_val()<<endl;
      
              //defining thread pool for non-ststic public member function
      
              D poo;
      
              thread_pool.clear();
      
              for (int i=0; i<5; i++)
              {
                  reference_wrapper<D> poo_ref= ref(poo);
      
                  D& poo_ref_= poo_ref.get();             //ref(obj) returns a reference (alias) to an object not a pointer(address)!
      
                  D* d_ptr= &poo;                         //to work with thread we need pointers not references!
      
      
                  thread_pool.push_back(thread(&D::increase_member, d_ptr,10));             //void (D::* member_func_ptr) (int) equals & D::increase_member;
              }
      
              for(thread& thr: thread_pool)
                  thr.join();
      
              cout<<"non-static public member function (with inheritance)\t"<<poo.get_atomic_val()<<endl<<endl;
      
      
              return 0;
          }
      

      【讨论】:

        猜你喜欢
        • 2013-03-22
        • 1970-01-01
        • 2011-10-06
        • 2014-11-24
        • 1970-01-01
        • 1970-01-01
        • 2016-10-22
        • 2011-10-03
        • 1970-01-01
        相关资源
        最近更新 更多