C++的雜七雜八:如何實現一個簡單的bind

這篇文的草稿我是在2014年5月11號開始打的,可是拖拖拉拉直到現在才真正動筆寫,自己對自己也是醉了。。
之所以寫bind而不是什麼其他的東西,是因爲bind在各種C++的utility裏面可以說是最能體現出“利用語言本身來拓展語言功能”這一特徵的了。

在C++98/03的時代,人們爲了讓C++具有把函數和參數打包成閉包(closure)這一能力而發明了bind,從而使C++不僅可以存儲算法執行的結果,還可以打包算法和算法的傳入參數,存儲算法執行的動作,直到調用這個閉包的時候才釋放出參數並參與算法計算。有了這個東西之後,再配合可以存儲任何可調用體的function對象,不少面向對象裏麻煩的調用關係可以被簡化到不像話的地步(具體可參看此文:以boost::function和boost:bind取代虛函數)。

在C++98/03下實現一個好用的bind是痛苦的。看看Boost.Bind,還有我自己,爲了實現一個完善的bind折騰遍了C++的奇巧淫技……這是很划不來的,平時學習或興趣玩一玩還可以,真在項目工程裏這樣做了,如何維護會是一個很頭痛的事情。

在C++11裏事情變得很不一樣了。首先stl裏就已經給我們提供了好用的std::bind,然後再就是語言本身的進化,讓“寫一個bind”之類的事情變得無比簡單。於是我去年曾花了點時間,用C++11寫了下面這個200多行的“simple bind”,用來讓自己被C++98/03醃入味的思維習慣向C++11轉換一下:

#include <type_traits>
#include <tuple>
#include <utility>
 
