繼承關係在動多型中的體現
一、動態聯編
1.1 概念
動態聯編是指在執行時才將函式實現和函式呼叫關聯,因此也叫執行時繫結或者晚繫結。對函式的選擇不是基於指標(引用),而是基於物件型別,不同的物件型別將做出不同的編譯結果。C++中一般情況下聯編也是靜態聯編,但是一旦涉及到多型和虛擬函式就必須要使用動態聯編了。
假設我們什麼都不知道,基類、派生類,虛擬函式、非虛擬函式,組合起來一共有4種:
基類(虛)、派生類(虛)√覆蓋
基類(虛)、派生類(非虛)√覆蓋
基類(非虛)、派生類(虛)×
基類(非虛)、派生類(非虛)√ 隱藏
隱藏:父類中和子類中同名的成員被隱藏。
覆蓋:子類中重寫父類的相同(同返回值,同名,同參數列表)的虛成員方法。
值得注意的是:基類實現為虛擬函式,子類的相應函式也會被放入vtable中,是否加上virtual關鍵字修飾就變得無關緊要。1、2說的是一個意思。4說的是隱藏。單獨將3拿出來討論為什麼是錯的:如果基類無虛擬函式,派生類有虛擬函式,基類指標指向子類物件,記憶體佈局是這樣的——
基類指標指向子類物件,無法呼叫子類的虛擬函式此時還不是痛點,可能就故意這麼的設計;更要命的是,如果派生類物件位於堆上,一旦以基類指標的方式delete派生類,程式將崩潰:因為基類指標指向2號位置而不是1號。
1.2 舉例
示例程式碼說明:基類以virtual關鍵字修飾show()函式,Base::show()就會被放入vtable中;派生類繼承基類,也會繼承vtable,但是派生類也有Derive::show(),會發生覆蓋。程式碼執行時,最終呼叫的是派生類的成員方法。
#include<iostream> using namespace std; class Base { public: virtual void show() { cout << "void Base::show()" << endl; } ~Base() { cout << "Base::~Base()" << endl; } private: int _a; }; class Derive : public Base { public: void show() { cout << "void Derive::show()" << endl; } ~Derive() { cout << "Derive::~Derive()" << endl; } private: int _b; }; int main() { Base *p = new Derive; p->show(); delete p; return 0; }
二、 虛解構函式
2.1 解構函式
解構函式(destructor) 與建構函式相反,當物件結束其生命週期,如物件所在的函式已呼叫完畢時,系統自動執行解構函式。解構函式往往用來做“清理善後” 的工作(例如在建立物件時用new開闢了一片記憶體空間,delete會自動呼叫解構函式後釋放記憶體)。
2.2 為什麼實現為虛的
一個基類指標刪除一個派生類物件(增加了指標成員)時,應該去呼叫派生類的解構函式而不是基類的虛擬函式。避免記憶體洩漏。
如果不將基類寫成虛擬函式,那麼通過基類指標刪除派生類物件時,就會形成記憶體洩漏。
三、 virtual和形參預設值問題
#include<iostream>
using namespace std;
class Base
{
public:
virtual void func(int val = 1);
virtual void test();
};
class Derive : public Base
{
public:
void func(int val = 0);
};
void Base::func(int val)
{
cout << "Base::" << val << endl;
}
void Base::test()
{
func();
}
void Derive::func(int val)
{
cout << "Derive::" << val << endl;
}
int main()
{
Base *p = new Derive;
p->test();
return 0;
}
對於圖5 所示的現象,《Effective C++》 條款37是這樣解釋的:絕不重新定義繼承而來的預設引數值。virtual函式是動態繫結,而預設引數值卻是靜態繫結。