C++
的類沒有重載,所以類只能依靠特化來實現(xiàn)多態(tài)尸折。
例子:斐波那契數(shù)列
斐波那契數(shù)列(Fibonacci Number
)是一個經(jīng)典的數(shù)學(xué)問題啰脚。解決這類問題,是FP
的強項实夹。下面是一個Haskell
的實現(xiàn)橄浓。
fib :: Integer -> Integer
fib 0 = 0
fib 1 = 1
fib n = fib (n-1) + fib (n-2)
從其實現(xiàn)可以看出,函數(shù)式編程解決這類問題的兩種武器:
- 模式匹配
- 遞歸
而類模板特化亮航,也是同樣的思路:
template <U64 N>
struct Fib
{
static const U64 value = Fib<N-1>::value + Fib<N-2>::value;
};
template <>
struct Fib <1>
{
static const U64 value = 1;
};
template <>
struct Fib <0>
{
static const U64 value = 0;
};
例子:數(shù)列求和
我們再看另外一個例子:對任意一個整數(shù)數(shù)列求和荸实。在不使用fold
的情況下,Haskell
的實現(xiàn)方式如下:
sum :: [Int] -> Int
sum [] = 0
sum (x:xs) = x + (add xs)
其中缴淋,整數(shù)數(shù)列的長度不確定准给。如果用C++
的模版來實現(xiàn),則需要使用C++11
的變參模版:
template <int... T_ELEMS>
struct SUM;
template <int T_HEAD, int... T_TAIL>
struct SUM <T_HEAD, T_TAIL...>
{
static const int value = T_HEAD + SUM<T_TAIL...>::value;
};
template <>
struct SUM <>
{
static const int value = 0;
};
從這兩個例子可以看出重抖,C++
模版解決問題的思路和FP
是完全一樣露氮。正是因為模板具備了這樣的能力,所以它是圖靈完備的钟沛。
因而畔规,我們也可以清晰的看出,模版的特化像模式匹配一樣恨统,是一種ad-hoc多態(tài)叁扫。
給出這兩個例子,僅僅是為了展示模版特化的本質(zhì)延欠。在實際應(yīng)用中陌兑,肯定不是為了例子中的計算數(shù)值。而是通過編譯時和運行時的結(jié)合由捎,為程序員提供強大的武器兔综。
動靜結(jié)合:mockcpp
mockcpp
支持對于自由函數(shù)的mock
。其中一種實現(xiàn)技術(shù)稱之為 Api Hook
: 為了能夠mock
一個自由函數(shù)狞玛,就需要對自由函數(shù)的調(diào)用進行攔截软驰,并將控制權(quán)轉(zhuǎn)交給mock
框架。
而攔截技術(shù)的關(guān)鍵心肪,就是將被mock
自由函數(shù)的地址替換為mockcpp
為之生成的hook
锭亏。而每個被mock
的自由函數(shù)都有一個對應(yīng)的hook
。
這種一一映射關(guān)系硬鞍,就可以通過模板特化的技術(shù)來解決慧瘤。我們先定義一個特化模板ApiHookFunctor
戴已。其中,我們僅僅列出了針對兩參數(shù)函數(shù)的特化模板锅减。針對帶有其它參數(shù)個數(shù)函數(shù)的特化模板的算法完全一樣糖儡,這里就不再列出。
// 基礎(chǔ)模板
template <typename F, unsigned int SEQ>
struct ApiHookFunctor
{
};
// 2 個參數(shù)函數(shù)的特化
template <typename R, typename T1, typename T2, unsigned int SEQ>
struct ApiHookFunctor<R (T1, T2), SEQ>
{
private:
typedef R FUNC (T1, T2);
// 2 個參數(shù)的 hook
static R hook(T1 p1, T2 p2)
{
// 控制權(quán)轉(zhuǎn)交給 mockcpp 框架
return GlobalMockObject::instance.invoke<R>(apiAddress)(p1, p2);
}
public:
static void* getApiHook(FUNC* api)
{
if(not appliedBy(api)) return 0;
++refCount;
return getHook();
}
static void* applyApiHook(FUNC* api)
{
if(apiAddress != 0) return 0;
apiAddress = reinterpret_cast<void*>(api);
refCount = 1;
return getHook();
}
static bool freeApiHook(void* hook)
{
if(getHook() != hook) return false;
freeHook();
return true;
}
private:
static bool appliedBy(FUNC* api)
{
return apiAddress == reinterpret_cast<void*>(api);
}
static void* getHook()
{
return reinterpret_cast<void*>(hook);
}
static void freeHook()
{
if(--refCount == 0) apiAddress = 0;
}
private:
static void* apiAddress; // 原函數(shù)地址
static unsigned int refCount; // 引用計數(shù)
};
// ...
如果兩個函數(shù)如果有相同的函數(shù)原型怔匣,比如:
// 這兩個函數(shù)具有相同的類型
int f(int, double);
int g(int, double);
雖然f
和 g
是兩個不同的函數(shù)握联,但它們的類型是相同的。但之前我們已經(jīng)介紹過每瞒,Api Hook
設(shè)計的關(guān)鍵在于金闽,要為每一個需要mock
的自由函數(shù)都應(yīng)該生成一個hook
。所以剿骨,上述模板必須能夠為同一類型的函數(shù)預(yù)先分配多個hook
代芜,以供多個同一類型的不同函數(shù)進行動態(tài)分配。
而模板參數(shù)SEQ
正是為了區(qū)分同一類型函數(shù)的多個hook
懦砂。每一個經(jīng)過實例化之后的模板類都是一個可動態(tài)分配的資源蜒犯,因為一個這樣的模板類對應(yīng)了一個hook
组橄。
而每個模板的靜態(tài)數(shù)據(jù)apiAddress
用來紀錄當前模板類被分配給了哪個函數(shù), 其內(nèi)容為函數(shù)的原始地址荞膘。當一個自由函數(shù)被多次mock 時
,refCount
用來紀錄mock
的次數(shù)玉工。當其值為0
時,此模板類將會被釋放羽资,以供后續(xù)的分配。
ApiHookFunctor
用來生成單個資源遵班,而下面的模板:ApiHookGenerator
則用來生成和管理所有資源屠升。這是一個動態(tài)和靜態(tài)相結(jié)合,進行資源分配和釋放的典型處理手法狭郑。其中,編譯時(靜態(tài))生成所有可使用的資源腹暖,以及相關(guān)的算法代碼;而運行時(動態(tài))則根據(jù)靜態(tài)生成的算法翰萨,動態(tài)的管理靜態(tài)生成的資源脏答。如下:
template <typename F, unsigned int SEQ>
struct ApiHookGenerator
{
static void* findApiHook(F* api)
{
void* hook;
(hook = ApiHookFunctor<F, SEQ>::getApiHook(api))
|| (hook = ApiHookGenerator<F, SEQ-1>::findApiHook(api));
return hook;
}
static void* applyApiHook(F* api)
{
void* hook;
(hook = ApiHookFunctor<F, SEQ>::applyApiHook(api))
|| (hook = ApiHookGenerator<F, SEQ-1>::applyApiHook(api));
return hook;
}
static bool freeApiHook(void* hook)
{
return (ApiHookFunctor<F, SEQ>::freeApiHook(hook))
|| (ApiHookGenerator<F, SEQ-1>::freeApiHook(hook));
}
};
template <typename F>
struct ApiHookGenerator<F, 0>
{
static void* findApiHook(F* api) { return 0; }
static void* applyApiHook(F* api) { return 0; }
static bool freeApiHook(void* hook) { return true; }
};
有了ApiHookGenerator
這個資源管理器之后,用戶就可以通過如下方式來使用:
template <typename F>
struct ParameterizedApiHookHolder
: public ApiHookHolder
{
const static unsigned int MAX_RESOURCES = 10;
ParameterizedApiHookHolder(F* api)
{
(m_hook = ApiHookGenerator<F, MAX_RESOURCES>::findApiHook(api))
|| (m_hook = ApiHookGenerator<F, MAX_RESOURCES>::appyApiHook(api));
}
void * getApiHook() const { return m_hook; }
~ParameterizedApiHookHolder()
{
ApiHookGenerator<F, MAX_RESOURCES>::freeApiHook(m_hook);
}
private:
void* m_hook;
};
最后亩鬼,為了有助于進一步理解上述代碼殖告,我們來總結(jié)一下解決問題的整個思路:
- 每個被
mock
函數(shù),都要有一個自己的hook
函數(shù)雳锋; - 每個
hook
函數(shù)黄绩,必須和原函數(shù)的原型完全一樣; - 由于函數(shù)原型存在無窮個種類,所以我們必須借助模板玷过,按需在編譯時生成
hook
資源爽丹; - 不同函數(shù)可能屬于同一類型,所以需要給同一類型的的函數(shù)預(yù)留多份
hook
資源; - 有了多份資源筑煮,就需要按照一定的算法進行管理。于是為資源模板引入
SEQ
參數(shù)粤蝎;資源的生成和管理算法咆瘟,都是以SEQ
來區(qū)分同一類型函數(shù)的多份hook
資源。 - 由于每個函數(shù)的運行時地址都是唯一的诽里,所以袒餐,使用“函數(shù)地址”作為
key
值,在運行時谤狡,按照靜態(tài)生成的算法培遵,動態(tài)地分配編譯時按需生成的hook
資源掉伏。
動靜結(jié)合:組合
Transaction DSL
中可以這樣描述一個序列:
__sequential
( __asyn(Action1)
, __asyn(Action2)
, __sync(Action3))
__sequantial
里包含的Action
完全由用戶根據(jù)自己的需要填寫,因而數(shù)量不確定。
而__sequential
這個宏背后直接對應(yīng)的就是變參模版:
#define __sequential(...) \
SEQUENTIAL__< __VA_ARGS__ >
而模版SEQUENTIAL__
的實現(xiàn)如下:
template <typename... T_ACTIONS>
struct GenericSequential;
template <typename T_HEAD, typename... T_TAIL>
struct GenericSequential<T_HEAD, T_TAIL...> : GenericSequential<T_TAIL...>
{
protected:
void init()
{
GenericSequential<T_TAIL...>::pushBackAction(action);
GenericSequential<T_TAIL...>::init();
}
private:
details::LINKED__< T_HEAD > action;
};
template <>
struct GenericSequential<> : SchedSequentialAction
{
protected:
void init() {}
};
template <typename... T_ACTIONS>
struct SEQUENTIAL__ : GenericSequential<T_ACTIONS...>
{
SEQUENTIAL__()
{
GenericSequential<T_ACTIONS...>::init();
}
};
這里例子展示的是费彼,對于不確定數(shù)量的被組合元素,通過變參模版在編譯時完成對于類型的靜態(tài)組合喘鸟。然后運行時橡卤,讓模版類里的函數(shù)完成動態(tài)組合。
小結(jié)
本文介紹了類模版特化的語義榜跌,并通過幾個例子介紹了通過它解決問題的方式闪唆。
從中我們可以看出,類模版特化要表達的語義為:
在隨后的文章中钓葫,會繼續(xù)介紹與泛型有關(guān)的其它多態(tài)悄蕾。