当前位置: 首页 > news >正文

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)时,通过以下逻辑选择调用策略:

  1. 若第一个参数(_Ty1)是成员函数所属类(A)的实例或派生类实例 → 使用_Invoker_pmf_object,调用语法为 (obj.*mem_func)(args...)
  2. 若第一个参数是reference_wrapper<A>(引用包装器) → 使用_Invoker_pmf_refwrap,调用语法为 (ref.get().*mem_func)(args...)
  3. 否则(通常是对象指针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)时,逻辑与成员函数类似:

  1. 若第一个参数是类A的实例 → 使用_Invoker_pmd_object,访问语法为 obj.*mem_ptr
  2. 若第一个参数是reference_wrapper<A> → 使用_Invoker_pmd_refwrap,访问语法为 ref.get().*mem_ptr
  3. 否则(通常是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>:移除Tconst/volatile和引用(&/&&),得到原始类型。
  • is_member_function_pointer_v<T>:判断T是否为成员函数指针。
  • is_member_object_pointer_v<T>:判断T是否为成员变量指针。
  • is_same_v<T, U>:判断TU是否为同一类型。
  • is_base_of_v<T, U>:判断T是否为U的基类(用于派生类实例判断)。
  • _Is_specialization_v<T, Template>:判断T是否为模板Template的特化(如reference_wrapper<int>reference_wrapper的特化)。

总结

_Invoker1std::invoke实现的“大脑”,它通过以下步骤工作:

  1. 对可调用对象(_Callable)进行类型清理(_Removed_cvref)。
  2. 判断_Callable是成员函数指针、成员变量指针,还是普通可调用对象(通过_Is_pmf_Is_pmd)。
  3. 根据第一个参数(_Ty1)的类型(实例、引用包装器、指针),选择对应的调用策略(继承不同的_Invoker_*类)。
  4. 最终由选中的_Invoker_*类实现具体的调用语法(如obj.*mem_funcfunc(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),移动传递避免了元组到线程函数的二次拷贝。


文章转载自:

http://sxZYFRGd.rqnhf.cn
http://LqvNhPhL.rqnhf.cn
http://qvdQlOjv.rqnhf.cn
http://P4Qdopsd.rqnhf.cn
http://rGc2N6x7.rqnhf.cn
http://a1GdrK6A.rqnhf.cn
http://idTnjtrT.rqnhf.cn
http://xjmlmmxg.rqnhf.cn
http://tcUyOxTq.rqnhf.cn
http://ySiM4p26.rqnhf.cn
http://Iw4PeWe9.rqnhf.cn
http://IkYn28oD.rqnhf.cn
http://apQAXEtc.rqnhf.cn
http://Cl9UFtyJ.rqnhf.cn
http://JwYlIsdE.rqnhf.cn
http://r1ZtMbjq.rqnhf.cn
http://33zbpFIU.rqnhf.cn
http://AKYWm51s.rqnhf.cn
http://sCZHzZ0m.rqnhf.cn
http://7agOO1J8.rqnhf.cn
http://PPb1GBmk.rqnhf.cn
http://6aEZab80.rqnhf.cn
http://kbBs93O6.rqnhf.cn
http://WcA4CFty.rqnhf.cn
http://e962jCxC.rqnhf.cn
http://xoVs2Xlf.rqnhf.cn
http://fd8Opv1U.rqnhf.cn
http://7r1QhriG.rqnhf.cn
http://Fq9DgXmB.rqnhf.cn
http://4paNsuWy.rqnhf.cn
http://www.dtcms.com/a/379901.html

相关文章:

  • 判断子序列
  • 鸿蒙数据安全实战:从 AES 到 RSA 的加密解密全流程解析
  • Python与MiniKanren:逻辑编程的艺术与科学
  • DeviceNet 转 EtherCAT:发那科焊接机器人与倍福 CX5140 在汽车焊装线的高速数据同步通讯配置案例
  • J002 Vue+SpringBoot电影推荐可视化系统|双协同过滤推荐算法评论情感分析spark数据分析|配套文档1.34万字
  • 连续hash函数
  • 七彩喜智慧养老:用科技温暖晚年,让关爱永不掉线
  • C++微基础蓝桥杯之旅9.9-9.12
  • 一款好看的jQuery前端框架-HisUI
  • Go语言io.Copy深度解析:高效数据复制的终极指南
  • k8s-init容器学习
  • 【算法磨剑:用 C++ 思考的艺术・Dijkstra 实战】弱化版 vs 标准版模板,洛谷 P3371/P4779 双题精讲
  • Java大厂面试实录:产业互联网大数据与AI服务场景下的微服务与智能搜索(含详细解读)
  • 苍穹外卖项目笔记day08
  • 智能逗猫球方案MCU控制方案浅析-智能宠物玩具,宠物解闷神器
  • Unity键盘控制角色运动
  • 大数据毕业设计-基于Spark的全国高速公路实时路况融合与拥堵预测系统(高分计算机毕业设计选题·定制开发·真正大数据)
  • zmq源码分析之session
  • Xcode 上传 ipa 全流程详解 App Store 上架流程、uni-app 生成 ipa 文件上传与审核指南
  • Java 泛型详解:从基础到高级应用
  • 第6.2节 Android Agent开发<二>
  • ubuntu挂载新硬盘的方法
  • Kubernetes Ingress:使用 Apache APISIX 进行外部流量路由
  • 初学者如何选择适合的云平台进行AIGC训练?
  • Docker存储卷(Volume)完全指南:从入门到精通
  • STM32-FreeRTOS操作系统-二值信号量与计数信号量
  • 蒸面器/蒸脸仪方案开发,蒸面器/蒸脸仪MCU控制方案分析
  • 容器技术崛起:从PaaS到Docker的变革探问
  • 如何定位Mysql慢查询和短而频的查询
  • 机器学习的基本流程:从数据到模型