本文的目標是,讓以下代碼能順利跑起來:
int intfun0()
{
return 1;
}
struct _intfunctor0
{
int operator()()
{
return 2;
}
} intfunctor0;
struct Test
{
int intmem0()
{
return 3;
}
} test;
int main()
{
Function<int ()> f1(&intfun0);
Function<int ()> f1_(intfun0);
Function<int ()> f2(intfunctor0);
Function<int ()> f3(&test, &Test::intmem0);
f1();
f1_();
f2();
f3();
return 0;
}
除了上述例子中顯示的,還要支持有返回值的函數和沒返回值的函數,以及有0個、1個、2個、……、MAX 個參數的函數,參數類型無限制。最後實現的 Function 對象僅僅可以執行就好。(至於是否可拷貝、是否可判斷相等 等問題,都是小事,本文暫不考慮。)最後,Bind 概念也不在本文討論范圍之內。
對於這個問題,我們一開始考慮的可能是怎樣統一三種不同形式。有兩個選擇,第一,使用 C++ 的多態機制,最後統一到基類指針的類型;第二,允許類內部有冗余變量以及必要的 Flag,用於判斷是哪種形式的函數,要如何執行。這樣看起來,第一種方案比第二種爽一點。於是,最初想到的實現有可能是這樣的:
先定義一個虛基類:
template <typename R>
class FunctionBase0
{
public:
virtual R Invoke() = 0;
virtual ~FunctionBase0() {}
};
然後實現一個普通函數/仿函數的版本:
template <typename R, typename T>
class Function0 : public FunctionBase0<R>
{
public:
R Invoke()
{
return m_Fun();
}
public:
Function0(const T &fun)
: m_Fun(fun)
{
}
private:
T m_Fun;
};
這裡需要說明的是,如果是普通函數,T會被特化成 R() 或者 R (&)() 或者 R(*)(),取決於使用的時候傳入 fun 還是傳入 &fun。所以不必另外實現針對 R(*)() 的版本。Loki (姑且就以作品名稱乎 Loki 的作者吧,他那個真名實在是太長)在他的書中稱之為“做一個,送一個”。不過對於他書中所說的,我有一個疑惑。Loki 說傳入 fun,模版參數 T 會被特化成 R (&)(),於是一切順利。可是我在操作過程中發現 T 一直被特化成 R (),於是上述 class 中的 m_Fun 被認為是成員函數而不是成員變量。不知道是為什麼,有知道者請不吝指教哈。因為以上原因,本文中我一直用 &fun 的形式對待普通函數。
再實現一個成員函數的版本:
template <typename R, typename T>
class MemberFunction0 : public FunctionBase0<R>
{
public:
R Invoke()
{
return (m_pObj->*m_pMemFun)();
}
public:
MemberFunction0(T *pObj, R (T::*pMemFun)())
: m_pObj(pObj), m_pMemFun(pMemFun)
{
}
private:
R (T::*m_pMemFun)();
T *m_pObj;
};
最後是一個包裝類。如果你可以接受 Function<int> 表示 int(), Function<int, int> 表示 int (int),…,那麼這裡沒有多少技巧可言。boost 的那個 function 使用的是函數簽名作為模版參數,即 Function<int()>,Function<int (int)> 等形式。如果不太研究語法,可能會像我一樣,一開始會對尖括號裡的 int (int) 之類的玩意兒不太熟悉,覺得很牛逼。可是了解了以後,不過是個函數類型而已,沒什麼大不了的。Loki 的 Functor 的使用方式是 Functor<int, TYPELIST_0()>,Functor<int, TYPELIST_1(int)>。其中第一個模版參數始終是返回值,第二個模版參數是參數類型列表,Loki 使用了他創造的玩意兒 TypeList 使得所有函數參數只占一個坑,這在等下的支持多參數的擴展中能夠帶來一些美觀。我比較喜歡 boost 的使用方式,讓使用者直接以語言規定的形式填入函數簽名,而不是一些額外的約定(“第一個模版參數表示返回值”,“第二個到最後的模版參數表示參數”,“第二個模版參數以 TypeList 形式表示函數參數”等)。
為了達到這個目標,我們要玩一些偏特化技巧。關於偏特化,我一直以來的膚淺認識都是錯誤的。我原以為,對於模版類:
template <typename T0, typename T1>
class Foo;
我如果特化其中一個參數 T1:
template <typename T0>
class Foo<T0, int>
{
}
我以為只有這樣才叫偏特化,以為偏特化的過程總是減少模版參數的。而實際上,只要用某個/些類型占據原始模版參數的位置,就可以了。比如,對於上述 Foo,我可以特化一個 class<T0, std::map<U0, U1>>,消去一個 T1,而新增 U0、U1:
template <typename T0, typename U0, typename U1>
class Foo<T0, std::map<U0, U1>>
{
}
原來 T1 的位置被 std::map<U0, U1> 占據了,這也是偏特化。當然最後的模版參數數量也可以不變,如:
template <typename T0, typename U>
class Foo<T0, std::vector<U>>
{
}
以及
template <typename T0, typename U>
class Foo<T0, U*>
{
}
其中後者是實現類型萃取的主要方式。只要特化以後,這個類依然帶有至少一個模版參數,就是偏特化。如果最後產生了 template<> 的形式,那就是完全特化。
回到我們剛才的主題,我們要提供給用戶的是這樣一個類:
template <typename Signature>
class Function;
其中參數 Signature 會被實際的函數類型所特化。但是我們只知道整體的一個 Signature 並沒有用,我們必須知道被分解開來的返回值類型、參數類型。於是,引入一個偏特化版本:
template <typename R>
class Function<R ()>
這裡使用 R () 特化原始的 Signature,引入一個新的參數 R。於是返回值類型 R 就被萃取出來了。實現如下:
template <typename R>
class Function<R ()>
{
public:
template <typename T>
Function(const T &fun)
: m_pFunBase(new Function0<R, T>(fun))
{
}
template <typename T>
Function(T *pObj, R (T::*pMemFun)())
: m_pFunBase(new MemberFunction0<R, T>(pObj, pMemFun))
{
}
~Function()
{
delete m_pFunBase;
}
R operator ()()
{
return m_pFunBase->Invoke();
}
private:
FunctionBase0<R> *m_pFunBase;
};
如果對上面說的“普通函數的使用方式必須是函數指針而不是函數本身”耿耿於懷,可以再引入一個的構造函數:
typedef R (F