##概覽c++
###std::function 安全
template<class _Rp, class ..._ArgTypes> class function<_Rp(_ArgTypes...)> : public __function::__maybe_derive_from_unary_function<_Rp(_ArgTypes...)>, public __function::__maybe_derive_from_binary_function<_Rp(_ArgTypes...)> { __base* __f_; //points to __func aligned_storage< 3 *sizeof(void *)>::type __buf_; //... };
std::function
最重要的部分就是這個__base*
指針,及其所指向的存儲了實際可調用對象的多態類__func
。__base
類充當了__func
類的接口,定義了clone
、operator()
等純虛函數。app
而__func
對象可能存儲的區域之一就是自帶的默認緩衝區__buf_
,部分MIPS指令集要求指令必需要對齊,因此這裏的存儲地址也要遵循平臺默認的對齊方式。默認的大小是3*sizeof(void*)
,這是純經驗數據,對大部分的函數指針以及成員函數指針這個大小都夠用(經@Anthonyhl提示,加上base*
指針,__func
對象總大小應該剛好是4*sizeof(void*)
)。但由於可調用對象大小變幻無窮,因此實際存儲的區域可能也會在新開的堆上。函數
std::function
類繼承自__maybe_derive_from_unary_function
與__maybe_derive_from_binary_function
兩個類。這兩個類在函數分別知足ResultT f(ArgT)
和ResultT f(Arg1T, Arg2T)
形式的時候,分別會特化繼承std::unary_function<ResultT, ArgT>
與std::binary_function<ResultT, arg1T, arg2T>
。 這兩個類是C++11以前對兩種特殊可調用對象的靜態接口,其內只有typedef
,在C++11以後已經deprecated,C++17後將移除,這裏繼承這兩個接口只是爲了兼容目的。關於C++11以前的<functional>
分析,詳見這篇文章。post
###__func 性能
template<class _Fp, class _Alloc, class _Rp, class ..._ArgTypes> class __func<_Fp, _Alloc, _Rp(_ArgTypes...)> : public __base<_Rp(_ArgTypes...)> { __compressed_pair<_Fp, _Alloc> __f_; //... };
__func
是實際存儲可調用對象的類,其繼承了__base
這個接口。可調用對象與allocator都被存儲在一個__compressed_pair
當中。this
###__base指針
template<class _Rp, class ..._ArgTypes> class __base<_Rp(_ArgTypes...)> { __base(const __base&); __base& operator=(const __base&); public: __base() {} 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_RTTI virtual const void* target(const type_info&) const _NOEXCEPT = 0; virtual const std::type_info& target_type() const _NOEXCEPT = 0; #endif // _LIBCPP_NO_RTTI };
__base
是一個純虛基類,是__func
類的接口,對外提供了clone
(複製、移動)、destroy
(析構)、operator()
(調用)等函數。 ##構造 從可調用對象構造出function
有如下幾步:code
__buf_
不夠存放可調用對象,新開內存__f_
指向的內存區域調用placement new,移動構造可調用對象。###對象是否可調用對象
template<class _Rp, class ..._ArgTypes> template <class _Fp> function<_Rp(_ArgTypes...)>::function(_Fp __f, typename enable_if < __callable<_Fp>::value && !is_same<_Fp, function>::value >::type*) //使用SFINAE檢查該對象是否可調用,而且不是std::function(防止出現function套function的狀況)。 : __f_(0)
在滾到下面以前,先猜一下__callable是怎麼實現的。注意如下代碼也是合法的,還要考慮reference_wrapper
、返回值轉化等各類形式:
struct A { void f() { cout << "called" << endl;} }; int main() { void (A::*mfp)() = &A::f; std::function<void(A*)> f(mfp); A a; f(&a); }
實際上,實現__callable主要依賴於invoke
的實現,invoke
規定了一個統一的調用方式,將於C++17標準中出現。不管是f(a,b)
仍是(f.*a)(b)
(f
是可調用對象,a
是成員函數指針)仍是(a->*f)(b)
(a
是可調用對象指針,f
是成員函數指針),均可以以invoke(f,a,b)
的形式調用。
知道了這個函數,咱們只要規定invoke
能夠調用,而且返回值能夠轉換成std::function
規定的返回類型的函數就是callable
:
template <class _Fp, bool = !is_same<_Fp, function>::value && __invokable<_Fp&, _ArgTypes...>::value> //__invokable表明是否這一些類型是否能夠發生調用 struct __callable; template <class _Fp> struct __callable<_Fp, true> { //若是能夠發生調用,繼續檢查返回值是否能夠轉換成function的返回值 static const bool value = is_same<void, _Rp>::value || //實際任何類型的T fun(...)都能被綁定到void fun(...),但T對void不是convertible is_convertible<typename __invoke_of<_Fp&, _ArgTypes...>::type, _Rp>::value; }; template <class _Fp> struct __callable<_Fp, false> { static const bool value = false; };
題外話,有人在C++17當中提出統一
x.f(a,b)
與f(x,a,b)
,應該會給invoke當前的複雜狀況帶來一點幫助:http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4165.pdf
###內存分配與構造
####function 爲了保證異常安全。分爲兩種狀況:若自帶的__buf_
大小夠大,且可調用對象的構造函數不拋出異常,則直接構造;不然,則用unique_ptr
來處理allocator分配出的內存地址,再在上面調用構造函數,這樣即便構造函數拋出了異常,unique_ptr
也會自動delete掉指向的內存地址;而若是用裸指針,構造函數拋出異常就會內存泄漏。
if (__not_null(__f)) { typedef __function::__func<_Fp, allocator<_Fp>, _Rp(_ArgTypes...)> _FF; if (sizeof(_FF) <= sizeof(__buf_) && is_nothrow_copy_constructible<_Fp>::value) //緩衝區夠大,構造函數不拋異常 { __f_ = (__base*)&__buf_; //__f_指向緩衝區 ::new (__f_) _FF(_VSTD::move(__f)); //直接構造,間接調用了__func的移動構造函數 } else { typedef allocator<_FF> _Ap; _Ap __a; typedef __allocator_destructor<_Ap> _Dp; unique_ptr<__base, _Dp> __hold(__a.allocate(1), _Dp(__a, 1)); //__a.allocate(1)分配了一個對象的內存,用unique_ptr保護起來 ::new (__hold.get()) _FF(_VSTD::move(__f), allocator<_Fp>(__a)); //placement new, 在指定的內存地址調用__func的構造函數。這一步new可能會拋異常,unique_ptr在異常時會自動析構並delete內存空間 __f_ = __hold.release(); //安全了,把指針的控制權移交給__f_ } }
####__func 這個構造函數之中調用了__func
類的構造函數:
__compressed_pair<_Fp, _Alloc> __f_; //__func的的__f_是一個compressed_pair, 不是上面的base*指針 explicit __func(_Fp&& __f, _Alloc&& __a) : __f_(piecewise_construct, _VSTD::forward_as_tuple(_VSTD::move(__f)), _VSTD::forward_as_tuple(_VSTD::move(__a))) {}
首先介紹下這個compressed_pair, 衆所周知C++的空類默認也會佔空間:
struct Null {}; struct Test { int a; }; struct B { Null n; Test c; }; cout << sizeof(Null) << " "<< sizeof(Test)<<" "<<sizeof(B)<<endl; //1 4 8
但這樣在有內存對其的時候其實浪費了大量的存儲空間,特別是對於function
這類小對象來講節約空間很是重要。對於空類Null,一個繼承自它的類B2,且B2非空類,則B2不會由於Null類的繼承而像上例中的內含同樣佔用空間:
struct B1 : private Null { }; struct B2 : private B1, private Test { }; cout << sizeof(B1)<<" "<<sizeof(B2) << endl; // 1 4
compressed_pair
就用了這種技巧來壓縮內存,這種技術在boost::compressed_pair
當中已經有成熟的庫,這裏libc++內部也製做了一個本身的__compressed_pair
。
再來講說這個piecewise_construct
。通常使用pair
時,咱們都是利用make_pair(T1(arg1, arg2), T2(arg))
這樣來構造。實際上,發生瞭如下的步驟:
T1
的xvalue(消亡值,屬於右值),匹配上make_pair(T1&&, T2&&)
make_pair
把這兩個右值引用傳遞給pair<T1, T2>(T1&& t1, T2&& t2)
pair
的構造函數把內部的first
, second
對象在初始化列表中以first(t1), second(t2)
形式初始化,這個t1,t2都是右值,因此調用了移動構造函數至關於咱們構造了一個臨時對象,而後又調用了移動構造函數。這樣就有一個問題:若是沒有移動構造函數怎麼辦?piecewise_construct
就是爲此而生的。使用pair<T1, T2>(piecewise_construct, tuple<Args...>&& t1, tuple<Args...>&& t2)
這樣的形式,最終初始化列表中會直接轉化成: first(std::forward<_Args1>(std::get<_I1>( __first_args))...)
,即這些參數會被直接傳遞給first
,second
對象,直接在pair
的構造函數內初始化first
second
,而不是先在造成參數時構造出臨時對象,再移動過去。這樣既有比較好的性能,也不須要具備first
,second
具備複製、移動構造函數。
##複製與移動 複製與移動實際上都是操做內部的__func
對象。可是,構造函數不具備多態性,怎麼根據父類的指針來得到子類的拷貝呢?這是一種經常使用的技巧:
virtual SuperClass* SubClass::clone() { return new SubClass(*this); } //至關於多態new virtual SuperClass* SubClass::clone(SuperClass* p) { return new (p) SubClass(*this); } //多態placement new
###複製構造
//.__f_是指向__func對象的指針 template<class _Rp, class ..._ArgTypes> function<_Rp(_ArgTypes...)>::function(const function& __f) { if (__f.__f_ == 0) //未初始化 __f_ = 0; else if (__f.__f_ == (const __base*)&__f.__buf_) //另外一個對象的__func存放在自身的緩衝區內,既然在緩衝區內能放下,也應該能在個人緩衝區內放下 { __f_ = (__base*)&__buf_; //本身指向自身的緩衝區 __f.__f_->__clone(__f_); //至關於new (__f_) __func(另外一個__func),把另外一個__func複製到自身緩衝區內 } else __f_ = __f.__f_->__clone(); //放不下了,讓它新開一塊內存複製到其中,而後本身指過去 }
###移動構造
template<class _Rp, class ..._ArgTypes> function<_Rp(_ArgTypes...)>::function(function&& __f) _NOEXCEPT { if (__f.__f_ == 0) __f_ = 0; else if (__f.__f_ == (__base*)&__f.__buf_) //__func在緩衝區,緩衝區夠用 { __f_ = (__base*)&__buf_; //不能直接指到對方緩衝區去,由於對方__buf會隨對象析構銷燬掉 __f.__f_->__clone(__f_); //仍是要複製到本身的緩衝區來 } else { __f_ = __f.__f_; //對方的__func在堆上,直接指過去 __f.__f_ = 0; //把對方的__f_指空 } }
##調用
調用的時候先檢查內部的__f_
指針是否爲空,若空則拋異常,不然調用__f_
指向的__func
對象的operator()
:
template<class _Rp, class ..._ArgTypes> _Rp function<_Rp(_ArgTypes...)>::operator()(_ArgTypes... __arg) const { #ifndef _LIBCPP_NO_EXCEPTIONS if (__f_ == 0) throw bad_function_call(); #endif // _LIBCPP_NO_EXCEPTIONS return (*__f_)(_VSTD::forward<_ArgTypes>(__arg)...); //調用內部__func對象的operator() }
ArgType |
forward<ArgType> |
---|---|
T |
static_cast<T&&> |
T& |
static_cast<T&> |
T&& |
static_cast<T&&> |
std::forward
做用如其名,即將參數向前傳遞。原先的ArgType
=T
時,在調用這個函數時已經複製過了一遍,所以複製過的值能夠做爲右值,forward<T>(t)
將t
轉成了右值。而對於原先是左值、右值引用的來講,則不能都做爲右值處理,而應保持它們自己的類別。
template<class _Fp, class _Alloc, class _Rp, class ..._ArgTypes> _Rp __func<_Fp, _Alloc, _Rp(_ArgTypes...)>::operator()(_ArgTypes&& ... __arg) //完美轉發 { typedef __invoke_void_return_wrapper<_Rp> _Invoker; //後述,與invoke的特殊語法有關 return _Invoker::__call(__f_.first(), _VSTD::forward<_ArgTypes>(__arg)...); //__f_.first()便可調用對象 }
這裏不直接return invoke(__f_.first(), ...)
的緣由是,若是__f_
的返回值是void
,但實際可調用對象返回值,就會出錯:
int foo() { return 42; } void bar() { return foo(); } //報錯,int不能轉成void void bar2() { foo(); } //針對void返回值這樣纔對 function<void()> f(foo); //合法
因此針對void
返回值要特化一下:
template <class _Ret> struct __invoke_void_return_wrapper { template <class ..._Args> static _Ret __call(_Args&&... __args) { return __invoke(_VSTD::forward<_Args>(__args)...); } }; template <> struct __invoke_void_return_wrapper<void> { template <class ..._Args> static void __call(_Args&&... __args) { __invoke(_VSTD::forward<_Args>(__args)...); } };
仔細思考一下整個調用過程,發現仍是具備負擔的: 對於形參是T的對象來講,
void foo(A) {} A a; foo(a); //a被複制構造一次 function<void(A)> f(foo); f(a); //先被複制構造一次,再被移動構造一次 // 等價於 A b(a); //這個複製發生在function::operator()的形參表裏 foo(forward<A>(b)); //發生了移動構造
因此在C++11中,移動構造很是重要,若是可以定義移動構造函數請務一定義。不然該例就會退化到兩次複製構造,若是在傳遞大對象時將是不小的負擔。
##總結
std::function
是自帶的可調用對象適配器。它經過內部__f_
指針調用所指向的__func
類對象的虛方法來實現多態的函數調用、new
與placement new
。其中內帶了一個大小是3*sizeof(void*)
的緩衝區,小對象將被分配在緩衝區上,大對象將另外在堆上分配內存存儲。__func
對象利用了compressed_pair
技術來壓縮存儲的可調用對象 - Allocator
對,並利用piecewise_construct
來就地構造這兩個對象,可以處理這兩個類沒有移動複製構造函數的狀況,也提升了性能。std::function
在形參是非引用時會多發生一次移動構造,可能成爲性能的瓶頸。