C++并发编程:std::thread右值形式传参解析
C++并发编程实战(第二版)2.2节开头有这句话:
如代码清单2.4所示,若需向新线程上的函数或可调用对象传递参数,方法相当简单,直接向std::thread 的构造函数增添更多参数即可。不过请务必牢记,线程具有内部存储空间,参数会按照默认方式先复制到该处,新创建的执行线程才能直接访问它们。然后,这些副本被当成临时变量,以右值形式传给新线程上的函数或可调用对象。即便函数的相关参数按设想应该是引用,上述过程依然会发生。
这里有个重点就是参数会被复制到线程内部存储空间中,然后这些副本会被当成临时变量以右值形式传给新线程上的函数或可调用对象,英文原版也是这么说的:
As shown in listing 2.4, passing arguments to the callable object or function is fundamentally as simple as passing additional arguments to the std::thread constructor. But it’s important to bear in mind that by default, the arguments are copied into internal storage, where they can be accessed by the newly created thread of execution, and then passed to the callable object or function as rvalues as if they were temporaries.
这一切都源自线程启动后,参数的生命周期必须至少持续到线程执行结束(尤其是引用或指针参数),否则会出现未定义行为(如访问已销毁的变量)。我们来看一下MSVC的源码:
_STD_BEGIN
#if _HAS_CXX20
_EXPORT_STD class jthread;
#endif // _HAS_CXX20_EXPORT_STD class thread { // class for observing and managing threads
public:class id;using native_handle_type = void*;thread() noexcept : _Thr{} {}private:
#if _HAS_CXX20friend jthread;
#endif // _HAS_CXX20template <class _Tuple, size_t... _Indices>static unsigned int __stdcall _Invoke(void* _RawVals) noexcept /* terminates */ {// adapt invoke of user's callable object to _beginthreadex's thread procedureconst unique_ptr<_Tuple> _FnVals(static_cast<_Tuple*>(_RawVals));_Tuple& _Tup = *_FnVals.get(); // avoid ADL, handle incomplete types_STD invoke(_STD move(_STD get<_Indices>(_Tup))...);_Cnd_do_broadcast_at_thread_exit(); // TRANSITION, ABIreturn 0;}template <class _Tuple, size_t... _Indices>_NODISCARD static constexpr auto _Get_invoke(index_sequence<_Indices...>) noexcept {return &_Invoke<_Tuple, _Indices...>;}#pragma warning(push) // pointer or reference to potentially throwing function passed to 'extern "C"' function under
#pragma warning(disable : 5039) // -EHc. Undefined behavior may occur if this function throws an exception. (/Wall)template <class _Fn, class... _Args>void _Start(_Fn&& _Fx, _Args&&... _Ax) {using _Tuple = tuple<decay_t<_Fn>, decay_t<_Args>...>;auto _Decay_copied = _STD make_unique<_Tuple>(_STD forward<_Fn>(_Fx), _STD forward<_Args>(_Ax)...);constexpr auto _Invoker_proc = _Get_invoke<_Tuple>(make_index_sequence<1 + sizeof...(_Args)>{});_Thr._Hnd =reinterpret_cast<void*>(_CSTD _beginthreadex(nullptr, 0, _Invoker_proc, _Decay_copied.get(), 0, &_Thr._Id));if (_Thr._Hnd) { // ownership transferred to the thread(void) _Decay_copied.release();} else { // failed to start thread_Thr._Id = 0;_Throw_Cpp_error(_RESOURCE_UNAVAILABLE_TRY_AGAIN);}}
#pragma warning(pop)public:template <class _Fn, class... _Args, enable_if_t<!is_same_v<_Remove_cvref_t<_Fn>, thread>, int> = 0>_NODISCARD_CTOR_THREAD explicit thread(_Fn&& _Fx, _Args&&... _Ax) {_Start(_STD forward<_Fn>(_Fx), _STD forward<_Args>(_Ax)...);}~thread() noexcept {if (joinable()) {_STD terminate(); // per N4950 [thread.thread.destr]/1}}thread(thread&& _Other) noexcept : _Thr(_STD exchange(_Other._Thr, {})) {}thread& operator=(thread&& _Other) noexcept {if (joinable()) {_STD terminate(); // per N4950 [thread.thread.assign]/1}_Thr = _STD exchange(_Other._Thr, {});return *this;}thread(const thread&) = delete;thread& operator=(const thread&) = delete;void swap(thread& _Other) noexcept {_STD swap(_Thr, _Other._Thr);}_NODISCARD bool joinable() const noexcept {return _Thr._Id != 0;}void join() {if (!joinable()) {_Throw_Cpp_error(_INVALID_ARGUMENT);}if (_Thr._Id == _Thrd_id()) {_Throw_Cpp_error(_RESOURCE_DEADLOCK_WOULD_OCCUR);}if (_Thrd_join(_Thr, nullptr) != _Thrd_result::_Success) {_Throw_Cpp_error(_NO_SUCH_PROCESS);}_Thr = {};}void detach() {if (!joinable()) {_Throw_Cpp_error(_INVALID_ARGUMENT);}if (_Thrd_detach(_Thr) != _Thrd_result::_Success) {_Throw_Cpp_error(_INVALID_ARGUMENT);}_Thr = {};}_NODISCARD id get_id() const noexcept;_NODISCARD native_handle_type native_handle() noexcept /* strengthened */ { // return Win32 HANDLE as void *return _Thr._Hnd;}_NODISCARD static unsigned int hardware_concurrency() noexcept {return _Thrd_hardware_concurrency();}private:_Thrd_t _Thr;
};
我们可以知道整个流程:
//首先调用模板构造函数
public:template <class _Fn, class... _Args, enable_if_t<!is_same_v<_Remove_cvref_t<_Fn>, thread>, int> = 0>_NODISCARD_CTOR_THREAD explicit thread(_Fn&& _Fx, _Args&&... _Ax) {_Start(_STD forward<_Fn>(_Fx), _STD forward<_Args>(_Ax)...);}//然后进入_Start
#pragma warning(push) // pointer or reference to potentially throwing function passed to 'extern "C"' function under
#pragma warning(disable : 5039) // -EHc. Undefined behavior may occur if this function throws an exception. (/Wall)template <class _Fn, class... _Args>void _Start(_Fn&& _Fx, _Args&&... _Ax) {using _Tuple = tuple<decay_t<_Fn>, decay_t<_Args>...>;auto _Decay_copied = _STD make_unique<_Tuple>(_STD forward<_Fn>(_Fx), _STD forward<_Args>(_Ax)...);constexpr auto _Invoker_proc = _Get_invoke<_Tuple>(make_index_sequence<1 + sizeof...(_Args)>{});_Thr._Hnd =reinterpret_cast<void*>(_CSTD _beginthreadex(nullptr, 0, _Invoker_proc, _Decay_copied.get(), 0, &_Thr._Id));if (_Thr._Hnd) { // ownership transferred to the thread(void) _Decay_copied.release();} else { // failed to start thread_Thr._Id = 0;_Throw_Cpp_error(_RESOURCE_UNAVAILABLE_TRY_AGAIN);}}
#pragma warning(pop)
其中最重要的是下面这个流程:
using _Tuple = tuple<decay_t<_Fn>, decay_t<_Args>...>;
auto _Decay_copied = _STD make_unique<_Tuple>(_STD forward<_Fn>(_Fx), _STD forward<_Args>(_Ax)...);
constexpr auto _Invoker_proc = _Get_invoke<_Tuple>(make_index_sequence<1 + sizeof...(_Args)>{});
这里有一个类型转换工具decay_t,这个类型转换工具会对类型T执行退化操作:
- 移除引用修饰符(&)
- 移除 const/volatile 限定符
- 将数组类型转为指针类型
- 将函数类型转为函数指针类型
这里需要注意,这个类型转换工具并不会影响原始类型,只是根据原始类型推断其退化后的类型。而后面构造元组时std::forward的作用是在构造元组对象时,根据原始参数的值类别(左值 / 右值),以最优方式传递参数(左值则拷贝,右值则移动),避免性能损耗。
然后:
- make_index_sequence:会生成一个索引序列(如0,1,2…),长度为1 + 参数数量(1 是函数本身,其余是参数)。
- _Get_invoke:根据元组类型_Tuple和索引序列,返回一个实例化的_Invoke函数指针(_Invoker_proc)。
- _Invoke是线程的实际入口函数,负责从元组中解析出函数和参数并执行。
下面是invoke的源码:
template <class _Tuple, size_t... _Indices>
_NODISCARD static constexpr auto _Get_invoke(index_sequence<_Indices...>) noexcept {return &_Invoke<_Tuple, _Indices...>;
}template <class _Tuple, size_t... _Indices>
static unsigned int __stdcall _Invoke(void* _RawVals) noexcept /* terminates */ {// adapt invoke of user's callable object to _beginthreadex's thread procedureconst unique_ptr<_Tuple> _FnVals(static_cast<_Tuple*>(_RawVals));_Tuple& _Tup = *_FnVals.get(); // avoid ADL, handle incomplete types_STD invoke(_STD move(_STD get<_Indices>(_Tup))...);_Cnd_do_broadcast_at_thread_exit(); // TRANSITION, ABIreturn 0;
}template <class _Tuple, size_t... _Indices>
_NODISCARD static constexpr auto _Get_invoke(index_sequence<_Indices...>) noexcept {return &_Invoke<_Tuple, _Indices...>;
}//多参数版本
_EXPORT_STD template <class _Callable, class _Ty1, class... _Types2>
_CONSTEXPR17 auto invoke(_Callable&& _Obj, _Ty1&& _Arg1, _Types2&&... _Args2) noexcept(noexcept(_Invoker1<_Callable, _Ty1>::_Call(static_cast<_Callable&&>(_Obj), static_cast<_Ty1&&>(_Arg1), static_cast<_Types2&&>(_Args2)...)))-> decltype(_Invoker1<_Callable, _Ty1>::_Call(static_cast<_Callable&&>(_Obj), static_cast<_Ty1&&>(_Arg1), static_cast<_Types2&&>(_Args2)...)) {if constexpr (_Invoker1<_Callable, _Ty1>::_Strategy == _Invoker_strategy::_Functor) {return static_cast<_Callable&&>(_Obj)(static_cast<_Ty1&&>(_Arg1), static_cast<_Types2&&>(_Args2)...);} else if constexpr (_Invoker1<_Callable, _Ty1>::_Strategy == _Invoker_strategy::_Pmf_object) {return (static_cast<_Ty1&&>(_Arg1).*_Obj)(static_cast<_Types2&&>(_Args2)...);} else if constexpr (_Invoker1<_Callable, _Ty1>::_Strategy == _Invoker_strategy::_Pmf_refwrap) {return (_Arg1.get().*_Obj)(static_cast<_Types2&&>(_Args2)...);} else if constexpr (_Invoker1<_Callable, _Ty1>::_Strategy == _Invoker_strategy::_Pmf_pointer) {return ((*static_cast<_Ty1&&>(_Arg1)).*_Obj)(static_cast<_Types2&&>(_Args2)...);} else if constexpr (_Invoker1<_Callable, _Ty1>::_Strategy == _Invoker_strategy::_Pmd_object) {return static_cast<_Ty1&&>(_Arg1).*_Obj;} else if constexpr (_Invoker1<_Callable, _Ty1>::_Strategy == _Invoker_strategy::_Pmd_refwrap) {return _Arg1.get().*_Obj;} else {_STL_INTERNAL_STATIC_ASSERT(_Invoker1<_Callable, _Ty1>::_Strategy == _Invoker_strategy::_Pmd_pointer);return (*static_cast<_Ty1&&>(_Arg1)).*_Obj;}
}//无参数版本
_EXPORT_STD template <class _Callable>
_CONSTEXPR17 auto invoke(_Callable&& _Obj) noexcept(noexcept(static_cast<_Callable&&>(_Obj)()))-> decltype(static_cast<_Callable&&>(_Obj)()) {return static_cast<_Callable&&>(_Obj)();
}
_Get_invoke的作用是根据元组类型_Tuple和索引序列_Indices…,生成一个实例化的_Invoke函数指针。它是连接模板参数与线程入口函数的 “桥梁”。然后是_STD invoke(_STD move(_STD get<_Indices>(_Tup))…);:
- std::get<_Indices>(_Tup)…:通过索引序列_Indices…从元组中提取元素。例如,若_Indices是0,1,2,则提取_Tup[0](函数)、_Tup[1](第一个参数)、_Tup[2](第二个参数)。
- std::move(…):将提取的元素转为右值,触发移动语义(减少拷贝,尤其是对于大对象)。
- std::invoke(…):调用提取的函数,并传递提取的参数。std::invoke支持任意可调用对象(函数、lambda、函数对象等),自动匹配参数类型
C++标准库中std::invoke
是一个通用的调用器,能够统一调用各种可调用对象(函数、成员函数、函数对象、成员变量指针等),并正确处理参数传递。下面详细解释其功能和实现细节:
1. 函数基本定义与模板参数
_EXPORT_STD template <class _Callable, class _Ty1, class... _Types2>
_CONSTEXPR17 auto invoke(_Callable&& _Obj, // 可调用对象(函数、成员函数指针等)_Ty1&& _Arg1, // 第一个参数(可能是对象实例,用于成员调用)_Types2&&... _Args2 // 剩余参数(函数的实际参数)
)
// 异常说明:与被调用对象的异常特性一致
noexcept(noexcept(_Invoker1<_Callable, _Ty1>::_Call(...)))
// 返回值类型:由被调用对象的返回类型决定
-> decltype(_Invoker1<_Callable, _Ty1>::_Call(...)) {// ... 实现 ...
}
- 核心作用:提供统一接口调用任意可调用对象,屏蔽不同类型可调用对象的调用语法差异。
- 模板参数:
_Callable
:可调用对象类型(函数、lambda、成员函数指针等)。_Ty1
:第一个参数类型(对于成员函数/变量,通常是对象实例或指针)。_Types2
:剩余参数的类型包。
2. 异常说明与返回类型推导
noexcept(noexcept(_Invoker1<_Callable, _Ty1>::_Call(...)))
-> decltype(_Invoker1<_Callable, _Ty1>::_Call(...))
- 异常说明(noexcept):通过
noexcept
表达式传递被调用对象的异常特性,即invoke
是否抛出异常取决于被调用对象。 - 返回类型推导(decltype):使用
decltype
自动推导返回类型,与被调用对象的返回类型一致。
3. 调用策略与_Invoker1
的作用
代码的核心是通过_Invoker1<_Callable, _Ty1>
判断可调用对象的类型,并选择对应的调用策略。_Invoker1
是一个内部辅助模板,用于在编译期分析_Callable
和第一个参数_Ty1
的关系,确定调用方式(即_Strategy
)。
_Invoker_strategy
(调用策略)包含以下类型,对应不同可调用对象的处理方式:
策略1:_Functor
(函数对象/普通函数)
if constexpr (_Invoker1<...>::_Strategy == _Invoker_strategy::_Functor) {return static_cast<_Callable&&>(_Obj)(static_cast<_Ty1&&>(_Arg1), static_cast<_Types2&&>(_Args2)...);
}
- 适用场景:普通函数、lambda表达式、函数对象(重载了
operator()
的类)等。 - 调用方式:直接使用函数调用语法
obj(arg1, arg2...)
。 - 示例:
void func(int a, int b) { ... } std::invoke(func, 10, 20); // 等价于 func(10, 20)
策略2:_Pmf_object
(成员函数 + 对象实例)
else if constexpr (_Strategy == _Invoker_strategy::_Pmf_object) {return (static_cast<_Ty1&&>(_Arg1).*_Obj)(static_cast<_Types2&&>(_Args2)...);
}
- 适用场景:调用类的非静态成员函数,且第一个参数是对象实例(左值/右值)。
- 调用方式:使用成员函数调用语法
(obj.*mem_func)(args...)
。 - 示例:
struct A { void method(int a) { ... } }; A a; std::invoke(&A::method, a, 10); // 等价于 (a.*&A::method)(10)
策略3:_Pmf_refwrap
(成员函数 + 引用包装器)
else if constexpr (_Strategy == _Invoker_strategy::_Pmf_refwrap) {return (_Arg1.get().*_Obj)(static_cast<_Types2&&>(_Args2)...);
}
- 适用场景:调用成员函数,且第一个参数是
std::reference_wrapper
(包装了对象引用)。 - 调用方式:先通过
get()
获取包装的对象引用,再调用成员函数。 - 示例:
A a; auto ref = std::ref(a); std::invoke(&A::method, ref, 10); // 等价于 (ref.get().*&A::method)(10)
策略4:_Pmf_pointer
(成员函数 + 对象指针)
else if constexpr (_Strategy == _Invoker_strategy::_Pmf_pointer) {return ((*static_cast<_Ty1&&>(_Arg1)).*_Obj)(static_cast<_Types2&&>(_Args2)...);
}
- 适用场景:调用成员函数,且第一个参数是对象指针(
T*
)。 - 调用方式:先解引用指针得到对象,再调用成员函数:
((*ptr).*mem_func)(args...)
。 - 示例:
A* a_ptr = new A(); std::invoke(&A::method, a_ptr, 10); // 等价于 ((*a_ptr).*&A::method)(10)
策略5-7:成员变量访问(_Pmd_*
)
// 成员变量 + 对象实例
else if constexpr (_Strategy == _Invoker_strategy::_Pmd_object) {return static_cast<_Ty1&&>(_Arg1).*_Obj;
}
// 成员变量 + 引用包装器
else if constexpr (_Strategy == _Invoker_strategy::_Pmd_refwrap) {return _Arg1.get().*_Obj;
}
// 成员变量 + 对象指针
else {return (*static_cast<_Ty1&&>(_Arg1)).*_Obj;
}
- 适用场景:访问类的非静态成员变量(通过成员变量指针)。
- 调用方式:使用成员访问语法
obj.*mem_ptr
或(*ptr).*mem_ptr
。 - 示例:
struct A { int x; }; A a{42}; std::invoke(&A::x, a); // 等价于 a.*&A::x → 返回42
这段代码是C++标准库中std::invoke
实现的核心辅助模板——_Invoker1
的定义,它通过编译期类型判断,确定可调用对象(_Callable
)与第一个参数(_Ty1
)的关系,从而选择正确的调用策略(如普通函数调用、成员函数调用、成员变量访问等)。
这其中涉及了一个模板类_Invoker1:
template <class _Callable, class _Ty1, class _Removed_cvref = _Remove_cvref_t<_Callable>,bool _Is_pmf = is_member_function_pointer_v<_Removed_cvref>,bool _Is_pmd = is_member_object_pointer_v<_Removed_cvref>>
struct _Invoker1;template <class _Callable, class _Ty1, class _Removed_cvref>
struct _Invoker1<_Callable, _Ty1, _Removed_cvref, true, false>: conditional_t<is_same_v<typename _Is_memfunptr<_Removed_cvref>::_Class_type, _Remove_cvref_t<_Ty1>>|| is_base_of_v<typename _Is_memfunptr<_Removed_cvref>::_Class_type, _Remove_cvref_t<_Ty1>>,_Invoker_pmf_object,conditional_t<_Is_specialization_v<_Remove_cvref_t<_Ty1>, reference_wrapper>, _Invoker_pmf_refwrap,_Invoker_pmf_pointer>> {}; // pointer to member functiontemplate <class _Callable, class _Ty1, class _Removed_cvref>
struct _Invoker1<_Callable, _Ty1, _Removed_cvref, false, true>: conditional_t<is_same_v<typename _Is_member_object_pointer<_Removed_cvref>::_Class_type, _Remove_cvref_t<_Ty1>>|| is_base_of_v<typename _Is_member_object_pointer<_Removed_cvref>::_Class_type, _Remove_cvref_t<_Ty1>>,_Invoker_pmd_object,conditional_t<_Is_specialization_v<_Remove_cvref_t<_Ty1>, reference_wrapper>, _Invoker_pmd_refwrap,_Invoker_pmd_pointer>> {}; // pointer to member datatemplate <class _Callable, class _Ty1, class _Removed_cvref>
struct _Invoker1<_Callable, _Ty1, _Removed_cvref, false, false> : _Invoker_functor {};
简单说,_Invoker1
的作用是:在编译期“识别”可调用对象的类型(是普通函数?成员函数指针?还是成员变量指针?),以及它与第一个参数的关系,然后绑定到对应的调用逻辑。
整体结构:模板特化与条件继承
_Invoker1
是一个类模板,通过模板特化和条件继承(conditional_t
)实现不同场景的调用策略分发。它有3个主要特化版本,分别对应“成员函数指针”“成员变量指针”和“普通可调用对象”。
基本定义:
template <class _Callable, class _Ty1, class _Removed_cvref = _Remove_cvref_t<_Callable>, // 移除_Callable的cv限定符和引用bool _Is_pmf = is_member_function_pointer_v<_Removed_cvref>, // 是否是成员函数指针bool _Is_pmd = is_member_object_pointer_v<_Removed_cvref>> // 是否是成员变量指针
struct _Invoker1;
- 模板参数
_Removed_cvref
:对_Callable
进行“去cv引用”处理(移除const
/volatile
和引用),得到纯粹的可调用对象类型。 - 两个布尔值
_Is_pmf
和_Is_pmd
:通过类型 traits 判断_Callable
是否为“成员函数指针”(pmf)或“成员变量指针”(pmd)。
特化版本1:处理成员函数指针(_Is_pmf = true
)
template <class _Callable, class _Ty1, class _Removed_cvref>
struct _Invoker1<_Callable, _Ty1, _Removed_cvref, true, false>: conditional_t<// 条件1:第一个参数的类型是成员函数所属类的实例或派生类实例is_same_v<typename _Is_memfunptr<_Removed_cvref>::_Class_type, _Remove_cvref_t<_Ty1>>|| is_base_of_v<typename _Is_memfunptr<_Removed_cvref>::_Class_type, _Remove_cvref_t<_Ty1>>,_Invoker_pmf_object, // 策略1:直接通过对象实例调用成员函数(obj.*mem_func)// 条件2:如果条件1不满足,检查第一个参数是否是reference_wrapperconditional_t<_Is_specialization_v<_Remove_cvref_t<_Ty1>, reference_wrapper>,_Invoker_pmf_refwrap, // 策略2:通过reference_wrapper调用(ref.get().*mem_func)_Invoker_pmf_pointer // 策略3:通过对象指针调用((*ptr).*mem_func)>> {};
作用:
当_Callable
是成员函数指针(如&A::method
)时,通过以下逻辑选择调用策略:
- 若第一个参数(
_Ty1
)是成员函数所属类(A
)的实例或派生类实例 → 使用_Invoker_pmf_object
,调用语法为(obj.*mem_func)(args...)
。 - 若第一个参数是
reference_wrapper<A>
(引用包装器) → 使用_Invoker_pmf_refwrap
,调用语法为(ref.get().*mem_func)(args...)
。 - 否则(通常是对象指针
A*
) → 使用_Invoker_pmf_pointer
,调用语法为((*ptr).*mem_func)(args...)
。
特化版本2:处理成员变量指针(_Is_pmd = true
)
template <class _Callable, class _Ty1, class _Removed_cvref>
struct _Invoker1<_Callable, _Ty1, _Removed_cvref, false, true>: conditional_t<// 条件1:第一个参数的类型是成员变量所属类的实例或派生类实例is_same_v<typename _Is_member_object_pointer<_Removed_cvref>::_Class_type, _Remove_cvref_t<_Ty1>>|| is_base_of_v<typename _Is_member_object_pointer<_Removed_cvref>::_Class_type, _Remove_cvref_t<_Ty1>>,_Invoker_pmd_object, // 策略1:直接通过对象实例访问成员变量(obj.*mem_ptr)// 条件2:如果条件1不满足,检查第一个参数是否是reference_wrapperconditional_t<_Is_specialization_v<_Remove_cvref_t<_Ty1>, reference_wrapper>,_Invoker_pmd_refwrap, // 策略2:通过reference_wrapper访问(ref.get().*mem_ptr)_Invoker_pmd_pointer // 策略3:通过对象指针访问((*ptr).*mem_ptr)>> {};
作用:
当_Callable
是成员变量指针(如&A::x
)时,逻辑与成员函数类似:
- 若第一个参数是类
A
的实例 → 使用_Invoker_pmd_object
,访问语法为obj.*mem_ptr
。 - 若第一个参数是
reference_wrapper<A>
→ 使用_Invoker_pmd_refwrap
,访问语法为ref.get().*mem_ptr
。 - 否则(通常是
A*
) → 使用_Invoker_pmd_pointer
,访问语法为(*ptr).*mem_ptr
。
特化版本3:处理普通可调用对象(非成员指针)
template <class _Callable, class _Ty1, class _Removed_cvref>
struct _Invoker1<_Callable, _Ty1, _Removed_cvref, false, false> : _Invoker_functor {};
作用:
当_Callable
是普通可调用对象(非成员函数指针、非成员变量指针)时,直接继承_Invoker_functor
,调用策略为普通函数调用:
- 包括:普通函数(如
void func()
)、lambda表达式、函数对象(重载operator()
的类)等。 - 调用语法为
obj(args...)
(直接调用可调用对象)。
辅助工具与类型判断
代码中用到了多个C++标准库的类型 traits(类型特性)工具,用于编译期类型判断:
_Remove_cvref_t<T>
:移除T
的const
/volatile
和引用(&
/&&
),得到原始类型。is_member_function_pointer_v<T>
:判断T
是否为成员函数指针。is_member_object_pointer_v<T>
:判断T
是否为成员变量指针。is_same_v<T, U>
:判断T
和U
是否为同一类型。is_base_of_v<T, U>
:判断T
是否为U
的基类(用于派生类实例判断)。_Is_specialization_v<T, Template>
:判断T
是否为模板Template
的特化(如reference_wrapper<int>
是reference_wrapper
的特化)。
总结
_Invoker1
是std::invoke
实现的“大脑”,它通过以下步骤工作:
- 对可调用对象(
_Callable
)进行类型清理(_Removed_cvref
)。 - 判断
_Callable
是成员函数指针、成员变量指针,还是普通可调用对象(通过_Is_pmf
和_Is_pmd
)。 - 根据第一个参数(
_Ty1
)的类型(实例、引用包装器、指针),选择对应的调用策略(继承不同的_Invoker_*
类)。 - 最终由选中的
_Invoker_*
类实现具体的调用语法(如obj.*mem_func
、func(args)
等)。
这一设计通过编译期类型分发,实现了std::invoke
对任意可调用对象的统一调用,是C++泛型编程中“编译期多态”的典型应用。
综上我们可以看出整个流程:传入的函数和参数首先以退化后的类型存放到了一个元组中,然后通过_Invoke函数指针找到根据传入的元组而实例化的函数实例,这个函数实例会解析元组中的函数及其参数,其参数会被std::move全部转换成右值传入std::invoke这个调用器中,因此强制类型转换成右值引用时,函数的指针类型和传入的参数也都变成了右值引用,但是这里有个例外,如果用户通过std::ref或std::cref传递参数(如std::thread t(f, std::ref(x))),元组中存储的是std::reference_wrapper对象,而非原始对象的副本。此时:
- _STD move(reference_wrapper)虽然将其转为右值,但reference_wrapper的移动操作不改变其引用的目标对象。
- 在invoke中,当处理reference_wrapper时(如_Pmf_refwrap策略),会通过_Arg1.get()获取原始对象的左值引用,最终传递给被调函数的仍是左值。
这样使得需要接收左值引用为参数的函数也能被支持,因为右值唯独不能赋给非const的左值引用。若用户需要传递引用(而非拷贝,注意引用不能被拷贝),必须显式使用std::ref包装参数(如std::thread t(f, std::ref(x)))。此时元组中存储的是reference_wrapper对象,std::move传递的是包装器的右值,但包装器内部仍指向原对象的左值引用,既满足线程安全(用户保证原对象生命周期),又支持引用语义。
std::thread在内部会将参数打包为元组,并通过std::move以右值形式传递给线程函数,核心目的是确保参数所有权完全转移到新线程,避免线程安全问题和资源泄漏。这是因为线程参数需要独立于创建线程的上下文,新线程的执行时机不确定(可能晚于创建线程的上下文销毁),因此线程函数的参数必须独立存储(不能依赖创建线程中的变量生命周期)。std::thread会先拷贝参数到内部元组(脱离原上下文),再通过右值传递给线程函数,确保参数的所有权完全属于新线程,与原上下文无关。若参数是可移动对象(如std::vector),移动传递避免了元组到线程函数的二次拷贝。