C++泛型與多態(tài)(1):基礎(chǔ)篇

C++的泛型編程是一種非常強(qiáng)大的武器拘央。但它看上去復(fù)雜的語法斋日,以及背后不明的原理渴杆,一直讓很多程序員望而生畏林艘。很多即便已經(jīng)使用了C++很久的程序員也僅僅敢觸碰其非常表象的部分盖奈。還有一些大膽而莽撞的程序員則對其進(jìn)行濫用,讓本來可以簡單解決的問題狐援,變成秀技場钢坦。因而,泛型技術(shù)一直得到一個(gè)不公正的名聲:奇技淫巧啥酱。

但是爹凹,泛型編程對于很多問題的高效解決是不可或缺的。它是C++最重要的元編程工具镶殷,也是很多框架制作的利器禾酱,也是組合式設(shè)計(jì)的主要手段之一。不掌握它绘趋,C++的強(qiáng)大威力將大打折扣颤陶。

我們需要深入挖掘其背后的原理及本質(zhì),這樣才能理解它陷遮,掌握它指郁,在合適的場合使用它,避免濫用和無用拷呆。

C++的泛型編程闲坎,其本質(zhì)其實(shí)非常簡單:為了做基于類型常量編譯時(shí)多態(tài)疫粥。(關(guān)于多態(tài)的目的和價(jià)值,請參閱《多態(tài)腰懂,F(xiàn)P與OO》)

C++編譯時(shí)多態(tài)由如下四種類型構(gòu)成:

  1. 模版
  2. 函數(shù)(操作符)重載
  3. 模板特化
  4. Duck Typing

我們下面一一介紹梗逮。

1. 模版:參數(shù)化多態(tài)

先看一段簡單的haskell代碼:

max :: (Ord a) => a -> a -> a
max x y = if x > y then x else y

在這個(gè)實(shí)現(xiàn)里,a是類型變量绣溜,(Ord a)表示對a約束慷彤,意思是:對于所有屬于typeclass Ord的類型a,都可以實(shí)例化這個(gè)函數(shù)怖喻。

這種用法被稱作參數(shù)化多態(tài)底哗。很顯然,參數(shù)化多態(tài)是為了避免基于類型的重復(fù)代碼锚沸。

而回到C++跋选,基于模版的實(shí)現(xiàn)則為:

template <typename T>
T max(T a, T b)
{
   return a > b ? a : b;
}

參數(shù)化多態(tài),是C++提供泛型的最初目的哗蜈。STL的各種容器和算法前标,基本上都是基于這類目的設(shè)計(jì)的。這也是最容易理解距潘,被使用最為廣泛的C++編譯時(shí)多態(tài)技術(shù)炼列。

Concept

上述haskell代碼中的約束Ord a,到C++14為止尚未支持音比,但已經(jīng)確定要在C++17中支持(被稱作Concept)俭尖。

高階類型

上述haskell例子中的多態(tài)類型屬于rank 1 type,但rank 1 type無法支持這類的代碼:

g f = (f True, f 'a')

