考虑这个简单的可变参数模板函数,它生成一个线程并将args转发给线程函数。 为什么我在这里的线程构造函数中得到模板替换失败?
std::thread t;
void test3(int& a)
{
a = 10;
}
template<class ...Args>
void test(Args&&... args)
{
t = std::thread(test3, std::forward<Args>(args)...);
}
int main()
{
auto timer = 2s;
int a = 1;
test(a);
std::this_thread::sleep_for(timer);
std::cout << a << std::endl;
t.join();
}
编译器输出:
template argument deduction/substitution failed:
/opt/wandbox/gcc-head/include/c++/8.0.0/bits/invoke.h: In substitution of
'template<class _Callable, class ... _Args> constexpr typename
std::__invoke_result<_Functor, _ArgTypes>::type std::__invoke(_Callable&&,
_Args&& ...) [with _Callable = void (*)(int&); _Args = {int}]':
/opt/wandbox/gcc-head/include/c++/8.0.0/thread:233:29: required by
substitution of 'template<long unsigned int ..._Ind> decltype
(std::__invoke(_S_declval<_Ind>()...)) std::thread::_Invoker<std::tuple<void
(*)(int&), int> >::_M_invoke<_Ind ...>(std::_Index_tuple<_Ind1 ...>) [with
long unsigned int ..._Ind = {0, 1}]'
/opt/wandbox/gcc-head/include/c++/8.0.0/thread:240:2: required from
'struct std::thread::_Invoker<std::tuple<void (*)(int&), int> >'
/opt/wandbox/gcc-head/include/c++/8.0.0/thread:127:22: required from
'std::thread::thread(_Callable&&, _Args&& ...) [with _Callable = void (&)
(int&); _Args = {int&}]'
prog.cc:23:14: required from 'void test(Args&& ...) [with Args = {int&}]'
prog.cc:43:11: required from here
/opt/wandbox/gcc-head/include/c++/8.0.0/bits/invoke.h:89:5: error: no type
named 'type' in 'struct std::__invoke_result<void (*)(int&), int>'
当我用std :: ref包装转发参数时,如下所示:
std::thread(test3, std::ref(std::forward<Args>(args)...));
有效。 Aren首先应该完全转发这些论点吗?
答案 0 :(得分:0)
默认情况下,线程复制其参数。另一种选择是疯狂,因为对本地数据的引用往往不太可能持续整个线程的生命周期。
如果你想要,如果你真的想要,传递一个参考文件用std::ref
包裹它:
test(std::ref(a));
现在,您的代码仍然显示未定义的行为; sleep
不同步访问,所有不同步的读/写操作都是未定义的行为。
对于具体的“非敌对”示例,编译器可以自由地假设a
中main
保持不变,直到join
之后,因为这是您参与的第一个同步操作,因此所有非局部效果都可以忽略,并且不会在本地修改。
他们可能无法检测到此优化,您未定义的行为可能会导致您想要发生的事情,但是鼻子恶魔可以任何。
因此请在join
之后移动打印件。
这是C ++。可变寿命管理是程序员的工作。隐含地在线程之间悬挂的引用是一个非常糟糕的主意。