今日的C++再也不是個單純的「帶類的C」語言,它已經發展成爲一個多種次語言所組成的語言集合,其中泛型編程與基於它的STL是C++發展中最爲出彩的那部分。在面向對象C++編程中,多態是OO三大特性之一,這種多態稱爲運行期多態,也稱爲動態多態;在泛型編程中,多態基於template(模板)的具現化與函數的重載解析,這種多態在編譯期進行,所以稱爲編譯期多態或靜態多態。在本文中,咱們將瞭解:html
運行期多態的設計思想要歸結到類繼承體系的設計上去。對於有相關功能的對象集合,咱們總但願可以抽象出它們共有的功能集合,在基類中將這些功能聲明爲虛接口(虛函數),而後由子類繼承基類去重寫這些虛接口,以實現子類特有的具體功能。典型地咱們會舉下面這個例子:
編程
class Animal { public : virtual void shout() = 0; }; class Dog :public Animal { public: virtual void shout(){ cout << "汪汪!"<<endl; } }; class Cat :public Animal { public: virtual void shout(){ cout << "喵喵~"<<endl; } }; class Bird : public Animal { public: virtual void shout(){ cout << "嘰喳!"<<endl; } }; int main() { Animal * anim1 = new Dog; Animal * anim2 = new Cat; Animal * anim3 = new Bird; //藉由指針(或引用)調用的接口,在運行期肯定指針(或引用)所指對象的真正類型,調用該類型對應的接口 anim1->shout(); anim2->shout(); anim3->shout(); //delete 對象 ... return 0; }
運行期多態的實現依賴於虛函數機制。當某個類聲明瞭虛函數時,編譯器將爲該類對象安插一個虛函數表指針,併爲該類設置一張惟一的虛函數表,虛函數表中存放的是該類虛函數地址。運行期間經過虛函數表指針與虛函數表去肯定該類虛函數的真正實現。函數
運行期多態的優點還在於它使處理異質對象集合稱爲可能:優化
//咱們有個動物園,裏面有一堆動物 int main() { vector<Animal*>anims; Animal * anim1 = new Dog; Animal * anim2 = new Cat; Animal * anim3 = new Bird; Animal * anim4 = new Dog; Animal * anim5 = new Cat; Animal * anim6 = new Bird; //處理異質類集合 anims.push_back(anim1); anims.push_back(anim2); anims.push_back(anim3); anims.push_back(anim4); anims.push_back(anim5); anims.push_back(anim6); for (auto & i : anims) { i->shout(); } //delete對象 //... return 0; }
總結:運行期多態經過虛函數發生於運行期設計
對模板參數而言,多態是經過模板具現化和函數重載解析實現的。以不一樣的模板參數具現化致使調用不一樣的函數,這就是所謂的編譯期多態。
相比較於運行期多態,實現編譯期多態的類之間並不須要成爲一個繼承體系,它們之間能夠沒有什麼關係,但約束是它們都有相同的隱式接口。咱們將上面的例子改寫爲:指針
class Animal { public : void shout() { cout << "發出動物的叫聲" << endl; }; }; class Dog { public: void shout(){ cout << "汪汪!"<<endl; } }; class Cat { public: void shout(){ cout << "喵喵~"<<endl; } }; class Bird { public: void shout(){ cout << "嘰喳!"<<endl; } }; template <typename T> void animalShout(T & t) { t.shout(); } int main() { Animal anim; Dog dog; Cat cat; Bird bird; animalShout(anim); animalShout(dog); animalShout(cat); animalShout(bird); getchar(); }
在編譯以前,函數模板中t.shout()調用的是哪一個接口並不肯定。在編譯期間,編譯器推斷出模板參數,所以肯定調用的shout是哪一個具體類型的接口。不一樣的推斷結果調用不一樣的函數,這就是編譯器多態。這相似於重載函數在編譯器進行推導,以肯定哪個函數被調用。調試
虛表指針增大了對象體積,類也多了一張虛函數表,固然,這是理所應當值得付出的資源消耗,列爲缺點有點勉強。code
具備很強的適配性與鬆耦合性,對於特殊類型可由模板偏特化、全特化來處理。htm
所謂的顯式接口是指類繼承層次中定義的接口或是某個具體類提供的接口,總而言之,咱們可以在源代碼中找到這個接口.顯式接口以函數簽名爲中心,例如對象
void AnimalShot(Animal & anim) { anim.shout(); }
咱們稱shout爲一個顯式接口。在運行期多態中的接口皆爲顯式接口。
而對模板參數而言,接口是隱式的,奠定於有效表達式。例如:
template <typename T> void AnimalShot(T & anim) { anim.shout(); }
對於anim來講,必須支持哪種接口,要由模板參數執行於anim身上的操做來決定,在上面這個例子中,T必須支持shout()操做,那麼shout就是T的一個隱式接口。