总结:如何通过std::ref()
和std::cref()
包装器正确地将引用传递给std::thread
构造函数
使用std::ref()
包装引用参数,使用std::cref()
包装const
引用参数:
void foo(Data& data);
void foo2(const Data& data);
std::thread t1 = std::thread(foo, std::ref(data));
std::thread t2 = std::thread(foo2, std::cref(data));
问题
我似乎无法在 std::thread()
构造函数中使用引用参数。我本来想将这个问题作为一个单独的问答来解决,但是后来发现了这个问题。下面是来自g++的完整错误输出,以便更容易地搜索此问题。
我的示例:
#include <cstdint>
#include <cstdio>
#include <iostream>
#include <thread>
struct Data
{
int i = 7;
};
void foo(Data& data)
{
printf("data.i = %i\n", data.i);
}
int main()
{
printf("`std::thread` test\n");
Data data;
std::thread t1 = std::thread(foo, data);
t1.join();
return 0;
}
示例构建命令和输出:
eRCaGuy_hello_world/cpp$ g++ -Wall -Wextra -Werror -O3 -std=c++17 -pthread std_thread__pass_parameter_by_reference_to_constructor.cpp -o bin/a && bin/a
In file included from std_thread__pass_parameter_by_reference_to_constructor.cpp:87:
/usr/include/c++/8/thread: In instantiation of ‘std::thread::thread(_Callable&&, _Args&& ...) [with _Callable = void (&)(Data&); _Args = {Data&}; <template-parameter-1-3> = void]’:
std_thread__pass_parameter_by_reference_to_constructor.cpp:105:43: required from here
/usr/include/c++/8/thread:120:17: error: static assertion failed: std::thread arguments must be invocable after conversion to rvalues
static_assert( __is_invocable<typename decay<_Callable>::type,
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
typename decay<_Args>::type...>::value,
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/include/c++/8/thread: In instantiation of ‘struct std::thread::_Invoker<std::tuple<void (*)(Data&), Data> >’:
/usr/include/c++/8/thread:132:22: required from ‘std::thread::thread(_Callable&&, _Args&& ...) [with _Callable = void (&)(Data&); _Args = {Data&}; <template-parameter-1-3> = void]’
std_thread__pass_parameter_by_reference_to_constructor.cpp:105:43: required from here
/usr/include/c++/8/thread:250:2: error: no matching function for call to ‘std::thread::_Invoker<std::tuple<void (*)(Data&), Data> >::_M_invoke(std::thread::_Invoker<std::tuple<void (*)(Data&), Data> >::_Indices)’
operator()()
^~~~~~~~
/usr/include/c++/8/thread:241:4: note: candidate: ‘template<long unsigned int ..._Ind> decltype (std::__invoke((_S_declval<_Ind>)()...)) std::thread::_Invoker<_Tuple>::_M_invoke(std::_Index_tuple<_Ind ...>) [with long unsigned int ..._Ind = {_Ind ...}; _Tuple = std::tuple<void (*)(Data&), Data>]’
_M_invoke(_Index_tuple<_Ind...>)
^~~~~~~~~
/usr/include/c++/8/thread:241:4: note: template argument deduction/substitution failed:
/usr/include/c++/8/thread: In substitution of ‘template<long unsigned int ..._Ind> decltype (std::__invoke(_S_declval<_Ind>()...)) std::thread::_Invoker<std::tuple<void (*)(Data&), Data> >::_M_invoke<_Ind ...>(std::_Index_tuple<_Ind ...>) [with long unsigned int ..._Ind = {0, 1}]’:
/usr/include/c++/8/thread:250:2: required from ‘struct std::thread::_Invoker<std::tuple<void (*)(Data&), Data> >’
/usr/include/c++/8/thread:132:22: required from ‘std::thread::thread(_Callable&&, _Args&& ...) [with _Callable = void (&)(Data&); _Args = {Data&}; <template-parameter-1-3> = void]’
std_thread__pass_parameter_by_reference_to_constructor.cpp:105:43: required from here
/usr/include/c++/8/thread:243:29: error: no matching function for call to ‘__invoke(std::__tuple_element_t<0, std::tuple<void (*)(Data&), Data> >, std::__tuple_element_t<1, std::tuple<void (*)(Data&), Data> >)’
-> decltype(std::__invoke(_S_declval<_Ind>()...))
~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~
In file included from /usr/include/c++/8/tuple:41,
from /usr/include/c++/8/bits/unique_ptr.h:37,
from /usr/include/c++/8/memory:80,
from /usr/include/c++/8/thread:39,
from std_thread__pass_parameter_by_reference_to_constructor.cpp:87:
/usr/include/c++/8/bits/invoke.h:89:5: note: candidate: ‘template<class _Callable, class ... _Args> constexpr typename std::__invoke_result<_Functor, _ArgTypes>::type std::__invoke(_Callable&&, _Args&& ...)’
__invoke(_Callable&& __fn, _Args&&... __args)
^~~~~~~~
/usr/include/c++/8/bits/invoke.h:89:5: note: template argument deduction/substitution failed:
/usr/include/c++/8/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 (*)(Data&); _Args = {Data}]’:
/usr/include/c++/8/thread:243:29: required by substitution of ‘template<long unsigned int ..._Ind> decltype (std::__invoke(_S_declval<_Ind>()...)) std::thread::_Invoker<std::tuple<void (*)(Data&), Data> >::_M_invoke<_Ind ...>(std::_Index_tuple<_Ind ...>) [with long unsigned int ..._Ind = {0, 1}]’
/usr/include/c++/8/thread:250:2: required from ‘struct std::thread::_Invoker<std::tuple<void (*)(Data&), Data> >’
/usr/include/c++/8/thread:132:22: required from ‘std::thread::thread(_Callable&&, _Args&& ...) [with _Callable = void (&)(Data&); _Args = {Data&}; <template-parameter-1-3> = void]’
std_thread__pass_parameter_by_reference_to_constructor.cpp:105:43: required from here
/usr/include/c++/8/bits/invoke.h:89:5: error: no type named ‘type’ in ‘struct std::__invoke_result<void (*)(Data&), Data>’
有很多需要注意的地方,但是我首先注意到的错误是:
/usr/include/c++/8/thread:120:17: error: static assertion failed: std::thread arguments must be invocable after conversion to rvalues
问题出在哪里呢?
解决方法
你不能直接将引用传递给 std::thread()
构造函数。必须使用 std::ref()
对它们进行包装。
查看这里的 std::thread::thread()
构造函数参考页面:https://en.cppreference.com/w/cpp/thread/thread/thread:
线程函数的参数通过值移动或复制。如果需要将引用参数传递给线程函数,则必须对其进行包装(例如,使用 std::ref
或 std::cref
)。
因此,用 std::ref()
包装引用参数,并用 std::cref()
包装 const
引用参数,例如:
std::thread t1 = std::thread(foo, std::ref(data));
std::thread t2 = std::thread(foo2, std::cref(data));
完整示例:
std_thread__pass_parameter_by_reference_to_constructor_via_std_ref.cpp:
#include <cstdint>
#include <cstdio>
#include <iostream>
#include <thread>
struct Data
{
int i = 7;
};
void foo(Data& data)
{
printf("data.i = %i\n", data.i);
}
void foo2(const Data& data)
{
printf("data.i = %i\n", data.i);
}
int main()
{
printf("`std::thread` test\n");
Data data;
std::thread t1 = std::thread(foo, std::ref(data));
std::thread t2 = std::thread(foo2, std::cref(data));
t1.join();
t2.join();
return 0;
}
参考资料
- 我最初学习这个的地方:C++11 std::thread accepting function with rvalue parameter;在这个答案下面看到我的评论
- https://en.cppreference.com/w/cpp/thread/thread/thread
线程函数的参数是按值移动或复制的。如果需要将引用参数传递给线程函数,则必须进行包装(例如,使用std::ref
或std::cref
)。
lp
变量,或将p
移入lp
。 - Some programmer dudelp.use_count()
的结果取决于线程的调度方式,你当前代码中线程中lp.use_count()
显示的数字是不可预测的。 - t.niesesleep_for
。对于使用计数为5的“How to explain it?”是因为所有3个生成的线程之间存在竞争条件。 - Eljay