目錄:
C++ 下 Function 對象的實(shí)現(xiàn)(上)
C++ 下 Function 對象的實(shí)現(xiàn)(下)
起因在上一篇已經(jīng)說過了。現(xiàn)在讓我們直接進(jìn)入主題。本文的目標(biāo)是,讓以下代碼能順利跑起來:
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;
}
除了上述例子中顯示的,還要支持有返回值的函數(shù)和沒返回值的函數(shù),以及有0個(gè)、1個(gè)、2個(gè)、……、MAX 個(gè)參數(shù)的函數(shù),參數(shù)類型無限制。最后實(shí)現(xiàn)的 Function 對象僅僅可以執(zhí)行就好。(至于是否可拷貝、是否可判斷相等 等問題,都是小事,本文暫不考慮。)最后,Bind 概念也不在本文討論范圍之內(nèi)。
對于這個(gè)問題,我們一開始考慮的可能是怎樣統(tǒng)一三種不同形式。有兩個(gè)選擇,第一,使用 C++ 的多態(tài)機(jī)制,最后統(tǒng)一到基類指針的類型;第二,允許類內(nèi)部有冗余變量以及必要的 Flag,用于判斷是哪種形式的函數(shù),要如何執(zhí)行。這樣看起來,第一種方案比第二種爽一點(diǎn)。于是,最初想到的實(shí)現(xiàn)有可能是這樣的:
先定義一個(gè)虛基類:
template <typename R>
class FunctionBase0
{
public:
virtual R Invoke() = 0;
virtual ~FunctionBase0() {}
};
然后實(shí)現(xiàn)一個(gè)普通函數(shù)/仿函數(shù)的版本:
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;
};
這里需要說明的是,如果是普通函數(shù),T會被特化成 R() 或者 R (&)() 或者 R(*)(),取決于使用的時(shí)候傳入 fun 還是傳入 &fun。所以不必另外實(shí)現(xiàn)針對 R(*)() 的版本。Loki (姑且就以作品名稱乎 Loki 的作者吧,他那個(gè)真名實(shí)在是太長)在他的書中稱之為“做一個(gè),送一個(gè)”。不過對于他書中所說的,我有一個(gè)疑惑。Loki 說傳入 fun,模版參數(shù) T 會被特化成 R (&)(),于是一切順利。可是我在操作過程中發(fā)現(xiàn) T 一直被特化成 R (),于是上述 class 中的 m_Fun 被認(rèn)為是成員函數(shù)而不是成員變量。不知道是為什么,有知道者請不吝指教哈。因?yàn)橐陨显颍疚闹形乙恢庇?&fun 的形式對待普通函數(shù)。
再實(shí)現(xiàn)一個(gè)成員函數(shù)的版本:
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;
};
最后是一個(gè)包裝類。如果你可以接受 Function<int> 表示 int(), Function<int, int> 表示 int (int),…,那么這里沒有多少技巧可言。boost 的那個(gè) function 使用的是函數(shù)簽名作為模版參數(shù),即 Function<int()>,F(xiàn)unction<int (int)> 等形式。如果不太研究語法,可能會像我一樣,一開始會對尖括號里的 int (int) 之類的玩意兒不太熟悉,覺得很牛逼。可是了解了以后,不過是個(gè)函數(shù)類型而已,沒什么大不了的。Loki 的 Functor 的使用方式是 Functor<int, TYPELIST_0()>,F(xiàn)unctor<int, TYPELIST_1(int)>。其中第一個(gè)模版參數(shù)始終是返回值,第二個(gè)模版參數(shù)是參數(shù)類型列表,Loki 使用了他創(chuàng)造的玩意兒 TypeList 使得所有函數(shù)參數(shù)只占一個(gè)坑,這在等下的支持多參數(shù)的擴(kuò)展中能夠帶來一些美觀。我比較喜歡 boost 的使用方式,讓使用者直接以語言規(guī)定的形式填入函數(shù)簽名,而不是一些額外的約定(“第一個(gè)模版參數(shù)表示返回值”,“第二個(gè)到最后的模版參數(shù)表示參數(shù)”,“第二個(gè)模版參數(shù)以 TypeList 形式表示函數(shù)參數(shù)”等)。
為了達(dá)到這個(gè)目標(biāo),我們要玩一些偏特化技巧。關(guān)于偏特化,我一直以來的膚淺認(rèn)識都是錯(cuò)誤的。我原以為,對于模版類:
template <typename T0, typename T1>
class Foo;
我如果特化其中一個(gè)參數(shù) T1:
template <typename T0>
class Foo<T0, int>
{
} 我以為只有這樣才叫偏特化,以為偏特化的過程總是減少模版參數(shù)的。而實(shí)際上,只要用某個(gè)/些類型占據(jù)原始模版參數(shù)的位置,就可以了。比如,對于上述 Foo,我可以特化一個(gè) class<T0, std::map<U0, U1>>,消去一個(gè) T1,而新增 U0、U1:
template <typename T0, typename U0, typename U1>
class Foo<T0, std::map<U0, U1>>
{
} 原來 T1 的位置被 std::map<U0, U1> 占據(jù)了,這也是偏特化。當(dāng)然最后的模版參數(shù)數(shù)量也可以不變,如:
template <typename T0, typename U>
class Foo<T0, std::vector<U>>
{
} 以及
template <typename T0, typename U>
class Foo<T0, U*>
{
} 其中后者是實(shí)現(xiàn)類型萃取的主要方式。只要特化以后,這個(gè)類依然帶有至少一個(gè)模版參數(shù),就是偏特化。如果最后產(chǎn)生了 template<> 的形式,那就是完全特化。
回到我們剛才的主題,我們要提供給用戶的是這樣一個(gè)類:
template <typename Signature>
class Function;
其中參數(shù) Signature 會被實(shí)際的函數(shù)類型所特化。但是我們只知道整體的一個(gè) Signature 并沒有用,我們必須知道被分解開來的返回值類型、參數(shù)類型。于是,引入一個(gè)偏特化版本:
template <typename R>
class Function<R ()>
這里使用 R () 特化原始的 Signature,引入一個(gè)新的參數(shù) R。于是返回值類型 R 就被萃取出來了。實(shí)現(xiàn)如下:
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;
};
如果對上面說的“普通函數(shù)的使用方式必須是函數(shù)指針而不是函數(shù)本身”耿耿于懷,可以再引入一個(gè)的構(gòu)造函數(shù):
typedef R (FunctionType)();
Function(const FunctionType &fun)
: m_pFunBase(new Function0<R, FunctionType &>(fun))
{
}
這里 FunctionType 是 R(&)() 類型,強(qiáng)制使用它來特化 Function0 中的 T。該構(gòu)造函數(shù)在重載決議中會取得優(yōu)先權(quán)從而使普通函數(shù)本身的傳入成為可能。不過,以函數(shù)本身形式傳入的普通函數(shù)會喪失一些特性,比如 Function<int()> 只能接受 int() 類型的普通函數(shù)而不能接受 char () 型的普通函數(shù),因?yàn)檫@種情況下不會走我們剛才新定義的構(gòu)造函數(shù)。
還有一種做法,就是針對全局函數(shù),強(qiáng)制特化出模版參數(shù)為其引用類型的類。定義如下元函數(shù):
template <typename Signature>
struct FunctionTraits
{
typedef Signature ParamType;
};
template <typename RetType>
struct FunctionTraits<RetType ()>
{
typedef RetType (&ParamType)();
};
然后構(gòu)造函數(shù)改為:
template <typename T>
Function(const T &fun)
: m_pFunBase(new Function0<R, typename FunctionTraits<T>::ParamType>(fun))
{
} 用以上方法,所有的特性都不會丟失。
到這兒,我們的 Function 已經(jīng)可以小試牛刀了:
Function<int ()> f1(&intfun0);
Function<int ()> f1_(intfun0);
Function<int ()> f2(intfunctor0);
Function<int ()> f3(&test, &Test::intmem0); f1();
f1_();
f2();
f3();
上面這段代碼已經(jīng)能夠正常運(yùn)行了。
來,繼續(xù)做一個(gè),送一個(gè)。下面的代碼居然也能跑(voidfun0、voidfunctor0、Test::voidmem0類似int版本定義):
Function<void ()> f4(&voidfun0);
Function<void ()> f4_(voidfun0);
Function<void ()> f5(voidfunctor0);
Function<void ()> f6(&test, &Test::voidmem0); f4();
f4_();
f5();
f6();
這說明了,在類里面寫一個(gè)返回值為該類型的函數(shù),并在里面寫下 return XXX; 然后以 void 為模版參數(shù)傳入該模版類,是符合語法的。驗(yàn)證一下:
template <typename T>
class Foo
{
public:
T Bar()
{
printf("%s invoked\n", __FUNCTION__);
return T();
}
};
int main()
{
Foo<void> f1;
f1.Bar();
Foo<int> f2;
int i = f2.Bar();
return 0;
}
運(yùn)行結(jié)果:
Foo<void>::Bar invoked
Foo<int>::Bar invoked
到此為止,我們已經(jīng)實(shí)現(xiàn)了 0 個(gè)參數(shù)的函數(shù)支持,也即 R () 類型的所有函數(shù)的支持。接下來還要實(shí)現(xiàn)對具有 1 個(gè)、2 個(gè)、3 個(gè)直至任意有限個(gè)參數(shù)的函數(shù)支持。也許您也發(fā)現(xiàn)了,接下來的工作可以是體力活,我們可以照葫蘆畫瓢,搞出一堆 FunctionBaseN、FunctionN、MemberFunctionN,并在最后的 Function 中再實(shí)現(xiàn) N 個(gè)偏特化版本。是,不錯(cuò),大致上原理就是這樣。限于篇幅,我想暫時(shí)寫到這里,下篇將繼續(xù)談?wù)労辍ypeList,以及怎樣少花點(diǎn)力氣實(shí)現(xiàn)其余 N 個(gè)版本。最終達(dá)到的效果是,只要改一個(gè)宏定義,就可以提高參數(shù)上限。
在本文所涉及的內(nèi)容中,我比較糾結(jié)的是,可否在不用多態(tài)機(jī)制的情況下達(dá)到比較優(yōu)雅的形式統(tǒng)一?
歡迎討論。
posted on 2011-01-16 22:17
溪流 閱讀(7302)
評論(55) 編輯 收藏 引用 所屬分類:
C++