C++泛型與多態(tài)(3):類模板特化

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ù)式編程解決這類問題的兩種武器:

  1. 模式匹配
  2. 遞歸

類模板特化亮航,也是同樣的思路:

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); 

雖然fg是兩個不同的函數(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é)一下解決問題的整個思路:

  1. 每個被mock函數(shù),都要有一個自己的hook函數(shù)雳锋;
  2. 每個hook函數(shù)黄绩,必須和原函數(shù)的原型完全一樣;
  3. 由于函數(shù)原型存在無窮個種類,所以我們必須借助模板玷过,按需在編譯時生成hook資源爽丹;
  4. 不同函數(shù)可能屬于同一類型,所以需要給同一類型的的函數(shù)預(yù)留多份 hook資源;
  5. 有了多份資源筑煮,就需要按照一定的算法進行管理。于是為資源模板引入SEQ參數(shù)粤蝎;資源的生成和管理算法咆瘟,都是以SEQ來區(qū)分同一類型函數(shù)的多份hook 資源。
  6. 由于每個函數(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é)

本文介紹了類模版特化的語義榜跌,并通過幾個例子介紹了通過它解決問題的方式闪唆。

從中我們可以看出,類模版特化要表達的語義為:

函數(shù)選擇優(yōu)先級
函數(shù)選擇優(yōu)先級

在隨后的文章中钓葫,會繼續(xù)介紹與泛型有關(guān)的其它多態(tài)悄蕾。

相關(guān)鏈接

深入理解C++泛型(1):基礎(chǔ)篇

深入理解C++泛型(2):模板特化

深入理解C++泛型(4):Duck Typing

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市础浮,隨后出現(xiàn)的幾起案子帆调,更是在濱河造成了極大的恐慌,老刑警劉巖豆同,帶你破解...
    沈念sama閱讀 211,743評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件番刊,死亡現(xiàn)場離奇詭異,居然都是意外死亡影锈,警方通過查閱死者的電腦和手機芹务,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來精居,“玉大人锄禽,你說我怎么就攤上這事⊙プ耍” “怎么了沃但?”我有些...
    開封第一講書人閱讀 157,285評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長佛吓。 經(jīng)常有香客問我宵晚,道長垂攘,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,485評論 1 283
  • 正文 為了忘掉前任淤刃,我火速辦了婚禮晒他,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘逸贾。我一直安慰自己陨仅,他們只是感情好,可當我...
    茶點故事閱讀 65,581評論 6 386
  • 文/花漫 我一把揭開白布铝侵。 她就那樣靜靜地躺著灼伤,像睡著了一般。 火紅的嫁衣襯著肌膚如雪咪鲜。 梳的紋絲不亂的頭發(fā)上狐赡,一...
    開封第一講書人閱讀 49,821評論 1 290
  • 那天,我揣著相機與錄音疟丙,去河邊找鬼颖侄。 笑死,一個胖子當著我的面吹牛享郊,可吹牛的內(nèi)容都是我干的览祖。 我是一名探鬼主播,決...
    沈念sama閱讀 38,960評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼拂蝎,長吁一口氣:“原來是場噩夢啊……” “哼穴墅!你這毒婦竟也來了惶室?” 一聲冷哼從身側(cè)響起温自,我...
    開封第一講書人閱讀 37,719評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎皇钞,沒想到半個月后悼泌,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,186評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡夹界,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,516評論 2 327
  • 正文 我和宋清朗相戀三年馆里,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片可柿。...
    茶點故事閱讀 38,650評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡鸠踪,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出复斥,到底是詐尸還是另有隱情营密,我是刑警寧澤,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布目锭,位于F島的核電站评汰,受9級特大地震影響纷捞,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜被去,卻給世界環(huán)境...
    茶點故事閱讀 39,936評論 3 313
  • 文/蒙蒙 一主儡、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧惨缆,春花似錦糜值、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,757評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至畅蹂,卻和暖如春健无,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背液斜。 一陣腳步聲響...
    開封第一講書人閱讀 31,991評論 1 266
  • 我被黑心中介騙來泰國打工累贤, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人少漆。 一個月前我還...
    沈念sama閱讀 46,370評論 2 360
  • 正文 我出身青樓臼膏,卻偏偏與公主長得像,于是被迫代替她去往敵國和親示损。 傳聞我的和親對象是個殘疾皇子渗磅,可洞房花燭夜當晚...
    茶點故事閱讀 43,527評論 2 349

推薦閱讀更多精彩內(nèi)容