namespace simple {
 
/*
    Placeholder
*/
 
template <int N>
struct placeholder {};
 
const placeholder<1> _1; const placeholder<6>  _6;  const placeholder<11> _11; const placeholder<16> _16;
const placeholder<2> _2; const placeholder<7>  _7;  const placeholder<12> _12; const placeholder<17> _17;
const placeholder<3> _3; const placeholder<8>  _8;  const placeholder<13> _13; const placeholder<18> _18;
const placeholder<4> _4; const placeholder<9>  _9;  const placeholder<14> _14; const placeholder<19> _19;
const placeholder<5> _5; const placeholder<10> _10; const placeholder<15> _15; const placeholder<20> _20;
 
/*
    Sequence & Generater
*/
 
template <int... N>
struct seq { typedef seq<N..., sizeof...(N)> next_type; };
 
template <typename... P>
struct gen;
 
template <>
struct gen<>
{
    typedef seq<> seq_type;
};
 
template <typename P1, typename... P>
struct gen<P1, P...>
{
    typedef typename gen<P...>::seq_type::next_type seq_type;
};
 
/*
    Merge the type of tuple
*/
 
template <typename T, typename TupleT>
struct tuple_insert;
 
template <typename T, typename... TypesT>
struct tuple_insert<T, std::tuple<TypesT...>>
{
    typedef std::tuple<T, TypesT...> type;
};
 
template <class TupleT, typename... BindT>
struct merge;
 
template <typename... ParsT>
struct merge<std::tuple<ParsT...>>
{
    typedef std::tuple<> type;
};
 
template <typename... ParsT, typename B1, typename... BindT>
struct merge<std::tuple<ParsT...>, B1, BindT...>
{
    typedef std::tuple<ParsT...> tp_t;
    typedef typename tuple_insert<
            B1, 
            typename merge<tp_t, BindT...>::type
    >::type type;
};
 
template <typename... ParsT, int N, typename... BindT>
struct merge<std::tuple<ParsT...>, const placeholder<N>&, BindT...>
{
    typedef std::tuple<ParsT...> tp_t;
    typedef typename tuple_insert<
            typename std::tuple_element<N - 1, tp_t>::type,
            typename merge<tp_t, BindT...>::type
    >::type type;
};
 
/*
    Select the value of tuple
*/
 
template <typename T, class TupleT>
inline auto select(TupleT& /*tp*/, T&& val) -> T&&
{
    return std::forward<T>(val);
}
 
template <int N, class TupleT>
inline auto select(TupleT& tp, placeholder<N>) -> decltype(std::get<N - 1>(tp))
{
    return std::get<N - 1>(tp);
}
 
/*
    Return type traits
*/
 
template <typename F>
struct return_traits : return_traits<decltype(&F::operator())> {};
 
template <typename T>
struct return_traits<T*> : return_traits<T> {};
 
// check function
 
template <typename R, typename... P>
struct return_traits<R(*)(P...)> { typedef R type; };
 
// check member function
 
#define RESULT_TRAITS__(...) \
    template <typename R, typename C, typename... P> \
    struct return_traits<R(C::*)(P...) __VA_ARGS__> { typedef R type; };
 
RESULT_TRAITS__()
RESULT_TRAITS__(const)
RESULT_TRAITS__(volatile)
RESULT_TRAITS__(const volatile)
 
#undef RESULT_TRAITS__
 
/*
    Type detect
*/
 
template <typename T>
struct is_pointer_noref
    : std::is_pointer<typename std::remove_reference<T>::type>
{};
 
template <typename T>
struct is_memfunc_noref
    : std::is_member_function_pointer<typename std::remove_reference<T>::type>
{};
 
template <typename T>
struct is_wrapper
    : std::false_type
{};
 
template <typename T>
struct is_wrapper<std::reference_wrapper<T>>
    : std::true_type
{};
 
template <typename T>
struct is_wrapper_noref
    : is_wrapper<typename std::remove_reference<typename std::remove_cv<T>::type>::type>
{};
 
/*
    The invoker for call a callable
*/
 
template <typename R, typename F, typename... P>
inline typename std::enable_if<is_pointer_noref<F>::value,
R>::type invoke(F&& f, P&&... args)
{
    return (*std::forward<F>(f))(std::forward<P>(args)...);
}
 
template <typename R, typename F, typename P1, typename... P>
inline typename std::enable_if<is_memfunc_noref<F>::value &&
                               is_pointer_noref<P1>::value,
R>::type invoke(F&& f, P1&& this_ptr, P&&... args)
{
    return (std::forward<P1>(this_ptr)->*std::forward<F>(f))(std::forward<P>(args)...);
}
 
template <typename R, typename F, typename P1, typename... P>
inline typename std::enable_if<is_memfunc_noref<F>::value &&
                              !is_pointer_noref<P1>::value && !is_wrapper_noref<P1>::value,
R>::type invoke(F&& f, P1&& this_obj, P&&... args)
{
    return (std::forward<P1>(this_obj).*std::forward<F>(f))(std::forward<P>(args)...);
}
 
template <typename R, typename F, typename P1, typename... P>
inline typename std::enable_if<is_memfunc_noref<F>::value &&
                              !is_pointer_noref<P1>::value && is_wrapper_noref<P1>::value,
R>::type invoke(F&& f, P1&& this_wrp, P&&... args)
{
    typedef typename std::remove_reference<P1>::type wrapper_t;
    typedef typename wrapper_t::type this_t;
    return (static_cast<this_t&>(std::forward<P1>(this_wrp)).*std::forward<F>(f))(std::forward<P>(args)...);
}
 
template <typename R, typename F, typename... P>
inline typename std::enable_if<!is_pointer_noref<F>::value && !is_memfunc_noref<F>::value,
R>::type invoke(F&& f, P&&... args)
{
    return std::forward<F>(f)(std::forward<P>(args)...);
}
 
/*
    Simple functor for bind callable type and arguments
*/
 
template<typename FuncT, typename... ParsT>
class fr
{
    typedef std::tuple<typename std::decay<ParsT>::type...> args_type;
    typedef typename std::decay<FuncT>::type                callable_type;
    typedef typename return_traits<callable_type>::type     return_type;
 
    callable_type call_;
    args_type     args_;
 
    template <class TupleT, int... N>
    return_type do_call(TupleT&& tp, seq<N...>)
    {
        typedef typename merge<TupleT, ParsT...>::type params_t;
        return invoke<return_type>
            (call_, static_cast<typename std::tuple_element<N, params_t>::type>(select(tp, std::get<N>(args_)))...);
    }
 
public:
#if defined(_MSC_VER) && (_MSC_VER <= 1800)
    /*
        <MSVC 2013> Visual Studio does not support defaulted move constructors or 
                    move-assignment operators as the C++11 standard mandates.
        See: http://stackoverflow.com/questions/24573963/move-constructor-invalid-type-for-defaulted-constructor-vs-2013
    */
    fr(fr&& rhs) : call_(std::move(rhs.call_)), args_(std::move(rhs.args_)) {}
#else
    fr(fr&&)      = default;
#endif
    fr(const fr&) = default;
 
