天天看點

基類指針指向子類對象

沒有指定虛函數,  那麼它就隻能通路到類型對應的函數

基類指針就隻能通路到基類函數

子類指針就隻能通路到子類函數

要使用基類通路到子類的函數, 最符合正常的, 就是虛函數了.

當然, 你也可以使用非正常的, 比如強制轉換,   比如自己計算函數位址并調用.

正常情況,子類祖先類的析構函數都是虛拟的,這樣删除祖先類指針、子類對象的時候,可正确同時調用子類和祖先類的析構函數):

#include <iostream>
using namespace std;
 
class Base_J 
{
public:
	Base_J()
	{
		cout << "Base Created" << endl;
	}
	~Base_J()
	{
		cout << "Base Destroyed" << endl;
	}
};
 
class Derived_J : public Base_J
{
public:
	Derived_J()
	{
		cout << "Derived Created" << endl;
	}
	~Derived_J()
	{
		cout << "Derived Destroyed" << endl;
	}
};
 
int main()
{
	Base_J *pB = new Derived_J();//基類指針指向子類對象
	delete pB;
	pB = NULL;
	system("pause");
	return 0;
}
           

下面是它的運作結果:

基類指針指向子類對象

上述結果比較詭異,按理說每個建立的類中的内容都應該在不用時被銷毀,但是上述隻是銷毀了子類對象中屬于父類的部分,這是什麼原因呢?

先看一下幾個結論:

1,如果以一個基礎類指針指向一個衍生類對象(派生類對象),那麼經由該指針隻能通路基礎類定義的函數(“實函數”暫且這麼叫)( 靜态聯翩)

2,如果以一個衍生類指針指向一個基礎類對象,必須先做強制轉型動作(explicit cast),這種做法很危險,也不符合生活習慣,在程式設計上也會給程式員帶來困擾。(一般不會這麼去定義)

3,如果基礎類和衍生類定義了相同名稱的成員函數,那麼通過對象指針調用成員函數時,到底調用那個函數 要根據指針的原型來确定,而不是根據指針實際指向的對象類型确定。

虛拟函數就是為了對“如果你以一個基礎類指針指向一個衍生類對象,那麼通過該指針,你隻能通路基礎類定義的成員函數”這條規則反其道而行之的設計。

如果你預期衍生類由可能重新定義一個成員函數,那麼你就把它定義成虛拟函數( virtual )。 polymorphism就是讓處理基礎類别對象的程式代碼能夠通透的繼續适當地處理衍生類對象。

純虛拟函數: virtual void myfunc ( ) =0; 純虛拟函數不許定義其具體動作,它的存在隻是為了在衍生類鐘被重新定義。 隻要是擁有純虛拟函數的類,就是抽象類,它們是不能夠被執行個體化的( 隻能被繼承)。 如果一個繼承類沒有改寫父類中的純虛函數,那麼他也是抽象類,也不能被執行個體化。 抽象類不能被執行個體化,不過我們可以擁有指向抽象類的指針,以便于操縱各個衍生類。 虛拟函數衍生下去仍然是虛拟函數,而且還可以省略掉關鍵字“virtual”。 引自http://blog.csdn.net/taoyingzhushui/article/details/8100434。

由上述可見,程式錯誤出在:析構函數本應該設定成虛函數。否則由上述第1條可知,在父類指針指向子類對象做對象銷毀時,由于析構函數不是虛函數,則delete時,父類指針隻能調用父類自己的析構函數,這就造成了上述對象部分銷毀的錯誤狀況。

《Effective C++》條款 07 p40:為多态基類聲明virtual析構函數

《C++ Primer 5》 p527:在C++語言中,當我們使用基類的引用(或指針)調用一個虛函數時将發生動态綁定。

另外,Effective C++中有關于動态多态(運作時的多态:虛函數)和靜态多态(編譯時的多态:模闆)的說明。

現在我們将基類的析構改成虛函數試試:

class Base_J 
{
public:
	Base_J()
	{
		cout << "Base Created" << endl;
	}
	virtual ~Base_J()
	{
		cout << "Base Destroyed" << endl;
	}
};
 
           

結果便按照我們設想的執行了:

基類指針指向子類對象

派生類構造函數和析構函數的執行順序

當建立對象時,編譯系統會自動調用構造函數。當撤銷對象時,編譯系統會自動調用析構函數。當建立派生類的對象時,首先執行基類的構造函數,然後執行派生類的構造函數。當撤銷對象時,則先執行派生類的析構函數,然後再執行基類的析構函數。

《Effective C++》條款 07 p44:

  • 帶多态性質的base classes應該聲明一個virtual析構函數。如果class帶有任何virtual函數,它就應該擁有一個virtual析構函數
  • Classes的設計目的如果不是作為base classes 使用,或不是為了具備多态性,就不該聲明virtual析構函數。

    父類指針指向子類對象,而子類對象卻經由父類指針被删除,當父類有個non-virtual析構函數是,就會引起災難。

    C++明确指出,當子類對象經由一個父類指針被删除,而該父類帶有一個non-virtual析構函數,其結果未定義--實際執行時通常發生的是對象的derived成分沒有被銷毀。子類的析構函數也未能被執行。然而其base class成分通常會被銷毀,于是造成了詭異的“局部銷毀”對象。這可能造成資源洩露,敗壞資料結構等