其中洞翩,參數(shù)f作為一個(gè)函數(shù)目溉,在兩次調(diào)用時(shí)傳入的參數(shù)類型不同:分別為BoolChar。這屬于Rank 2 Type的范疇菱农。而為了支持Rank 2 Type缭付,GHC提供了一個(gè)擴(kuò)展`RankNTypes',并且要求程序員給出類型注解:

{-# LANGUAGE RankNTypes #-}

g :: (forall a. a -> a) -> (Bool, Char)
g f = (f True, f 'a')

而在C++中循未,對應(yīng)的實(shí)現(xiàn)技術(shù)為Template Template Parameter陷猫。比如:

template <typename T, typename G,
template <typename E> class Container >
struct Foo
{ 
  Container<T> tContainer; 
  Container<G> gContainer;
};

這就是參數(shù)化多態(tài)的全部:非常簡單,因而被很多語言采納和引入的妖,也被程序員廣泛的使用绣檬。

2. 函數(shù)重載:Ad-hoc多態(tài)

學(xué)習(xí)任何一門語言的第一個(gè)例子,往往都是Hello World:

std::cout << "Hello, 2016!" << std::endl;

如果我們讓程序稍微復(fù)雜一些,讓這段代碼可以對在任何一年都可以復(fù)用嫂粟,那么我們可以提供一個(gè)類似于下面的函數(shù):

void helloNewYear(unsigned int year) 
{ 
  std::cout << "Hello, " << year << "!" << std::endl;
}

在這個(gè)實(shí)現(xiàn)中娇未,對于字符串整數(shù)這兩種不同類型的數(shù)據(jù),std::cout可以用一致的方式來操作星虹。

按照多態(tài)四要素零抬,std::cout即是客戶镊讼;同一外表是操作符<<;而不同形態(tài)則是針對不同類型的不同實(shí)現(xiàn):

std::ostream& operator<<(std::ostream& os, const std::string& str);
std::ostream& operator<<(std::ostream& os, const int value);
// ... 

而編譯器會根據(jù)類型進(jìn)行匹配(這是一種簡化版本的模式匹配)平夜,從而在不同形態(tài)間進(jìn)行選擇蝶棋。

因而函數(shù)重載是一種多態(tài),而這樣的多態(tài)被稱作ad-hoc多態(tài)忽妒。

例子:雙重派發(fā)

雙重派發(fā)Double Dispatch)玩裙,是訪問者模式(Visitor Pattern)依托的的實(shí)現(xiàn)技術(shù)。

假設(shè)段直,一顆語法樹吃溅,上面有多種類型的節(jié)點(diǎn)。比如:Statement鸯檬,Expression决侈。

而一個(gè)訪問者則必須提供針對各種類型節(jié)點(diǎn)的訪問函數(shù),比如:

struct Visitor 
{ 
   virtual void visit(Statement&) = 0; 
   virtual void visit(Expression&) = 0; 
   virtual ~Visitor() {} 
}; 

所有的節(jié)點(diǎn)都需要實(shí)現(xiàn)accept方法京闰,所以它們都需要實(shí)現(xiàn)下面的接口:

struct Element 
{ 
  virtual void accept(Visitor&) = 0; 
  virtual ~Element() {} 
}; 

而每個(gè)具體的節(jié)點(diǎn)在實(shí)現(xiàn)此接口時(shí),分別調(diào)用Visitor中針對自己類型的成員函數(shù):

struct Statement: Element 
{ 
  void accept(Visitor& visitor) 
  { 
    visitor.visit(*this); 
  } 
    
  // ... 
};
 
struct Expression: Element 
{ 
  void accept(Visitor& visitor) 
  { 
    visitor.visit(*this); 
  } 
  // ... 
};

所謂雙重派發(fā)甩苛,指的正是兩個(gè)多態(tài)結(jié)構(gòu):

  1. 訪問算法通過接口accept,在運(yùn)行時(shí)將visitor派發(fā)給相應(yīng)的 Element;
  2. 具體的Element再從visitor的多個(gè)訪問函數(shù)中蹂楣,選擇歸屬于自己的那一個(gè),通過它讯蒲,將自己派發(fā)給visitor痊土。

其中,前者使用的是運(yùn)行時(shí)多態(tài)墨林,后者使用的則是函數(shù)重載赁酝。有了函數(shù)重載,就完全沒必要讓每個(gè)具體的Element重復(fù)的編寫完全相同的accept實(shí)現(xiàn)旭等。所以酌呆,我們可以將上述代碼重構(gòu)為:

template <typename T>
struct AutoDispatchElement : Element 
{ 
  void accept(Visitor& visitor)
  { 
     visitor.visit((T&)*this); 
  } 
};
 
struct Statement 
   : AutoDispatchElement<Statement>
{ 
  // ... 
};
 
struct Expression
   : AutoDispatchElement<Expression> 
{ 
  // ... 
};

并非所有重載都是為了多態(tài)

需要強(qiáng)調(diào)的是:盡管函數(shù)(操作符)重載可以用來實(shí)現(xiàn)編譯時(shí)多態(tài),但并非所有的重載都是為了多態(tài)搔耕。大多數(shù)情況下隙袁,操作符的重載都是為了提高表達(dá)力,比如:

// 讓復(fù)數(shù)可以進(jìn)行 a + b 形式的操作
Complex operator+(const Complex& lhs, const Complex& rhs); 

// 讓向量可以進(jìn)行 vector[5] 形式的操作
template <typename T>
T Vector<T>::operator[](unsigned int index); 

至于那些參數(shù)個(gè)數(shù)不同的重載函數(shù)弃榨,與多態(tài)更是沒有什么關(guān)系菩收,比如:

void f(int a) 
void f(short a, double b); 

這種樣式的重載,往往是因?yàn)殚_發(fā)人員懶得為之取一個(gè)更準(zhǔn)確的名字鲸睛,從而濫用了語言提供的重載機(jī)制娜饵,其結(jié)果反而傷害了表達(dá)力。對于這樣的重載官辈,要盡量避免箱舞。

小結(jié)

這一部分我們首先介紹了C++范型最為簡單的兩種多態(tài):以基于類型的重用為目的參數(shù)化多態(tài)和以函數(shù)重載為手段的ad-hoc多態(tài)遍坟。

在后續(xù)的文章里,會繼續(xù)介紹更為復(fù)雜的C++編譯時(shí)多態(tài)技術(shù)褐缠。

相關(guān)鏈接

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

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

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末政鼠,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子队魏,更是在濱河造成了極大的恐慌公般,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件胡桨,死亡現(xiàn)場離奇詭異官帘,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)昧谊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進(jìn)店門刽虹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人呢诬,你說我怎么就攤上這事涌哲。” “怎么了尚镰?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵阀圾,是天一觀的道長。 經(jīng)常有香客問我狗唉,道長初烘,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任分俯,我火速辦了婚禮肾筐,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘缸剪。我一直安慰自己吗铐,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布杏节。 她就那樣靜靜地躺著抓歼,像睡著了一般。 火紅的嫁衣襯著肌膚如雪拢锹。 梳的紋絲不亂的頭發(fā)上谣妻,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天,我揣著相機(jī)與錄音卒稳,去河邊找鬼蹋半。 笑死,一個(gè)胖子當(dāng)著我的面吹牛充坑,可吹牛的內(nèi)容都是我干的减江。 我是一名探鬼主播染突,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼辈灼!你這毒婦竟也來了份企?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤巡莹,失蹤者是張志新(化名)和其女友劉穎司志,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體降宅,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡骂远,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了腰根。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片激才。...
    茶點(diǎn)故事閱讀 39,696評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖额嘿,靈堂內(nèi)的尸體忽然破棺而出瘸恼,到底是詐尸還是另有隱情,我是刑警寧澤册养,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布东帅,位于F島的核電站,受9級特大地震影響捕儒,放射性物質(zhì)發(fā)生泄漏冰啃。R本人自食惡果不足惜邓夕,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一刘莹、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧焚刚,春花似錦点弯、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至碳柱,卻和暖如春捡絮,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背莲镣。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工福稳, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人瑞侮。 一個(gè)月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓的圆,卻偏偏與公主長得像鼓拧,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子越妈,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評論 2 353

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

  • object 變量可指向任何類的實(shí)例季俩,這讓你能夠創(chuàng)建可對任何數(shù)據(jù)類型進(jìn)程處理的類。然而梅掠,這種方法存在幾個(gè)嚴(yán)重的問題...
    CarlDonitz閱讀 913評論 0 5
  • 前言 人生苦多酌住,快來 Kotlin ,快速學(xué)習(xí)Kotlin瓤檐! 什么是Kotlin赂韵? Kotlin 是種靜態(tài)類型編程...
    任半生囂狂閱讀 26,201評論 9 118
  • 重新系統(tǒng)學(xué)習(xí)下C++;但是還是少了好多知識點(diǎn)挠蛉;socket祭示;unix;stl谴古;boost等质涛; C++ 教程 | 菜...
    kakukeme閱讀 19,872評論 0 50
  • 2014年的蘋果全球開發(fā)者大會(WWDC),當(dāng)Craig Federighi向全世界宣布“We have new ...
    yeshenlong520閱讀 2,289評論 0 9
  • 《U型變革》中總結(jié)了組織發(fā)展經(jīng)歷4個(gè)階段: 1.0結(jié)構(gòu)中掰担,權(quán)力位于金字塔的頂部汇陆,組織結(jié)構(gòu)是由上而下的集權(quán)式。協(xié)作是...
    敬恒教練閱讀 943評論 0 3