std::function源碼分析

##概覽c++

###std::function 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類的接口,定義了cloneoperator()等純虛函數。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 __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類對象的虛方法來實現多態的函數調用、newplacement new。其中內帶了一個大小是3*sizeof(void*)的緩衝區,小對象將被分配在緩衝區上,大對象將另外在堆上分配內存存儲。
  • __func對象利用了compressed_pair技術來壓縮存儲的可調用對象 - Allocator對,並利用piecewise_construct來就地構造這兩個對象,可以處理這兩個類沒有移動複製構造函數的狀況,也提升了性能。
  • std::function在形參是非引用時會多發生一次移動構造,可能成爲性能的瓶頸。
相關文章
相關標籤/搜索