C++ std::function 实现原理
msvc
预备知识
参数类型,可以分为一元(unary)和二元(binary),这个概念很重要,gcc的实现里也用到。
可以看到msvc里定义了三个_Arg_types:无参数类型;接受一个参数,一元;接受两个参数,二元。
并且_Arg_types没有成员变量,只是定义了对应的类型。
template <class... _Types>
struct _Arg_types {}; // provide argument_type, etc. when sizeof...(_Types) is 1 or 2
template <class _Ty1>
struct _Arg_types<_Ty1> {
_CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef _Ty1 _ARGUMENT_TYPE_NAME;
};
template <class _Ty1, class _Ty2>
struct _Arg_types<_Ty1, _Ty2> {
_CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef _Ty1 _FIRST_ARGUMENT_TYPE_NAME;
_CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef _Ty2 _SECOND_ARGUMENT_TYPE_NAME;
};
一个function应该有什么接口
- 能调用。必须的!
- 能复制。
- 能移动。
- 能删除。
我们看一下msvc实现的接口类,正好符合这四个特点。
这里需要注意关键字__declspec(novtable)
,它用于告诉编译器不要生成vtable,这样就可以减少额外的开销,毕竟function作为一个很基础的功能,当然是能省则省。
template <class _Rx, class... _Types>
class __declspec(novtable) _Func_base { // abstract base for implementation types
public:
virtual _Func_base* _Copy(void*) const = 0;
virtual _Func_base* _Move(void*) noexcept = 0;
virtual _Rx _Do_call(_Types&&...) = 0;
virtual const type_info& _Target_type() const noexcept = 0;
virtual void _Delete_this(bool) noexcept = 0;
#if _HAS_STATIC_RTTI
const void* _Target(const type_info& _Info) const noexcept {
return _Target_type() == _Info ? _Get() : nullptr;
}
#endif // _HAS_STATIC_RTTI
_Func_base() = default;
_Func_base(const _Func_base&) = delete;
_Func_base& operator=(const _Func_base&) = delete;
// dtor non-virtual due to _Delete_this()
private:
virtual const void* _Get() const noexcept = 0;
};
走进源码
内存对象
首先进入function class,它继承自_Get_function_impl::type,public函数里都是构造函数相关,我们着重观察这个_Get_function_impl实现。没有成员变量
template <class _Fty>
class function : public _Get_function_impl<_Fty>::type { // wrapper for callable objects
private:
using _Mybase = typename _Get_function_impl<_Fty>::type;
public:
// 构造函数
};
可以看到_Get_function_impl的主要部分为_Get_function_impl::type,它是_Func_class<_Ret, _Types...>。没有成员变量
template <class _Tx>
struct _Get_function_impl {
static_assert(_Always_false<_Tx>, "std::function only accepts function types as template arguments.");
};
#define _GET_FUNCTION_IMPL(CALL_OPT, X1, X2, X3) \
template <class _Ret, class... _Types> \
struct _Get_function_impl<_Ret CALL_OPT(_Types...)> { /* determine type from argument list */ \
using type = _Func_class<_Ret, _Types...>; \
};
_NON_MEMBER_CALL(_GET_FUNCTION_IMPL, X1, X2, X3)
#undef _GET_FUNCTION_IMPL
_Func_class 模板参数为 返回值 + 可变参,继承自_Arg_types<_Types...>,根据参数个数拿到了对应的类型。
-
private里有一个成员变量_Storage _Mystorage,它是一个联合体,在64位平台上,
_Small_object_num_ptrs = 6 + 16 / sizeof(void*) = 8
,_Space_size = (_Small_object_num_ptrs - 1) * sizeof(void*) = 56
,
所以这个_Mystorage的实际大小为64个byte。union _Storage { // storage for small objects (basic_string is small) max_align_t _Dummy1; // for maximum alignment char _Dummy2[_Space_size]; // to permit aliasing _Ptrt* _Ptrs[_Small_object_num_ptrs]; // _Ptrs[_Small_object_num_ptrs - 1] is reserved };
-
public里为构造函数 + 析构函数 + 重载operator(),符合我们的常规认知,function就是一个根据传参执行的函数。
这里我们主要看三个函数,_Set、_Getimpl、_Tidy,它们都是在对_Mystorage的最后一个_Ptr进行操作- _Set,赋值给_Mystorage的最后一个_Ptr
void _Set(_Ptrt* _Ptr) noexcept { // store pointer to object _Mystorage._Ptrs[_Small_object_num_ptrs - 1] = _Ptr; }
- _Getimpl,返回_Mystorage的最后一个_Ptr
_Ptrt* _Getimpl() const noexcept { // get pointer to object return _Mystorage._Ptrs[_Small_object_num_ptrs - 1]; }
- _Tidy,首先删除对应指针,然后_Mystorage的最后一个_Ptr置空。
void _Tidy() noexcept { if (!_Empty()) { // destroy callable object and maybe delete it _Getimpl()->_Delete_this(!_Local()); _Set(nullptr); } }
- _Set,赋值给_Mystorage的最后一个_Ptr
-
protected里主要为内存分配相关函数,我们重点关注内存分配函数_Reset,可以看到根据_Is_large主要分为两种分配方式,
// 当函数对象的大小超过56字节,或者对齐字节数大于max_align_t,或者是否可以不抛异常移动构造 template <class _Impl> // determine whether _Impl must be dynamically allocated _INLINE_VAR constexpr bool _Is_large = sizeof(_Impl) > _Space_size || alignof(_Impl) > alignof(max_align_t) || !_Impl::_Nothrow_move::value;
- 小内存函数对象,调用 placement new,直接构造在_Mystorage上
- 大内存函数对象,需要动态分配,内存分配到堆上
template <class _Fx> void _Reset(_Fx&& _Val) { // store copy of _Val if (!_Test_callable(_Val)) { // null member pointer/function pointer/std::function return; // already empty } using _Impl = _Func_impl_no_alloc<decay_t<_Fx>, _Ret, _Types...>; if constexpr (_Is_large<_Impl>) { // dynamically allocate _Val _Set(_Global_new<_Impl>(_STD forward<_Fx>(_Val))); } else { // store _Val in-situ _Set(::new (static_cast<void*>(&_Mystorage)) _Impl(_STD forward<_Fx>(_Val))); } }
template <class _Ret, class... _Types>
class _Func_class : public _Arg_types<_Types...> {
public:
using result_type = _Ret;
using _Ptrt = _Func_base<_Ret, _Types...>;
_Func_class() noexcept {
_Set(nullptr);
}
_Ret operator()(_Types... _Args) const {
if (_Empty()) {
_Xbad_function_call();
}
const auto _Impl = _Getimpl();
return _Impl->_Do_call(_STD forward<_Types>(_Args)...);
}
~_Func_class() noexcept {
_Tidy();
}
protected:
template <class _Fx, class _Function>
using _Enable_if_callable_t = enable_if_t<conjunction_v<negation<is_same<_Remove_cvref_t<_Fx>, _Function>>,
_Is_invocable_r<_Ret, decay_t<_Fx>&, _Types...>>,
int>;
bool _Empty() const noexcept {
return !_Getimpl();
}
void _Reset_copy(const _Func_class& _Right) { // copy _Right's stored object
if (!_Right._Empty()) {
_Set(_Right._Getimpl()->_Copy(&_Mystorage));
}
}
void _Reset_move(_Func_class&& _Right) noexcept { // move _Right's stored object
if (!_Right._Empty()) {
if (_Right._Local()) { // move and tidy
_Set(_Right._Getimpl()->_Move(&_Mystorage));
_Right._Tidy();
} else { // steal from _Right
_Set(_Right._Getimpl());
_Right._Set(nullptr);
}
}
}
template <class _Fx>
void _Reset(_Fx&& _Val) { // store copy of _Val
if (!_Test_callable(_Val)) { // null member pointer/function pointer/std::function
return; // already empty
}
using _Impl = _Func_impl_no_alloc<decay_t<_Fx>, _Ret, _Types...>;
if constexpr (_Is_large<_Impl>) {
// dynamically allocate _Val
_Set(_Global_new<_Impl>(_STD forward<_Fx>(_Val)));
} else {
// store _Val in-situ
_Set(::new (static_cast<void*>(&_Mystorage)) _Impl(_STD forward<_Fx>(_Val)));
}
}
void _Tidy() noexcept {
if (!_Empty()) { // destroy callable object and maybe delete it
_Getimpl()->_Delete_this(!_Local());
_Set(nullptr);
}
}
void _Swap(_Func_class& _Right) noexcept { // swap contents with contents of _Right
if (!_Local() && !_Right._Local()) { // just swap pointers
_Ptrt* _Temp = _Getimpl();
_Set(_Right._Getimpl());
_Right._Set(_Temp);
} else { // do three-way move
_Func_class _Temp;
_Temp._Reset_move(_STD move(*this));
_Reset_move(_STD move(_Right));
_Right._Reset_move(_STD move(_Temp));
}
}
private:
bool _Local() const noexcept { // test for locally stored copy of object
return _Getimpl() == static_cast<const void*>(&_Mystorage);
}
union _Storage { // storage for small objects (basic_string is small)
max_align_t _Dummy1; // for maximum alignment
char _Dummy2[_Space_size]; // to permit aliasing
_Ptrt* _Ptrs[_Small_object_num_ptrs]; // _Ptrs[_Small_object_num_ptrs - 1] is reserved
};
_Storage _Mystorage;
enum { _EEN_IMPL = _Small_object_num_ptrs - 1 }; // helper for expression evaluator
_Ptrt* _Getimpl() const noexcept { // get pointer to object
return _Mystorage._Ptrs[_Small_object_num_ptrs - 1];
}
void _Set(_Ptrt* _Ptr) noexcept { // store pointer to object
_Mystorage._Ptrs[_Small_object_num_ptrs - 1] = _Ptr;
}
};
如何调用
通过上一节我们可以了解到std::function核心的内存就是_Storage _Mystorage,可以通过_Mystorage的最后一个_Ptr得到函数调用的指针,那有了函数指针,怎么调用呢?
在_Func_class::_Reset中我们首先分配了对象_Func_impl_no_alloc,然后把其指针赋值给_Mystorage的最后一个_Ptr,这个_Func_impl_no_alloc就是函数调用的具体实现,
它继承自_Func_base,主要实现了调用、移动、复制、删除接口:
template <class _Callable, class _Rx, class... _Types>
class _Func_impl_no_alloc final : public _Func_base<_Rx, _Types...> {
// derived class for specific implementation types that don't use allocators
public:
using _Mybase = _Func_base<_Rx, _Types...>;
using _Nothrow_move = is_nothrow_move_constructible<_Callable>;
template <class _Other, enable_if_t<!is_same_v<_Func_impl_no_alloc, decay_t<_Other>>, int> = 0>
explicit _Func_impl_no_alloc(_Other&& _Val) : _Callee(_STD forward<_Other>(_Val)) {}
// dtor non-virtual due to _Delete_this()
private:
_Mybase* _Copy(void* _Where) const override {
if constexpr (_Is_large<_Func_impl_no_alloc>) {
return _Global_new<_Func_impl_no_alloc>(_Callee);
} else {
return ::new (_Where) _Func_impl_no_alloc(_Callee);
}
}
_Mybase* _Move(void* _Where) noexcept override {
if constexpr (_Is_large<_Func_impl_no_alloc>) {
return nullptr;
} else {
return ::new (_Where) _Func_impl_no_alloc(_STD move(_Callee));
}
}
_Rx _Do_call(_Types&&... _Args) override { // call wrapped function
return _Invoker_ret<_Rx>::_Call(_Callee, _STD forward<_Types>(_Args)...);
}
const void* _Get() const noexcept override {
return _STD addressof(_Callee);
}
void _Delete_this(bool _Dealloc) noexcept override { // destroy self
this->~_Func_impl_no_alloc();
if (_Dealloc) {
_Deallocate<alignof(_Func_impl_no_alloc)>(this, sizeof(_Func_impl_no_alloc));
}
}
_Callable _Callee;
};
我们主要看一下_Do_call函数,它内部调用了_Invoker_ret<_Rx>::_Call,最终调用std::invoke完成函数调用
// helper to give INVOKE an explicit return type; avoids undesirable Expression SFINAE
template <class _Rx, bool = is_void_v<_Rx>>
struct _Invoker_ret { // selected for _Rx being cv void
template <class _Fx, class... _Valtys>
static _CONSTEXPR20 void _Call(_Fx&& _Func, _Valtys&&... _Vals) noexcept(_Select_invoke_traits<_Fx,
_Valtys...>::_Is_nothrow_invocable::value) { // INVOKE, "implicitly" converted to void
_STD invoke(static_cast<_Fx&&>(_Func), static_cast<_Valtys&&>(_Vals)...);
}
};
template <class _Rx>
struct _Invoker_ret<_Rx, false> { // selected for all _Rx other than cv void and _Unforced
template <class _Fx, class... _Valtys>
static _CONSTEXPR20 _Rx _Call(_Fx&& _Func, _Valtys&&... _Vals) noexcept(_Select_invoke_traits<_Fx,
_Valtys...>::template _Is_nothrow_invocable_r<_Rx>::value) { // INVOKE, implicitly converted to _Rx
return _STD invoke(static_cast<_Fx&&>(_Func), static_cast<_Valtys&&>(_Vals)...);
}
};
template <>
struct _Invoker_ret<_Unforced, false> { // selected for _Rx being _Unforced
template <class _Fx, class... _Valtys>
static _CONSTEXPR20 auto _Call(_Fx&& _Func, _Valtys&&... _Vals) noexcept(
_Select_invoke_traits<_Fx, _Valtys...>::_Is_nothrow_invocable::value)
-> decltype(_STD invoke(static_cast<_Fx&&>(_Func), static_cast<_Valtys&&>(_Vals)...)) { // INVOKE, unchanged
return _STD invoke(static_cast<_Fx&&>(_Func), static_cast<_Valtys&&>(_Vals)...);
}
};