    fr(FuncT f, ParsT... args)
        : call_(std::forward<FuncT>(f))
        , args_(std::forward<ParsT>(args)...)
    {}
 
    template <typename... P>
    return_type operator()(P&&... args)
    {
        return do_call(std::forward_as_tuple(std::forward<P>(args)...), typename gen<ParsT...>::seq_type());
    }
};
 
/*
    Bind function arguments
*/
 
template <typename F, typename... P>
inline fr<F&&, P&&...> bind(F&& f, P&&... args)
{
    return { std::forward<F>(f), std::forward<P>(args)... };
}
 
} // namespace simple

這坨代碼在閱讀的時候,需要從後往前看。首先,我們來看下最後的這個函數:

/*
    Bind function arguments
*/
 
template <typename F, typename... P>
inline fr<F&&, P&&...> bind(F&& f, P&&... args)
{
    return { std::forward<F>(f), std::forward<P>(args)... };
}

這裏有幾個小地方需要說明下。
第一,fr是一個仿函數。bind想要實現的功能,目的就是要返回一個可以被施以括號操作符()進行類似函數調用的東西,因此fr是一個仿函數是很自然的事情。
第二,fr是一個類模板,參數是F&&和P&&...。這樣做的目的是,fr需要保存bind參數中的f和args,自然需要在編譯期獲得它們的類型。bind的函數模板參數可以在編譯的時候自動幫我們推導出類型,因此把類型傳遞給fr就可以了。爲何是F&&和P&&,而不是F和P呢?這是因爲F&&、P&&是通過引用摺疊特徵推導出來的攜帶了參數左右值屬性的引用類型,這樣處理可以讓我們在寫fr的時候可以直接利用這些引用類型來作爲構造時的參數,省下不少類型轉換的麻煩。

那麼接下來,該看一下類模板fr了。首先,fr需要是一個仿函數,它需要有一個可以接受任意參數的operator();
然後,很自然的,它需要有可以可以接收f和args的構造函數,然後它就成了這個樣子:

template<typename FuncT, typename... ParsT>
class fr
{
public:
    fr(FuncT f, ParsT... args)
        : call_(std::forward<FuncT>(f))
        , args_(std::forward<ParsT>(args)...)
    {}
 
    template <typename... P>
    return_type operator()(P&&... args)
    {
        // ......
    }
};

那麼這裏用來儲存f和args的分別是call_和args_,它們倆很顯然是fr的成員變量。
它們倆的類型分別應該是std::decay<FuncT>::type和std::tuple<typename std::decay<ParsT>::type...>。另外,fr還必須有一個拷貝構造函數和一個移動構造函數,把這些加上,fr就變成了這個樣子:

template<typename FuncT, typename... ParsT>
class fr
{
    typedef std::tuple<typename std::decay<ParsT>::type...> args_type;
    typedef typename std::decay<FuncT>::type                callable_type;
 
    callable_type call_;
    args_type     args_;
 
public:
    fr(fr&&)      = default;
    fr(const fr&) = default;
    // ......
};

蛋疼的是,這樣雖然符合標準,但在VS2013下卻是編譯不過的,原因見此:Move Constructor - invalid type for defaulted constructor VS 2013,因此我們不得不給默認的移動構造函數加上一個空實現:

    // ......
public:
#if defined(_MSC_VER) && (_MSC_VER <= 1800)
    /*
        <MSVC 2013> Visual Studio does not support defaulted move constructors or 
                    move-assignment operators as the C++11 standard mandates.
        See: http://stackoverflow.com/questions/24573963/move-constructor-invalid-type-for-defaulted-constructor-vs-2013
    */
    fr(fr&& rhs) : call_(std::move(rhs.call_)), args_(std::move(rhs.args_)) {}
#else
    fr(fr&&)      = default;
#endif
    // ......

做好了這些以後,可以說bind的框架已經搭起來了,接下來需要實現最核心的部分,也就是fr的operator()。在寫operator()之前,我們必須解決下面幾個問題:

1. 返回值(return_type)應該如何萃取? 2. 已打包好的args_如何一個個的放入call_中進行調用? 3. 在真正被調用時,如何將綁定時指定的佔位符替換成真正的實參?

