1. 程式人生 > >繼承關係在動多型中的體現

繼承關係在動多型中的體現

一、動態聯編

1.1 概念

動態聯編是指在執行時才將函式實現和函式呼叫關聯,因此也叫執行時繫結或者晚繫結。對函式的選擇不是基於指標(引用),而是基於物件型別,不同的物件型別將做出不同的編譯結果。C++中一般情況下聯編也是靜態聯編,但是一旦涉及到多型和虛擬函式就必須要使用動態聯編了。

假設我們什麼都不知道,基類、派生類,虛擬函式、非虛擬函式,組合起來一共有4種:

  1. 基類(虛)、派生類(虛)√覆蓋

  2. 基類(虛)、派生類(非虛)√覆蓋

  3. 基類(非虛)、派生類(虛)×

  4. 基類(非虛)、派生類(非虛)√ 隱藏

隱藏:父類中和子類中同名的成員被隱藏。 

覆蓋:子類中重寫父類的相同(同返回值,同名,同參數列表)的虛成員方法。

值得注意的是:基類實現為虛擬函式,子類的相應函式也會被放入vtable中,是否加上virtual關鍵字修飾就變得無關緊要。1、2說的是一個意思。4說的是隱藏。單獨將3拿出來討論為什麼是錯的:如果基類無虛擬函式,派生類有虛擬函式,基類指標指向子類物件,記憶體佈局是這樣的——

圖1 記憶體佈局

基類指標指向子類物件,無法呼叫子類的虛擬函式此時還不是痛點,可能就故意這麼的設計;更要命的是,如果派生類物件位於堆上,一旦以基類指標的方式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 多型在繼承中的體現(VS2017)
圖3 析構堆上的物件的流程

二、 虛解構函式 

2.1 解構函式

解構函式(destructor) 與建構函式相反,當物件結束其生命週期,如物件所在的函式已呼叫完畢時,系統自動執行解構函式。解構函式往往用來做“清理善後” 的工作(例如在建立物件時用new開闢了一片記憶體空間,delete會自動呼叫解構函式後釋放記憶體)。

2.2 為什麼實現為虛的

一個基類指標刪除一個派生類物件(增加了指標成員)時,應該去呼叫派生類的解構函式而不是基類的虛擬函式。避免記憶體洩漏

圖4-1 析構必須實現為虛擬函式的情形
圖4-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 呼叫子類的函式,繼承父類的預設值

對於圖5 所示的現象,《Effective C++》 條款37是這樣解釋的:絕不重新定義繼承而來的預設引數值。virtual函式是動態繫結,而預設引數值卻是靜態繫結。