解析网站接口怎么做,专门教ps的网站,关于网站建设与维护论文,女生学网站设计std::function是个有点神奇的模板#xff0c;无论是普通函数、函数对象、lambda表达式还是std::bind的返回值#xff08;以上统称为可调用对象#xff08;Callable#xff09;#xff09;#xff0c;无论可调用对象的实际类型是什么#xff0c;无论是有状态的还是无状态…std::function是个有点神奇的模板无论是普通函数、函数对象、lambda表达式还是std::bind的返回值以上统称为可调用对象Callable无论可调用对象的实际类型是什么无论是有状态的还是无状态的只要它们有相同参数类型和返回值类型就可以使用同一类型的std::function进行存储和调用。这种特性被称作类型擦除Type erasure它允许我们在不知道对象实际类型的情况下对对象进行存储和操作。
在本文中我将以std::function的libc实现14.0版本为例分析std::function类型擦除的实现原理以及实现一个精简版的std::functionMyFunction。
std::function如何实现类型擦除
在不知道对象实际类型的情况下操作对象有一种常规的手段可以实现这个功能那就是多态libc版的std::function正是基于虚函数实现的。具体是如何实现的呢我们可以从考察std::function在被调用时发生了什么作为这个问题的切入点。
对于以下代码
#include functional
#include iostream
int main() {std::functionvoid() f []() { //std::cout Hello, world! std::endl;};f();return 0;
}在std::cout一行打断点运行得到以下堆栈
#0 main::$_0::operator() (this0x7fffffffdb18) at /mnt/d/code/function_test/call.cpp:6
#1 0x0000555555557745 in std::__1::__invokemain::$_0 (__f...) at /usr/lib/llvm-14/bin/../include/c/v1/type_traits:3640
#2 0x00005555555576fd in std::__1::__invoke_void_return_wrappervoid, true::__callmain::$_0 (__args...) at /usr/lib/llvm-14/bin/../include/c/v1/__functional/invoke.h:61
#3 0x00005555555576cd in std::__1::__function::__alloc_funcmain::$_0, std::__1::allocatormain::$_0, void ()::operator()() (this0x7fffffffdb18) at /usr/lib/llvm-14/bin/../include/c/v1/__functional/function.h:180
#4 0x0000555555556839 in std::__1::__function::__funcmain::$_0, std::__1::allocatormain::$_0, void ()::operator()() (this0x7fffffffdb10) at /usr/lib/llvm-14/bin/../include/c/v1/__functional/function.h:354
#5 0x0000555555558622 in std::__1::__function::__value_funcvoid ()::operator()() const (this0x7fffffffdb10) at /usr/lib/llvm-14/bin/../include/c/v1/__functional/function.h:507
#6 0x00005555555577d5 in std::__1::functionvoid ()::operator()() const (this0x7fffffffdb10) at /usr/lib/llvm-14/bin/../include/c/v1/__functional/function.h:1184
#7 0x00005555555562e5 in main () at /mnt/d/code/function_test/call.cpp:8不考虑lambda本身以及invoke相关的类std::function实现相关的类有以下几个
std::__1::functionvoid ()std::__1::__function::__value_funcvoid ()std::__1::__function::__funcmain::$_0, std::__1::allocatormain::$_0, void ()std::__1::__function::__alloc_funcmain::$_0, std::__1::allocatormain::$_0, void ()
lambda的类型被定义为了main::$_0可以看出来function和__function::__value_func两个模板类不依赖lambda实际类型__function::__func和__function::__alloc_func对lambda类型有依赖。
std::function
从std::function看起被声明为拥有一个模板参数_Fp。我们使用的是它的特化版本具有两个模板参数返回值类型_Rp和参数列表类型_ArgTypes接下来几个类也都是特化出来的不再赘述。它有一个__function::__value_func_Rp(_ArgTypes...)类型的成员__f_
templateclass _Fp class function;templateclass _Rp, class ..._ArgTypes
class function_Rp(_ArgTypes...)
{typedef __function::__value_func_Rp(_ArgTypes...) __func;__func __f_;...
};
...
template class _Rp, class... _ArgTypes
template class _Fp, class
function_Rp(_ArgTypes...)::function(_Fp __f) : __f_(_VSTD::move(__f)) {}当std::function的operator()被调用时它只是地把调用转发给__f_
templateclass _Rp, class ..._ArgTypes
_Rp
function_Rp(_ArgTypes...)::operator()(_ArgTypes... __arg) const
{return __f_(_VSTD::forward_ArgTypes(__arg)...);
}__function::__value_func
看看__function::__value_func具体是什么类型
// __value_func creates a value-type from a __func.
template class _Fp class __value_func;template class _Rp, class... _ArgTypes class __value_func_Rp(_ArgTypes...)
{typename aligned_storage3 * sizeof(void*)::type __buf_;typedef __base_Rp(_ArgTypes...) __func;__func* __f_;...
};它的模板参数和std::function一致有两个成员一个成员是有3个指针大小的__buf_另一个成员是__function::__base_Rp(_ArgTypes...)*类型的__f_。
__function::__value_func的构造函数相对复杂一些主要是为了做一个优化当__f_指向的对象的大小小于等于__buf_的大小也就是3个指针时__f_会被构造在__buf_上这样可以减少堆上内存的分配
template class _Fp, class _Alloc
__value_func(_Fp __f, const _Alloc __a): __f_(nullptr)
{typedef allocator_traits_Alloc __alloc_traits;typedef __function::__func_Fp, _Alloc, _Rp(_ArgTypes...) _Fun;typedef typename __rebind_alloc_helper__alloc_traits, _Fun::type_FunAlloc;if (__function::__not_null(__f)){_FunAlloc __af(__a);if (sizeof(_Fun) sizeof(__buf_) is_nothrow_copy_constructible_Fp::value is_nothrow_copy_constructible_FunAlloc::value){__f_ ::new ((void*)__buf_) _Fun(_VSTD::move(__f), _Alloc(__af));}else{typedef __allocator_destructor_FunAlloc _Dp;unique_ptr__func, _Dp __hold(__af.allocate(1), _Dp(__af, 1));::new ((void*)__hold.get()) _Fun(_VSTD::move(__f), _Alloc(__a));__f_ __hold.release();}}
}需要注意到的一个细节是__f_在模板类定义中的类型是__function::__base而此处new出来的对象类型是__function::__func不难猜到__function::__func继承了__function::__base。
当__function::__value_func的operator()被调用时它也只是在做完合法性检查后把调用转发给了*__f_
_Rp operator()(_ArgTypes... __args) const
{if (__f_ nullptr)__throw_bad_function_call();return (*__f_)(_VSTD::forward_ArgTypes(__args)...);
}__function::__base
下面是__function::__base它是一个抽象模板类模板参数和std::function一致不包含可调用对象的具体类型
templateclass _Fp class __base;templateclass _Rp, class ..._ArgTypes
class __base_Rp(_ArgTypes...)
{__base(const __base);__base operator(const __base);
public:_LIBCPP_INLINE_VISIBILITY __base() {}_LIBCPP_INLINE_VISIBILITY virtual ~__base() {}virtual __base* __clone() const 0;virtual void __clone(__base*) const 0;virtual void destroy() _NOEXCEPT 0;virtual void destroy_deallocate() _NOEXCEPT 0;virtual _Rp operator()(_ArgTypes ...) 0;
#ifndef _LIBCPP_NO_RTTIvirtual const void* target(const type_info) const _NOEXCEPT 0;virtual const std::type_info target_type() const _NOEXCEPT 0;
#endif // _LIBCPP_NO_RTTI
};__function::__func
然后是__function::__func它继承了__function::__base并且其模板参数含有可调用对象的类型_Fp这正是实现类型擦除的关键类型_Fp被隐藏了在了__function::__base这个抽象类后面。__function::__func含有一个类型为__function::__alloc_func的成员__f_
// __func implements __base for a given functor type.
templateclass _FD, class _Alloc, class _FB class __func;templateclass _Fp, class _Alloc, class _Rp, class ..._ArgTypes
class __func_Fp, _Alloc, _Rp(_ArgTypes...): public __base_Rp(_ArgTypes...)
{__alloc_func_Fp, _Alloc, _Rp(_ArgTypes...) __f_;
public:explicit __func(_Fp __f): __f_(_VSTD::move(__f)) {}...
};__function::__func的operator()依然只是转发调用
templateclass _Fp, class _Alloc, class _Rp, class ..._ArgTypes
_Rp
__func_Fp, _Alloc, _Rp(_ArgTypes...)::operator()(_ArgTypes ... __arg)
{return __f_(_VSTD::forward_ArgTypes(__arg)...);
}__function::__alloc_func
然后是最后一个类__function::__alloc_func它有一个pair类型的成员__f_std::function构造时传入的可调用对象最终会存储在__f_中
// __alloc_func holds a functor and an allocator.
template class _Fp, class _Ap, class _FB class __alloc_func;template class _Fp, class _Ap, class _Rp, class... _ArgTypes
class __alloc_func_Fp, _Ap, _Rp(_ArgTypes...)
{__compressed_pair_Fp, _Ap __f_;public:...explicit __alloc_func(_Target __f): __f_(piecewise_construct, _VSTD::forward_as_tuple(_VSTD::move(__f)),_VSTD::forward_as_tuple()){}...
};在__function::__alloc_func的operator()方法中调用转发给了__invoke_void_return_wrapper::__call后面的流程就和std::function的实现无关了。
_Rp operator()(_ArgTypes... __arg)
{typedef __invoke_void_return_wrapper_Rp _Invoker;return _Invoker::__call(__f_.first(),_VSTD::forward_ArgTypes(__arg)...);
}最终我们发现“神奇”的类型擦除还是通过“朴素”的多态来实现的之所以显得神奇是因为多态被隐藏了起来没有暴露给用户。
std::function对构造参数的校验
仔细观察一下std::function的构造函数
template class _Rp, class... _ArgTypes
template class _Fp, class
function_Rp(_ArgTypes...)::function(_Fp __f) : __f_(_VSTD::move(__f)) {}构造函数对参数__f似乎并没有施加任何约束如何真是那样那我们在使用一个不恰当的_Fp类型构造std::function时很可能会得到可读性极差的编译错误信息因为std::function类本身对_Fp没有施加约束那么实例化std::function时也就不太可能出现错误了很有可能到了实例化__function::__alloc_func时编译错误才会报告出来这是一个内部类一般用户看到了关于它的实例化失败的错误信息大概会感到摸不着头脑。
但实际情况并不是这样的假设你这样定义一个std::function对象
std::functionvoid() f(1);你会得到一个比较清晰的编译错误信息
/mnt/d/code/function_test/myfunction.cpp:107:27: error: no matching constructor for initialization of std::functionvoid ()std::functionvoid() f(1);
...
/usr/lib/llvm-14/bin/../include/c/v1/__functional/function.h:998:5: note: candidate template ignored: requirement __callableint , false::value was not satisfied [with _Fp int]function(_Fp);
...这是怎么做到的呢答案藏在构造函数声明的第二个模板参数class _EnableIfLValueCallable_Fp
templateclass _Fp, class _EnableIfLValueCallable_Fp
function(_Fp);此处使用了SFINAE技术我们看看_EnableIfLValueCallable具体是怎么实现的
template class _Fp, bool _And_IsNotSame__uncvref_t_Fp, function,__invokable_Fp, _ArgTypes...
::value
struct __callable;
template class _Fpstruct __callable_Fp, true{static const bool value is_void_Rp::value ||__is_core_convertibletypename __invoke_of_Fp, _ArgTypes...::type,_Rp::value;};
template class _Fpstruct __callable_Fp, false{static const bool value false;};template class _Fp
using _EnableIfLValueCallable typename enable_if__callable_Fp::value::type;_EnableIfLValueCallable的实现依赖于__callable__callable是一个模板类拥有两个模板参数第一个模板参数_Fp是可调用对象的类型第二个模板参数是bool类型的当_IsNotSame__uncvref_t_Fp, function和__invokable_Fp, _ArgTypes...这两个条件同时满足时该模板参数为true否则为false。
_IsNotSame__uncvref_t_Fp, function顾名思义是用来判断两个模板参数是否为同一类型的这个条件似乎是为了避免歧义当我们用另一个std::function构造std::function时应该匹配到拷贝构造函数而不是这个。
__invokable_Fp, _ArgTypes...则是用来判断_Fp是否接受传入_ArgTypes参数调用。
__callable第二个模板参数为false的特化中将value直接定义为false。而模板参数为true的特化中还添加了新的判断条件用来校验可调用对象返回值的可转换性。
第一个条件为is_void_Rp::value用来判断_Rp为void类型。这意味着即使可调用对象实际上有返回类型但是std::function被定义为返回void那么编译也是可以通过的。
第二个条件是__is_core_convertibletypename __invoke_of_Fp, _ArgTypes...::type, _Rp::value用来判断_Fp被调用后返回值可转换为_Rp。
综上_Fp要满足以下条件std::function的构造函数才能正常实例化
_Fp不是std::function _Fp可以以_ArgTypes为参数调用 (_Rp为void || _Fp返回值类型可转换为_Rp)
这保证了当以不恰当的可调用对象构造std::function时能够尽可能提前触发编译错误提升编译错误信息的可读性。
MyFunction的实现
下面我们下面模仿libc实现一个“青春版”的std::functionMyFunction它忽略掉了大部分细节只实现了构造和调用部分的代码。
#include functional
#include iostream
#include utilitytemplate typename Func
class FunctionBase;template typename Ret, typename... Args
class FunctionBaseRet(Args...) {public:virtual Ret operator()(Args... args) 0;
};template typename Callable, typename Func
class FunctionImpl;template typename Callable, typename Ret, typename... Args
class FunctionImplCallable, Ret(Args...) : public FunctionBaseRet(Args...) {Callable c_;public:FunctionImpl(Callable c) : c_(std::move(c)) {}Ret operator()(Args... args) override {return std::invoke(c_, std::forwardArgs(args)...);}
};template typename Func
class MyFunction;template typename Ret, typename... Args
class MyFunctionRet(Args...) {FunctionBaseRet(Args...)* f_ nullptr;public:template typename CallableMyFunction(Callable c) {f_ new FunctionImplCallable, Ret(Args...)(std::move(c));}Ret operator()(Args... args) {if (f_ nullptr) {throw std::bad_function_call();}return (*f_)(std::forwardArgs(args)...);}
};void normalFunction() { std::cout Im a normal function std::endl; }struct FunctionObject {void operator()() { std::cout Im a function object std::endl; }
};int main() {MyFunctionvoid() f0 []() { std::cout Im a lambda std::endl; };f0();MyFunctionvoid() f1 normalFunction;f1();MyFunctionvoid() f2 FunctionObject();f2();return 0;
}结语
在没有std::function可用的年代或者场合我们一般会选择使用函数指针来实现类似std::function的功能。在使用C实现的Linux内核代码中我们仍可以看到大量的函数指针的存在主要是用来实现回调函数。
相较函数指针std::function最明显的优势在于可以方便地存储带状态的函数而函数指针只能以比较丑陋的方式来实现这个特性。
其次是灵活性std::function给客户代码施加的约束较小我们可以使用任意形式的可调用对象普通函数lambda表达式函数对象等函数指针就没有这种灵活性了。
不过由于虚函数的存在std::function多了一点性能开销但这点开销对大多数常规应用来说都是微不足道的。