首先,我們來想辦法從callable_type中把返回值類型return_type萃取出來:

/*
    Return type traits
*/
 
template <typename F>
struct return_traits : return_traits<decltype(&F::operator())> {};
 
template <typename T>
struct return_traits<T*> : return_traits<T> {};
 
// check function
 
template <typename R, typename... P>
struct return_traits<R(*)(P...)> { typedef R type; };
 
// check member function
 
#define RESULT_TRAITS__(...) \
    template <typename R, typename C, typename... P> \
    struct return_traits<R(C::*)(P...) __VA_ARGS__> { typedef R type; };
 
RESULT_TRAITS__()
RESULT_TRAITS__(const)
RESULT_TRAITS__(volatile)
RESULT_TRAITS__(const volatile)
 
#undef RESULT_TRAITS__

如上,我們構建了一個traits模板,當它遇到普通函數指針或類成員函數指針時會直接返回函數的返回值類型;當它遇到普通指針時,會取出指針內容的類型再次放入traits裏;當遇到的是普通類型,則嘗試取出類型的operator()成員函數指針,並把此指針類型放入traits。
於是我們上面的匹配規則覆蓋了普通函數指針、類成員函數指針、普通指針和仿函數。

好了,返回值類型的問題解決了,接下來,打包好的args如何解包呢?
首先,打包好的args實際上是一個std::tuple。那麼從args中獲取第N個元素的方法則是std::get<N>(args_)。在使用bind的時候,我們會爲待綁定的function指定它的參數,因此fr的typename... ParsT變參即保存了參數個數。
於是解包的過程就變成了如何在編譯期通過ParsT得到一個“0, 1, 2...”的編譯期常數數列。
我們可以通過這個模板元gen來完成保存了編譯期常數數列seq的計算:

/*
    Sequence & Generater
*/
 
template <int... N>
struct seq { typedef seq<N..., sizeof...(N)> next_type; };
 
template <typename... P>
struct gen;
 
template <>
struct gen<>
{
    typedef seq<> seq_type;
};
 
template <typename P1, typename... P>
struct gen<P1, P...>
{
    typedef typename gen<P...>::seq_type::next_type seq_type;
};

有了上面的gen以後,若ParsT包含了3個參數,gen<ParsT...>::seq_type的類型即爲seq<0, 1, 2>。
讓我們先不管佔位符_1、_2……之類的處理,只考慮bind時指定的所有實參,我們可以構造這樣的一個輔助函數來完成call_的調用:

template <int... N>
return_type do_call(seq<N...>)
{
    typedef std::tuple<ParsT...> params_t;
    return call_(static_cast<typename std::tuple_element<N, params_t>::type>(std::get<N>(args_))...);
}
 
return_type operator()(void)
{
    return do_call(typename gen<ParsT...>::seq_type());
}

上面的寫法裏,把ParsT...重新打包爲一個std::tuple類型,然後使用變參模板的自動展開配合std::tuple_element和std::get把ParsT和args_一個個解出來;接着使用static_cast把使用std::get得到的實參還原爲綁定時的類型,放入call_中。
當然,直接通過call_(...)這樣調用肯定是錯誤的,因爲call_有可能是一個普通指針,或一個類成員函數指針,必須再引入一個輔助函數invoke來做一次轉換。

下面我們來針對各種情況實現invoke函數。首先,很自然的,普通函數指針和仿函數指針都可以使用這個invoke:

template <typename R, typename F, typename... P>
inline typename std::enable_if<std::is_pointer<F>::value,
R>::type invoke(F&& f, P&&... args)
{
    return (*std::forward<F>(f))(std::forward<P>(args)...);
}

但是由於當f不是右值的時候,F是一個引用類型,所以我們需要在判斷is_pointer之前先remove_reference:
template <typename T>
struct is_pointer_noref
    : std::is_pointer<typename std::remove_reference<T>::type>
{};
 
template <typename R, typename F, typename... P>
inline typename std::enable_if<is_pointer_noref<F>::value,
R>::type invoke(F&& f, P&&... args)
{
    return (*std::forward<F>(f))(std::forward<P>(args)...);
}

然後,當f是一個類成員函數時,有兩種可能:1. 第一個參數是一個指針;2. 第一個參數是一個對象。我們先來考慮第一個參數是指針的情況:

