我特别害怕阅读C++的教程,如果是一个新手,你去搜索一些讲解教程,会看到各种你还没有见到学到的专有名词或者一些你不咋用到的标准库函数,然后作者经常会通过这些你都不怎么熟悉的名词去解释一个更难理解的功能。我想了想或许这也是我无法去学习人文社科类的原因,也就是经典的概念堆砌。
我还是一名C++新手,我学习C++一般遵循一个心智模型:全是围绕内存展开,比如数据存哪了,编译器如何解释某一块数据。就这么简单,才能让我理解原理是什么。
今天要学习的是类型擦除。学习入口就是std::function的源码。
类型擦除其实有好几种,虚函数机制其实就是一种类型擦除,通过基类指针去调用派生类的行为代码。今天学习的std::function其实也没有那么唬人,本质上还是:
可以看一看
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 class _Function_base { public : static const size_t _M_max_size = sizeof (_Nocopy_types); static const size_t _M_max_align = __alignof__(_Nocopy_types); template <typename _Functor> class _Base_manager { protected : static const bool __stored_locally = (__is_location_invariant<_Functor>::value && sizeof (_Functor) <= _M_max_size && __alignof__(_Functor) <= _M_max_align && (_M_max_align % __alignof__(_Functor) == 0 )); using _Local_storage = integral_constant<bool , __stored_locally>; static _Functor* _M_get_pointer(const _Any_data& __source) noexcept { if _GLIBCXX17_CONSTEXPR (__stored_locally) { const _Functor& __f = __source._M_access<_Functor>(); return const_cast <_Functor*>(std::__addressof(__f)); } else return __source._M_access<_Functor*>(); } private : template <typename _Fn> static void _M_create(_Any_data& __dest, _Fn&& __f, true_type) { ::new (__dest._M_access()) _Functor(std::forward<_Fn>(__f)); } template <typename _Fn> static void _M_create(_Any_data& __dest, _Fn&& __f, false_type) { __dest._M_access<_Functor*>() = new _Functor(std::forward<_Fn>(__f)); } static void _M_destroy(_Any_data& __victim, true_type) { __victim._M_access<_Functor>().~_Functor(); } static void _M_destroy(_Any_data& __victim, false_type) { delete __victim._M_access<_Functor*>(); } public : static bool _M_manager(_Any_data& __dest, const _Any_data& __source, _Manager_operation __op) { switch (__op) { case __get_type_info: #if __cpp_rtti __dest._M_access<const type_info*>() = &typeid (_Functor); #else __dest._M_access<const type_info*>() = nullptr ; #endif break ; case __get_functor_ptr: __dest._M_access<_Functor*>() = _M_get_pointer(__source); break ; case __clone_functor: _M_init_functor(__dest, *const_cast <const _Functor*>(_M_get_pointer(__source))); break ; case __destroy_functor: _M_destroy(__dest, _Local_storage()); break ; } return false ; } template <typename _Fn> static void _M_init_functor(_Any_data& __functor, _Fn&& __f) noexcept (__and_<_Local_storage, is_nothrow_constructible<_Functor, _Fn>>::value) { _M_create(__functor, std::forward<_Fn>(__f), _Local_storage()); } template <typename _Signature> static bool _M_not_empty_function(const function<_Signature>& __f) noexcept { return static_cast <bool >(__f); } template <typename _Tp> static bool _M_not_empty_function(_Tp* __fp) noexcept { return __fp != nullptr ; } template <typename _Class, typename _Tp> static bool _M_not_empty_function(_Tp _Class::* __mp) noexcept { return __mp != nullptr ; } template <typename _Tp> static bool _M_not_empty_function(const _Tp&) noexcept { return true ; } }; _Function_base() = default ; ~_Function_base() { if (_M_manager) _M_manager(_M_functor, _M_functor, __destroy_functor); } bool _M_empty() const { return !_M_manager; } using _Manager_type = bool (*)(_Any_data&, const _Any_data&, _Manager_operation); _Any_data _M_functor{}; _Manager_type _M_manager{}; };
乍一眼看去好可怕啊,全是模板实现。但是不看这些的话,把所有模板细节删掉,它本质上只解决三个问题:
函数对象存在哪?(栈 / 堆)
怎么拿到函数对象指针?
怎么统一管理生命周期?
删繁就简,这个基础对象的内部大致就是:
1 2 3 4 5 6 7 8 9 10 std::function │ ├── _Any_data _M_functor │ │ ├── _Manager_type _M_manager │ │ └── _Invoker_type _M_invoker
看源码首先看class成员对象,而不是成员函数,成员函数大概率都是如何操作这些成员对象罢了。但是大部分代码喜欢把对象写到最后面,比较唬人。
慢慢来,可以先看一下Any_data是什么:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 union [[gnu::may_alias]] _Any_data { void * _M_access() noexcept { return &_M_pod_data[0 ]; } const void * _M_access() const noexcept { return &_M_pod_data[0 ]; } template <typename _Tp> _Tp& _M_access() noexcept { return *static_cast <_Tp*>(_M_access()); } template <typename _Tp> const _Tp& _M_access() const noexcept { return *static_cast <const _Tp*>(_M_access()); } _Nocopy_types _M_unused; char _M_pod_data[sizeof (_Nocopy_types)]; };
其他都不用看,看到这个
1 char _M_pod_data[sizeof(_Nocopy_types)];
就行。
这段 _Any_data 的核心作用其实很简单:给 std::function 提供一块可以存储任意类型对象的原始内存,并提供统一的类型转换入口。因为 std::function 在编译时并不知道用户会传入什么函数对象(lambda、函数指针、bind 结果、仿函数类等),所以它不能直接写成 Functor member;。库实现者的解决办法就是准备一块无类型的内存缓冲区,等真正存储对象的时候再把这块内存解释成具体类型。
在这段代码中,这块缓冲区就是 _M_pod_data:
1 char _M_pod_data[sizeof(_Nocopy_types)];
它本质只是一个原始字节数组。它不代表任何类型,只是一块固定大小的内存。std::function 会把小的函数对象直接构造在这块内存里(这就是 small buffer optimization),如果对象太大,就在堆上 new 一个对象,而这块内存里只存一个指针。
但如果只有一个 char[],使用起来会很麻烦,因为每次都要做强制类型转换。所以实现者写了一组 _M_access() 函数来做统一入口。最基础的版本:
1 void* _M_access() noexcept { return &_M_pod_data[0]; }
这只是返回这块内存的起始地址,也就是一个 void*。接下来模板版本:
1 2 3 4 5 template<typename _Tp> _Tp& _M_access() noexcept { return *static_cast<_Tp*>(_M_access()); }
这里做的事情其实非常直接:先拿到 buffer 的地址,然后把它强制转换成 _Tp*,最后解引用。也就是说,这个函数允许代码在任何时候把这块原始内存“当作某种类型来看待”。如果之前在 buffer 中用 placement new 构造了一个 Lambda 对象,那么调用 _M_access<Lambda>() 就能得到这个对象的引用;如果 buffer 中存的是一个 Functor* 指针,那么调用 _M_access<Functor*>() 就会得到那个指针。
因此 _Any_data 并不真正“管理”对象,它只是提供一块可重解释的原始内存。真正知道里面存的是什么类型的,是外层的 _Base_manager<Functor>。manager 在创建对象时会决定是直接在 buffer 中构造对象,还是在堆上分配;在销毁时也会根据同样的策略决定是调用析构函数还是 delete 指针。换句话说,_Any_data 只是存储容器,而生命周期管理逻辑完全在 manager 里。
代码开头还有一个细节:
1 union [[gnu::may_alias]] _Any_data
这里使用 may_alias 是为了绕过 C++ 的严格别名规则。因为这块内存会被反复当作不同类型使用(例如先当 Lambda,再当 Lambda*),而严格别名规则通常不允许这种做法。这个属性告诉编译器:这个类型可能与其他类型发生别名关系,因此不要基于严格别名规则进行优化,否则可能产生未定义行为。
至于 union 中的 _Nocopy_types _M_unused,它的作用主要是控制 buffer 的大小和对齐。union 的大小取决于最大成员,所以通过放入 _Nocopy_types,库作者可以保证 _Any_data 至少有足够的空间和对齐要求,用来存储常见的小型函数对象或指针。
其实Any_data很简单,就是提供一块带类型转换接口的原始内存,使 std::function 能够在不知道具体类型的情况下存储和访问任意函数对象,从而实现类型擦除。
然后我们来看看_Manager_type
在class _Function_base里,最后一点:
1 2 3 4 5 using _Manager_type = bool (*)(_Any_data&, const _Any_data&, _Manager_operation); _Any_data _M_functor{}; _Manager_type _M_manager{};
可以看到_Manager_type其实是一个bool (*)(_Any_data&, const _Any_data&, _Manager_operation),也就是一个函数指针类型。
换句话说,_M_manager 不是对象,也不是类,而是一个指向管理函数的指针。这个管理函数接受三个参数:一个目标 _Any_data、一个源 _Any_data,以及一个 _Manager_operation 枚举,用来说明当前要执行什么操作。
如果只看接口,这个函数指针有点抽象,但理解起来其实可以把它想象成一个统一的管理入口。std::function 在运行时可能需要对内部保存的函数对象执行几种不同的操作,比如:
1.查询它的类型(typeid)
2.获取函数对象的指针
3.复制这个函数对象
4.销毁这个函数对象
这些操作都由 _Manager_operation 指定,而 _M_manager 根据这个操作码去执行对应逻辑。也就是说,_M_manager 实际上扮演了一个调度函数的角色。
接下来是_M_invoker;
在 function<_Res(_ArgTypes...)> 这个模板类中,你会看到类似这样的成员:
1 _Invoker_type _M_invoker;
它还是一个指针,上面是:
1 2 3 private : using _Invoker_type = _Res (*)(const _Any_data&, _ArgTypes&&...); _Invoker_type _M_invoker = nullptr ;
这表示 _M_invoker 指向一个函数指针,这个函数指针指向的函数必须满足这样一个签名:接受一个 _Any_data 和调用参数 _ArgTypes...,返回 _Res。换句话说,它描述的是一种统一的调用接口:无论真实函数对象是什么类型,只要能被包装进 std::function,最终都会被转换成一个符合这个签名的调用函数。
真正实现这个调用逻辑的代码并不在 function 类里,而是在 _Function_handler 模板中。源码里可以看到:
1 2 3 4 5 6 7 8 9 10 11 12 template <typename _Res, typename _Functor, typename ... _ArgTypes>class _Function_handler <_Res(_ArgTypes...), _Functor>: public _Function_base::_Base_manager<_Functor> { public : static _Res _M_invoke(const _Any_data& __functor, _ArgTypes&&... __args) { return std::__invoke_r<_Res>(*_Base::_M_get_pointer(__functor), std::forward<_ArgTypes>(__args)...); } };
这里的 _M_invoke 就是 _M_invoker 最终会指向的函数。这个函数的任务非常直接:它首先通过 _Base::_M_get_pointer(__functor) 从 _Any_data 中取出真实的函数对象指针,然后调用 std::__invoke_r 来执行这个函数对象,并把参数转发进去。
注意这里调用的是 std::__invoke_r 而不是直接写 f(args...)。__invoke_r 是标准库内部实现 std::invoke 的版本,它能够统一处理各种可调用对象:普通函数、成员函数指针、lambda、仿函数对象等。因此 _M_invoke 并不关心 _Functor 的具体形式,只要它是可调用的,__invoke_r 都能正确地执行它。
当 std::function 构造时,这个 _M_invoke 会被绑定到 _M_invoker 上。构造函数里可以看到这一句关键代码:
1 _M_invoker = &_My_handler::_M_invoke;
这里 _My_handler 是 _Function_handler<_Res(_ArgTypes...), Functor> 的别名。也就是说,每一种 Functor 类型都会生成一个专门的 _Function_handler 实例,而 _M_invoker 会指向这个实例里的 _M_invoke 函数。
因此在运行时,一个 std::function 对象实际上保存了三个重要成员:_M_functor、_M_manager 和 _M_invoker。_M_functor 是 _Any_data,用于存储函数对象本身;_M_manager 是管理函数,用于复制、销毁和查询对象;而 _M_invoker 则是调用入口。
当用户执行函数调用时,真正发生的事情可以在 operator() 里看到:
1 2 3 4 5 6 _Res operator () (_ArgTypes... __args) const { if (_M_empty()) __throw_bad_function_call(); return _M_invoker(_M_functor, std::forward<_ArgTypes>(__args)...); }
这里没有直接调用 _M_functor 中的对象,而是调用 _M_invoker。_M_invoker 再通过 _M_invoke 从 _Any_data 中提取出真正的 functor,并最终执行它。
因此可以把 std::function 的内部结构理解为三部分:一块 _Any_data 存储对象,一个 _M_manager 管理对象生命周期,以及一个 _M_invoker 负责执行函数调用。下面来结合着源码,看一看我们平常使用的时候,调用链会是什么呢?
这里还是粘上刚刚说到的三个重点对象:
1 2 3 _Any_data _M_functor _Manager_type _M_manager _Invoker_type _M_invoker
强调一下,这三者分别负责三件事: _M_functor 存储函数对象,_M_manager 管理对象生命周期,_M_invoker 完成函数调用。
写一个lambda传入function的话:
1 2 std::function<int(int)> f = [](int x){ return x + 1; }; int y = f(3);
从构造对象开始,模板构造函数在源码中是:
1 2 3 4 5 6 7 8 9 10 11 12 13 template<typename _Functor> function(_Functor&& __f) { using _My_handler = _Handler<_Functor>; if (_My_handler::_M_not_empty_function(__f)) { _My_handler::_M_init_functor(_M_functor, std::forward<_Functor>(__f)); _M_invoker = &_My_handler::_M_invoke; _M_manager = &_My_handler::_M_manager; } }
这里 _Handler<_Functor> 实际上是:
1 _Function_handler<_Res(_ArgTypes...), decay_t<_Functor>>
也就是说,对于每一种 functor 类型,都会实例化一个 _Function_handler。
简单说一下构造函数完成的三件关键工作:
第一件事是调用 _M_init_functor 把函数对象存入 _Any_data。 这一逻辑来自 _Base_manager:
1 _M_create(__functor, std::forward<_Fn>(__f), _Local_storage());
如果 functor 很小,就使用 placement new 直接构造在 _Any_data 里;如果对象较大,则在堆上 new 一个对象,并把指针存进 _Any_data。因此 _Any_data 最终保存的要么是 functor 本身,要么是一个指针。
第二件事是初始化 _M_invoker:
1 _M_invoker = &_My_handler::_M_invoke;
这里 _M_invoke 定义在 _Function_handler 中:
1 2 3 4 5 6 7 static _Res _M_invoke(const _Any_data& __functor, _ArgTypes&&... __args) { return std::__invoke_r<_Res>( *_Base::_M_get_pointer(__functor), std::forward<_ArgTypes>(__args)...); }
刚刚讲过,这个函数就是最终执行 functor 的地方。它先从 _Any_data 中取出真正的 functor,然后调用 std::__invoke_r 执行它。
第三件事是初始化 _M_manager:
1 _M_manager = &_My_handler::_M_manager;
这个函数负责对象复制、销毁等生命周期操作。
当构造完成后,一个 std::function 对象的运行时结构可以理解为:
1 2 3 4 std::function ├─ _M_functor -> 存储 functor 或 functor* ├─ _M_manager -> _Function_handler<Functor>::_M_manager └─ _M_invoker -> _Function_handler<Functor>::_M_invoke
然后来看看真正的调用阶段会发生什么:之前写了y=f(3)。
当然会进入我们的()运算符重载函数了~std::function::operator()
刚刚也贴过,这里再看一下:
1 2 3 4 5 6 _Res operator()(_ArgTypes... __args) const { if (_M_empty()) __throw_bad_function_call(); return _M_invoker(_M_functor, std::forward<_ArgTypes>(__args)...); }
这里的逻辑很简单。首先检查当前 std::function 是否为空,如果为空就抛出 bad_function_call。如果对象存在,就调用 _M_invoker。
这个例子是lambda对象,上面只是串了一遍逻辑,这次深入看看传入的是什么东西,也就是我们的函数对象。那就复盘一下lambda:
对于lambda,编译器会生成一个类似这样的类型(名字是编译器生成的):
1 2 3 4 5 6 class __lambda_123 { public: int operator()(int x) const { return x + 1; } };
表达式
1 [](int x){ return x + 1; }
实际上创建的是:
因此构造 std::function 时真正调用的是:
1 std::function<int(int)> f(__lambda_123{});
也就是说 _Functor 模板参数就是 __lambda_123。
接下来进入 std::function 构造函数。源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 template <typename _Functor, typename _Constraints = _Requires<_Callable<_Functor>>> function (_Functor&& __f): _Function_base() { using _My_handler = _Handler<_Functor>; if (_My_handler::_M_not_empty_function(__f)) { _My_handler::_M_init_functor(_M_functor, std::forward<_Functor>(__f)); _M_invoker = &_My_handler::_M_invoke; _M_manager = &_My_handler::_M_manager; } }
此时 _Functor = __lambda_123。
而 _Handler<_Functor> 定义为:
1 2 using _Handler = _Function_handler<_Res(_ArgTypes...), decay_t<_Functor>>;
因此 _My_handler 实际类型是:
1 _Function_handler<int(int), __lambda_123>
这一步非常关键,因为后面所有函数指针都会指向这个类型的静态函数。
接下来第一件事是把 lambda **存进 _Any_data**。
构造函数调用:
1 _M_init_functor(_M_functor, std::forward<_Functor>(__f));
这个函数在 _Base_manager 中:
1 2 3 4 5 static void _M_init_functor(_Any_data& __functor, _Fn&& __f) { _M_create(__functor, std::forward<_Fn>(__f), _Local_storage()); }
关键在 _Local_storage。它来自下面的判断:
1 2 3 4 5 6 static const bool __stored_locally = ( __is_location_invariant<_Functor>::value && sizeof(_Functor) <= _M_max_size && alignof(_Functor) <= _M_max_align );
_M_max_size 来自:
1 static const size_t _M_max_size = sizeof(_Nocopy_types);
_Nocopy_types 是:
1 2 3 4 5 6 7 union _Nocopy_types { void* _M_object; const void* _M_const_object; void (*_M_function_pointer)(); void (_Undefined_class::*_M_member_pointer)(); };
在 64 位系统上,这个 union 大约 16 bytes。
而一个无捕获 lambda 的大小通常是:
1 sizeof(__lambda_123) = 1
因此:
于是 _M_create 选择的是这个重载:
1 2 3 4 5 6 template<typename _Fn> static void _M_create(_Any_data& __dest, _Fn&& __f, true_type) { ::new (__dest._M_access()) _Functor(std::forward<_Fn>(__f)); }
这里使用的是 placement new。也就是说 lambda 直接构造在 _Any_data 的 buffer 中。
如果 lambda 很大,例如捕获很多变量,那么会走另一条路径:
1 2 3 4 5 6 template<typename _Fn> static void _M_create(_Any_data& __dest, _Fn&& __f, false_type) { __dest._M_access<_Functor*>() = new _Functor(...); }
这就是 Small Object Optimization (SBO)。
接下来构造函数设置 _M_invoker:
1 _M_invoker = &_My_handler::_M_invoke;
而 _M_invoke 的定义在 _Function_handler:
1 2 3 4 5 6 7 static _Res _M_invoke(const _Any_data& __functor, _ArgTypes&&... __args) { return std::__invoke_r<_Res>( *_Base::_M_get_pointer(__functor), std::forward<_ArgTypes>(__args)...); }
注意 _Base 是:
1 using _Base = _Function_base::_Base_manager<_Functor>;
因此 _M_get_pointer 实际是:
1 _Base_manager<__lambda_123>::_M_get_pointer
同时 _M_manager 也被设置:
1 _M_manager = &_My_handler::_M_manager;
现在进入调用阶段:
这会调用:
1 2 3 4 5 6 _Res operator()(_ArgTypes... __args) const { if (_M_empty()) __throw_bad_function_call(); return _M_invoker(_M_functor, std::forward<_ArgTypes>(__args)...); }
因此执行:
1 _M_invoker(_M_functor, 3)
也就是:
1 _Function_handler<lambda>::_M_invoke
进入 _M_invoke:
1 2 3 4 std::__invoke_r<_Res>( *_Base::_M_get_pointer(__functor), args... )
这里首先调用 _M_get_pointer:
1 2 3 4 5 6 7 8 9 10 11 static _Functor* _M_get_pointer(const _Any_data& __source) { if (__stored_locally) { const _Functor& __f = __source._M_access<_Functor>(); return &__f; } else return __source._M_access<_Functor*>(); }
由于 lambda 存在 _Any_data 内部,因此执行:
1 pointer = &(__lambda_123)
最后执行:
1 std::__invoke_r(pointer, args...)
而 std::__invoke_r 内部最终会展开为:
也就是:
1 __lambda_123::operator()(3)
返回:
因此完整调用链让AI画一下图:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 lambda 表达式 │ ▼ 生成 __lambda_123 对象 │ ▼ function 构造函数 │ ├─ placement new → _Any_data ├─ _M_invoker = &_Function_handler::_M_invoke └─ _M_manager = &_Function_handler::_M_manager │ ▼ 用户调用 f(3) │ ▼ function::operator() │ ▼ _M_invoker(_M_functor,3) │ ▼ _Function_handler::_M_invoke │ ▼ _Base_manager::_M_get_pointer │ ▼ std::__invoke_r │ ▼ lambda.operator()(3)
所以当 lambda 被传入 std::function 时,它并没有被转换成函数指针,也没有发生继承多态,而是原样存储为对象。std::function 内部通过 _Any_data 保存对象,通过 _M_invoker 保存调用函数,通过 _M_manager 保存生命周期管理函数。调用时 _M_invoker 从 _Any_data 取出对象,再通过 std::invoke 执行它。这就是 std::function 实现类型擦除和多态调用的完整机制。
关于std::__invoke 我注意到了一个新的标准库函数:std::__invoke,这个真正起到调用作用的函数又是哪来的?
std::_invoke是C++17才引入标准库的。打开看看,发现很短:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 #ifndef _GLIBCXX_INVOKE_H #define _GLIBCXX_INVOKE_H 1 #pragma GCC system_header #if __cplusplus < 201103L # include <bits/c++0x_warning.h> #else #include <type_traits> #include <bits/move.h> namespace std _GLIBCXX_VISIBILITY(default ){ _GLIBCXX_BEGIN_NAMESPACE_VERSION template <typename _Tp, typename _Up = typename __inv_unwrap<_Tp>::type> constexpr _Up&& __invfwd(typename remove_reference<_Tp>::type& __t ) noexcept { return static_cast <_Up&&>(__t ); } template <typename _Res, typename _Fn, typename ... _Args> constexpr _Res __invoke_impl(__invoke_other, _Fn&& __f, _Args&&... __args) { return std::forward<_Fn>(__f)(std::forward<_Args>(__args)...); } template <typename _Res, typename _MemFun, typename _Tp, typename ... _Args> constexpr _Res __invoke_impl(__invoke_memfun_ref, _MemFun&& __f, _Tp&& __t , _Args&&... __args) { return (__invfwd<_Tp>(__t ).*__f)(std::forward<_Args>(__args)...); } template <typename _Res, typename _MemFun, typename _Tp, typename ... _Args> constexpr _Res __invoke_impl(__invoke_memfun_deref, _MemFun&& __f, _Tp&& __t , _Args&&... __args) { return ((*std::forward<_Tp>(__t )).*__f)(std::forward<_Args>(__args)...); } template <typename _Res, typename _MemPtr, typename _Tp> constexpr _Res __invoke_impl(__invoke_memobj_ref, _MemPtr&& __f, _Tp&& __t ) { return __invfwd<_Tp>(__t ).*__f; } template <typename _Res, typename _MemPtr, typename _Tp> constexpr _Res __invoke_impl(__invoke_memobj_deref, _MemPtr&& __f, _Tp&& __t ) { return (*std::forward<_Tp>(__t )).*__f; } template <typename _Callable, typename ... _Args> constexpr typename __invoke_result<_Callable, _Args...>::type __invoke(_Callable&& __fn, _Args&&... __args) noexcept (__is_nothrow_invocable<_Callable, _Args...>::value) { using __result = __invoke_result<_Callable, _Args...>; using __type = typename __result::type; using __tag = typename __result::__invoke_type; return std::__invoke_impl<__type>(__tag{}, std::forward<_Callable>(__fn), std::forward<_Args>(__args)...); } #if __cplusplus >= 201703L template <typename _Res, typename _Callable, typename ... _Args> constexpr enable_if_t <is_invocable_r_v<_Res, _Callable, _Args...>, _Res> __invoke_r(_Callable&& __fn, _Args&&... __args) noexcept (is_nothrow_invocable_r_v<_Res, _Callable, _Args...>) { using __result = __invoke_result<_Callable, _Args...>; using __type = typename __result::type; using __tag = typename __result::__invoke_type; if constexpr (is_void_v<_Res>) std::__invoke_impl<__type>(__tag{}, std::forward<_Callable>(__fn), std::forward<_Args>(__args)...) ; else return std::__invoke_impl<__type>(__tag{}, std::forward<_Callable>(__fn), std::forward<_Args>(__args)...); } #else template <typename _Res, typename _Callable, typename ... _Args> constexpr __enable_if_t <!is_void<_Res>::value, _Res> __invoke_r(_Callable&& __fn, _Args&&... __args) { using __result = __invoke_result<_Callable, _Args...>; using __type = typename __result::type; #if __has_builtin(__reference_converts_from_temporary) static_assert (!__reference_converts_from_temporary(_Res, __type), "INVOKE<R> must not create a dangling reference" ); #endif using __tag = typename __result::__invoke_type; return std::__invoke_impl<__type>(__tag{}, std::forward<_Callable>(__fn), std::forward<_Args>(__args)...); } template <typename _Res, typename _Callable, typename ... _Args> _GLIBCXX14_CONSTEXPR __enable_if_t <is_void<_Res>::value, _Res> __invoke_r(_Callable&& __fn, _Args&&... __args) { using __result = __invoke_result<_Callable, _Args...>; using __type = typename __result::type; using __tag = typename __result::__invoke_type; std::__invoke_impl<__type>(__tag{}, std::forward<_Callable>(__fn), std::forward<_Args>(__args)...); } #endif _GLIBCXX_END_NAMESPACE_VERSION } #endif #endif
看看入口函数:
1 2 3 4 5 6 7 8 9 10 11 template <typename _Callable, typename ... _Args>constexpr typename __invoke_result<_Callable, _Args...>::type__invoke(_Callable&& __fn, _Args&&... __args) noexcept (__is_nothrow_invocable<_Callable, _Args...>::value){ using __result = __invoke_result<_Callable, _Args...>; using __type = typename __result::type; using __tag = typename __result::__invoke_type; return std::__invoke_impl<__type>(__tag{}, std::forward<_Callable>(__fn), std::forward<_Args>(__args)...); }
如果只看表面,这段代码似乎只是把调用转发给 __invoke_impl。但这里真正重要的是两行 using 声明:
1 2 using __result = __invoke_result<_Callable, _Args...>; using __tag = typename __result::__invoke_type;
__invoke_result 是一个模板类型推导工具,它不仅推导出调用的返回类型,还会生成一个 tag 类型。这个 tag 用来描述当前调用属于哪一种 INVOKE 规则。标准定义的 INVOKE 语义包含五种情况,因此 __tag 的类型可能是:
1 2 3 4 5 __invoke_other __invoke_memfun_ref __invoke_memfun_deref __invoke_memobj_ref __invoke_memobj_deref
看最简单的一种情况,也就是普通函数或 lambda 调用。源码中的实现是:
1 2 3 4 template<typename _Res, typename _Fn, typename... _Args> constexpr _Res __invoke_impl(__invoke_other, _Fn&& __f, _Args&&... __args) { return std::forward<_Fn>(__f)(std::forward<_Args>(__args)...); }
如果 callable 是普通函数对象,那么 tag 类型就是 __invoke_other,最终执行的表达式就是:
例如:
1 2 auto f = [](int x){ return x+1; }; std::invoke(f,3);
在编译器展开之后,本质上就是:
第二种情况是成员函数指针,并且对象是引用类型。这时 __invoke_impl 的实现是:
1 2 3 4 5 template<typename _Res, typename _MemFun, typename _Tp, typename... _Args> constexpr _Res __invoke_impl(__invoke_memfun_ref, _MemFun&& __f, _Tp&& __t, _Args&&... __args) { return (__invfwd<_Tp>(__t).*__f)(std::forward<_Args>(__args)...); }
这里生成的表达式是:
例如下面的代码:
1 2 3 4 struct A { int add(int); }; A a; std::invoke(&A::add, a, 3);
经过 invoke 展开之后会变成:
也就是成员函数调用 a.add(3)。
第三种情况与第二种类似,只不过对象是指针而不是引用。对应实现是:
1 2 3 4 5 6 7 template<typename _Res, typename _MemFun, typename _Tp, typename... _Args> constexpr _Res __invoke_impl(__invoke_memfun_deref, _MemFun&& __f, _Tp&& __t, _Args&&... __args) { return ((*std::forward<_Tp>(__t)).*__f)(std::forward<_Args>(__args)...); }
这里生成的表达式是:
例如:
1 2 A* p = &a; std::invoke(&A::add, p, 3);
编译期展开之后变成:
也就是 p->add(3)。
除了成员函数指针之外,std::invoke 还支持成员变量指针。第四种情况是成员变量与对象引用:
1 2 3 4 template<typename _Res, typename _MemPtr, typename _Tp> constexpr _Res __invoke_impl(__invoke_memobj_ref, _MemPtr&& __f, _Tp&& __t) { return __invfwd<_Tp>(__t).*__f; }
这里生成的表达式是:
例如:
1 2 3 4 struct B { int value; }; B b; std::invoke(&B::value, b);
最终表达式就是:
最后一种情况是对象指针访问成员变量:
1 2 3 4 template<typename _Res, typename _MemPtr, typename _Tp> constexpr _Res __invoke_impl(__invoke_memobj_deref, _MemPtr&& __f, _Tp&& __t) { return (*std::forward<_Tp>(__t)).*__f; }
生成表达式:
也就是:
这些不同的 __invoke_impl 重载共同构成了完整的 INVOKE 语义体系。
再细节的不想看了,以后再说。