采用引用参数的 C++ 线程编译失败

Posted

技术标签:

【中文标题】采用引用参数的 C++ 线程编译失败【英文标题】:C++ Thread taking reference argument failed compile 【发布时间】:2016-03-31 19:10:11 【问题描述】:
#include<iostream>
#include<thread>
using namespace std;

void f1(double& ret) 
   ret=5.;


int main() 
   double ret=0.;
   thread t1(f1, ret);
   t1.join();
   cout << "ret=" << ret << endl;

以上代码编译失败,error message:

g++ -std=c++14 -O2 -Wall -pedantic -pthread main.cpp && ./a.out
In file included from /usr/local/include/c++/5.3.0/thread:39:0,
                 from main.cpp:2:
/usr/local/include/c++/5.3.0/functional: In instantiation of 'struct std::_Bind_simple<void (*(double))(double&)>':
/usr/local/include/c++/5.3.0/thread:137:59:   required from 'std::thread::thread(_Callable&&, _Args&& ...) [with _Callable = void (&)(double&); _Args = double&]'
main.cpp:11:21:   required from here
/usr/local/include/c++/5.3.0/functional:1505:61: error: no type named 'type' in 'class std::result_of<void (*(double))(double&)>'
       typedef typename result_of<_Callable(_Args...)>::type result_type;
                                                             ^
/usr/local/include/c++/5.3.0/functional:1526:9: error: no type named 'type' in 'class std::result_of<void (*(double))(double&)>'
         _M_invoke(_Index_tuple<_Indices...>)
         ^

我知道我可以使用std::ref() 来传递参数。但是如果我按值传递,为什么会出现错误,因为thread 应该只通过 value 复制参数并传递存储在线程内的一些对象以与函数 f1 的引用参数绑定。

我觉得如果我能理解这个result_of在做什么以及为什么会出错,我就能更好地理解原因。那么任何人都可以引导我完成错误消息吗?特别是std::_Bind_simple&lt;void (*(double))(double&amp;)&gt;std::result_of&lt;void (*(double))(double&amp;)&gt;的含义。

EDIT:我知道如果我传递一个值,线程只会在副本上工作,线程返回后没有效果。这不是我关心的问题。我想知道为什么它现在给出错误,但它没有给 SO 上的其他帖子给出错误,如下所示:Difference between pointer and reference as thread parameter

【问题讨论】:

@Barry,您的编辑丢失了“突出显示的部分”。 @JonathanWakely 解决了这个问题。 【参考方案1】:

我知道如果我传递了一个值,线程只会在副本上工作,线程返回后没有效果。

不,这是不正确的。代码不应该默默地复制并在副本上工作,标准规定它甚至不能编译。

标准要求将被调用函数的参数复制(到由 C++ 运行时管理的存储中),然后将副本作为右值转发。因此,在您的示例中,f1 传递了一个 double 类型的右值,而 double&amp; 类型的参数无法绑定到该右值。

标准要求这样做的原因是没有静默复制和数据丢失:如果函数需要可修改的引用,那么除非您使用 reference_wrapper 显式传递引用,否则它将无法编译。

您得到的编译器错误涉及result_of,因为这就是我使 GCC 的std::thread 检查是否可以使用提供的参数调用函数的方式。我使用result_of&lt;decltype(&amp;f1)(double)&gt; 来检查是否可以使用double 类型的右值调用函数指针&amp;f1(类型为void(*)(double&amp;))。不能用该类型的参数调用它,所以嵌套类型result_of&lt;decltype(&amp;f1)(double)&gt;::type没有定义,所以编译器说:

error: no type named 'type' in 'class std::result_of<void (*(double))(double&)>'

该错误有点令人困惑,因为 C++ 声明器规则意味着 decltype(&amp;f1)(double) 显示为 void(*(double))(double&amp;)

这不是我关心的问题。我想知道为什么它现在会出错,但它没有给 SO 上的其他帖子出错

那些帖子使用的是旧的 C++11 之前的编译器或不符合 C++11 标准要求的不符合要求的编译器,并且错误地编译了代码。

【讨论】:

来自en.cppreference.com/w/cpp/thread/thread/thread,它明确表示会将参数复制/移动到线程可访问存储 那又怎样?这并不意味着它应该编译。确保您已刷新我的答案以查看最新版本,该版本解释了副本作为右值转发,因此无法绑定到非 const 左值引用参数。 您能详细说明std::result_of 错误吗?是按照您所说的那样尝试执行标准要求的操作,即将副本作为右值传递吗? result_of 错误意味着您不能使用double 类型的参数调用void(double&amp;) 类型的函数,这是真的,因为您不能将非常量左值引用绑定到右值。所以你不能用这些参数构造一个std::thread,因为用右值调用f1不会编译。 我有点困惑。为什么double的参数类型是右值?【参考方案2】:

乔纳森的回答是肯定的。花时间研究它是值得的。

与此同时,修改代码将做你想做的事 - 即将引用发送到线程函数:

thread t1(f1, std::ref(ret));

【讨论】:

以上是关于采用引用参数的 C++ 线程编译失败的主要内容,如果未能解决你的问题,请参考以下文章

C++ 把引用作为参数

C++ 对象作为返回值:复制还是引用?

python中函数参数的引用方式

取消引用从线程原因返回的指针;分段错误:11 [重复]

C++ Primer 5th笔记(chap 16 模板和泛型编程)模板实参推断和引用

C++ 引用包装器作为函数参数