template <typename T>
struct is_memfunc_noref
    : std::is_member_function_pointer<typename std::remove_reference<T>::type>
{};
 
template <typename R, typename F, typename P1, typename... P>
inline typename std::enable_if<is_memfunc_noref<F>::value &&
                               is_pointer_noref<P1>::value,
R>::type invoke(F&& f, P1&& this_ptr, P&&... args)
{
    return (std::forward<P1>(this_ptr)->*std::forward<F>(f))(std::forward<P>(args)...);
}

同普通函數一樣,參數和函數類型都先去掉了引用再進行判斷。
當第一個參數是一個對象的時候,情況變得稍稍有些複雜。因爲這個參數有可能是一個std::reference_wrapper。我們先把這種情況排除掉:

template <typename T>
struct is_wrapper
    : std::false_type
{};
 
template <typename T>
struct is_wrapper<std::reference_wrapper<T>>
    : std::true_type
{};
 
template <typename T>
struct is_wrapper_noref
    : is_wrapper<typename std::remove_reference<typename std::remove_cv<T>::type>::type>
{};
 
template <typename R, typename F, typename P1, typename... P>
inline typename std::enable_if<is_memfunc_noref<F>::value &&
                              !is_pointer_noref<P1>::value && !is_wrapper_noref<P1>::value,
R>::type invoke(F&& f, P1&& this_obj, P&&... args)
{
    return (std::forward<P1>(this_obj).*std::forward<F>(f))(std::forward<P>(args)...);
}

相比起第一參數爲指針的情況,第一參數爲對象(引用)其實就是把->*操作符換成了.*操作符。
那麼當考慮了std::reference_wrapper以後,很顯然,我們需要把wrapper中真正的類型撥出來,否則無法直接使用.*操作符:

template <typename R, typename F, typename P1, typename... P>
inline typename std::enable_if<is_memfunc_noref<F>::value &&
                              !is_pointer_noref<P1>::value && is_wrapper_noref<P1>::value,
R>::type invoke(F&& f, P1&& this_wrp, P&&... args)
{
    typedef typename std::remove_reference<P1>::type wrapper_t;
    typedef typename wrapper_t::type this_t;
    return (static_cast<this_t&>(std::forward<P1>(this_wrp)).*std::forward<F>(f))(std::forward<P>(args)...);
}

最後,我們來考慮仿函數,非常簡單,把指針和成員函數指針的情況過濾掉,剩下的就是專門處理仿函數的case了:

template <typename R, typename F, typename... P>
inline typename std::enable_if<!is_pointer_noref<F>::value && !is_memfunc_noref<F>::value,
R>::type invoke(F&& f, P&&... args)
{
    return std::forward<F>(f)(std::forward<P>(args)...);
}

invoke做好了,我們前面的do_call就可以這樣寫了:

template <int... N>
return_type do_call(seq<N...>)
{
    typedef std::tuple<ParsT...> params_t;
    return invoke<return_type>
        (call_, static_cast<typename std::tuple_element<N, params_t>::type>(std::get<N>(args_))...);
}

接下來,我們來考慮佔位符(placeholder)。
佔位符本身只是一個標記,一種特殊的類型對象。我們可以使用一個空的類模板,並對其進行實例化來完成_1 - _20的定義:

/*
    Placeholder
*/
 
template <int N>
struct placeholder {};
 
const placeholder<1> _1; const placeholder<6>  _6;  const placeholder<11> _11; const placeholder<16> _16;
const placeholder<2> _2; const placeholder<7>  _7;  const placeholder<12> _12; const placeholder<17> _17;
const placeholder<3> _3; const placeholder<8>  _8;  const placeholder<13> _13; const placeholder<18> _18;
const placeholder<4> _4; const placeholder<9>  _9;  const placeholder<14> _14; const placeholder<19> _19;
const placeholder<5> _5; const placeholder<10> _10; const placeholder<15> _15; const placeholder<20> _20;

上面的寫法直接用了const來定義佔位符,按照C++標準,這些佔位符默認是static的。
當然了,比較好的做法應該是使用extern const,不過這樣就需要一個cpp來對這些佔位符提供定義了。好在編譯器是聰明的,對於static的全局變量,雖然原則上不同的獨立編譯單元會重新實例化,但如果它們全是一樣的,而且又沒有在運行時被修改,它們一般會被優化爲同一份內存。

