前言
今日的C++不再是個單純的“帶類的C”語言,它已經發展成為一個多種次語言所組成的語言集合,其中泛型程式設計與基于它的STL是C++發展中最為出彩的那部分。在面向對象C++程式設計中,多态是OO三大特性之一,這種多态稱為運作期多态,也稱為動态多态;在泛型程式設計中,多态基于template(模闆)的具現化與函數的重載解析,這種多态在編譯期進行,是以稱為編譯期多态或靜态多态。在本文中,我們将了解:
- 什麼是運作期多态
- 什麼是編譯期多态
- 它們的優缺點在哪
運作期多态
運作期多态的設計思想要歸結到類繼承體系的設計上去。對于有相關功能的對象集合,我們總希望能夠抽象出它們共有的功能集合,在基類中将這些功能聲明為虛接口(虛函數),然後由子類繼承基類去重寫這些虛接口,以實作子類特有的具體功能。典型地我們會舉下面這個例子:
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5iN4EmZ3EDOjNzY4AjN4ADZhZ2YiJmNhlDZiFTMyUjNy8CX0JXZ252bj91Ztl2Lc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
class Animal
{
public :
virtual void shout() = 0;
};
class Dog :public Animal
{
public:
virtual void shout(){ cout << "汪汪!"<
};
class Cat :public Animal
{
public:
virtual void shout(){ cout << "喵喵~"<
};
class Bird : public Animal
{
public:
virtual void shout(){ cout << "叽喳!"<
};
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()
{
vectoranims;
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 << "汪汪!"<
};
class Cat
{
public:
void shout(){ cout << "喵喵~"<
};
class Bird
{
public:
void shout(){ cout << "叽喳!"<
};
template
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是哪個具體類型的接口。不同的推斷結果調用不同的函數,這就是編譯器多态。這類似于重載函數在編譯器進行推導,以确定哪一個函數被調用。
運作期多态與編譯期多态優缺點分析
運作期多态優點
- OO設計中重要的特性,對客觀世界直覺認識。
- 能夠處理同一個繼承體系下的異質類集合。
運作期多态缺點
- 運作期間進行虛函數綁定,提高了程式運作開銷。
- 龐大的類繼承層次,對接口的修改易影響類繼承層次。
- 由于虛函數在運作期在确定,是以編譯器無法對虛函數進行優化。
- 虛表指針增大了對象體積,類也多了一張虛函數表,當然,這是理所應當值得付出的資源消耗,列為缺點有點勉強。編譯期多态優點
- 它帶來了泛型程式設計的概念,使得C++擁有泛型程式設計與STL這樣的強大武器。
- 在編譯器完成多态,提高運作期效率。
- 具有很強的适配性與松耦合性,對于特殊類型可由模闆偏特化、全特化來處理。
編譯期多态缺點
- 程式可讀性降低,代碼調試帶來困難。
- 無法實作模闆的分離編譯,當工程很大時,編譯時間不可小觑。
- 無法處理異質對象集合。
回到頂部
關于顯式接口與隐式接口
所謂的顯式接口是指類繼承層次中定義的接口或是某個具體類提供的接口,總而言之,我們能夠在源代碼中找到這個接口.顯式接口以函數簽名為中心,例如
void AnimalShot(Animal & anim)
{
anim.shout();
}
我們稱shout為一個顯式接口。在運作期多态中的接口皆為顯式接口。
而對模闆參數而言,接口是隐式的,奠基于有效表達式。例如:
template
void AnimalShot(T & anim)
{
anim.shout();
}
對于anim來說,必須支援哪一種接口,要由模闆參數執行于anim身上的操作來決定,在上面這個例子中,T必須支援shout()操作,那麼shout就是T的一個隐式接口。
更多技術支援:私信我