佔位符要完成“佔位”的功能,需要我們從兩方面把它篩選出來,並替換爲外部實際調用閉包時向operator()傳入的參數。
首先,當然是實參本身的替換了。也就是在調用invoke的時候,向invoke傳遞的參數在傳遞之時就應當完成替換;
然後,是參數類型的替換。我們在向invoke傳遞參數的同時,還使用std::tuple_element從params_t中提取出了對應的類型,並對std::get的結果進行了static_cast之後才能正確的invoke。因此params_t必須在std::tuple_element之前,把其中的placeholder換成對應的實參類型。

對於佔位符對象的替換,我們可以使用一個select函數來完成。通過C++的函數重載,其實根本不需要祭出“SIFNAE”之類的大招就可以很輕鬆的把placeholder剃出來:

/*
    Select the value of tuple
*/
 
template <typename T, class TupleT>
inline auto select(TupleT& /*tp*/, T&& val) -> T&&
{
    return std::forward<T>(val);
}
 
template <int N, class TupleT>
inline auto select(TupleT& tp, placeholder<N>) -> decltype(std::get<N - 1>(tp))
{
    return std::get<N - 1>(tp);
}

這裏的tp,是調用operator()時傳遞的所有實參的tuple,而第二個參數則是args_通過std::get解包後得到的參數。償若args_解包得到的是placeholder,那麼select就會使用tp並解包出對應位置的參數傳回去;否則就仍然使用args_解包得到的參數。

從params_t中把placeholder的類型置換掉稍微有些麻煩,我們需要用一點模板元的思想去處理這個問題。考慮有這麼一個模板merge,它第一個模板參數是調用operator()時傳遞的實參的類型tuple,後面則是變參列表,傳遞的是bind時的所有參數類型,那麼我們可以先定義出merge的基本樣子:

template <class TupleT, typename... BindT>
struct merge;
 
template <typename... ParsT>
struct merge<std::tuple<ParsT...>>
{
    typedef std::tuple<> type;
};

merge<std::tuple<ParsT...>>的模板特化是爲了處理bind無參數的情況,這個時候只有用戶在調用operator()時傳遞的參數列表。由於bind時沒有指定任何一個參數,operator()的參數將全部被忽略。
那麼,後面的置換過程和select其實是差不多的,只是select使用的是重載,返回的是一個運行時的對象;而merge的武器則是特化,返回的是一個類型:

template <typename T, typename TupleT>
struct tuple_insert;
 
template <typename T, typename... TypesT>
struct tuple_insert<T, std::tuple<TypesT...>>
{
    typedef std::tuple<T, TypesT...> type;
};
 
template <typename... ParsT, typename B1, typename... BindT>
struct merge<std::tuple<ParsT...>, B1, BindT...>
{
    typedef std::tuple<ParsT...> tp_t;
    typedef typename tuple_insert<
            B1, 
            typename merge<tp_t, BindT...>::type
    >::type type;
};
 
template <typename... ParsT, int N, typename... BindT>
struct merge<std::tuple<ParsT...>, const placeholder<N>&, BindT...>
{
    typedef std::tuple<ParsT...> tp_t;
    typedef typename tuple_insert<
            typename std::tuple_element<N - 1, tp_t>::type,
            typename merge<tp_t, BindT...>::type
    >::type type;
};

這裏匹配佔位符用的是const placeholder<N>&,原因是特化必須精準的給出需要匹配的類型,而不像重載,會自動選擇最合適的進行適配。
有了invoke、select和merge以後,我們就可以把operator()寫完整了:

template <class TupleT, int... N>
return_type do_call(TupleT&& tp, seq<N...>)
{
    typedef typename merge<TupleT, ParsT...>::type params_t;
    return invoke<return_type>
        (call_, static_cast<typename std::tuple_element<N, params_t>::type>(select(tp, std::get<N>(args_)))...);
}
 
template <typename... P>
return_type operator()(P&&... args)
{
    return do_call(std::forward_as_tuple(std::forward<P>(args)...), typename gen<ParsT...>::seq_type());
}

以上,一個簡單的bind構建完畢。上面的代碼沒有考慮類成員的綁定(只考慮了類成員函數),如果讀者感興趣,可自行增加此功能。


友情鏈接:qicosmos - std::bind技術內幕

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章