虛析構函數(√)、純虛析構函數(√)、虛構造函數(X)

一. 虛析構函數
我們知道,爲了能夠正確的調用對象的析構函數,一般要求具有層次結構的頂級類定義其析構函數爲虛函數。因爲在delete一個抽象類指針的時候,必須要通過虛函數找到真正的析構函數。例如:
#include <iostream>
using namespace std;
class Base
{
public:
Base() {}
virtual ~Base() {cout<<"Base Destruct"<<endl;}
};
class Derived : public Base
{
public:
    Derived() {}
    ~Derived() {cout<<"Derived Destruct"<<endl;}
};
int main()
{
    Base * pb = new Derived();
    delete pb;

    return 0;
}
這是正確的用法,會發生動態綁定,它會先調用Derived的析構函數,然後調用Base的析構函數。
如果Base析構函數不加virtual,說明Base的析構函數不是虛析構函數,調用的函數依賴於指向靜態類型(即Base),delete pb只會執行Base的析構函數,而不是真正的Derived析構函數。

二. 純虛析構函數
現在的問題是,我們想把Base做成抽象類,不能直接構造對象,需要在其中定義一個純虛函數。如果其中沒有其他合適的函數,可以把析構函數定義爲純虛的,即將前面的Base定義改成:
class Base
{
public:
    Base() {}
    virtual ~Base() = 0;
};
可是,這段代碼不能通過編譯,通常是link錯誤,不能找到~Base()的引用。這是因爲,析構函數、構造函數和其他內部函數不一樣,在調用時,編譯器需要產生一個調用鏈。也就是說,Derived的析構函數裏面隱含調用了Base的析構函數。而剛纔的代碼中,缺少~Base()的函數體,當然會出現錯誤。

這裏面有一個誤區,有人認爲,virtual f()=0這種純虛函數是沒有定義體的。其實,這是不對的。這種語法只是表明這個函數是一個純虛函數,因此這個類變成了抽象類,不能產生對象。我們完全可以爲純虛函數指定函數體。通常的純虛函數不需要函數體,是因爲我們一般不會調用抽象類的這個函數,只會調用派生類的對應函數。這樣,我們就有了一個純虛析構函數的函數體,上面的代碼需要改成:
class Base
{
public:
    Base(){}
    virtual ~Base() = 0; //pure virtual
};
Base::~Base()//function body
{
}
從語法角度來說,不可以將上面的析構函數直接寫入類聲明中(內聯函數的寫法)。
這個問題看起來有些學術化,因爲一般我們完全可以在Base中找到一個更加適合的函數,通過將其定義爲沒有實現體的純虛函數,而將整個類定義爲抽象類。但這種技術也有一些應用,如這個例子:
class Base //abstract class
{
public:
    virtual ~Base(){};//virtual, but not pure
    virtual void Hiberarchy() const = 0;//pure virtual
};

void Base::Hiberarchy() const //pure virtual also can have function body
{
    cout <<"Base::Hiberarchy"<<endl;
}

class Derived : public Base
{
public:
    Derived(){}
    virtual void Hiberarchy() const
    {
        Base::Hiberarchy();
        cout <<"Derived::Hiberarchy"<<endl;
    }
};

int main()
{
    Base* pb=new Derived();
    pb->Hiberarchy();
    pb->Base::Hiberarchy();
    return 0;
}
在這個例子中,我們試圖打印出類的繼承關係。在基類Base中定義了虛函數Hiberarchy,然後在派生類中重載此函數。我們再一次看到,由於想把Base做成個抽象類,而這個類中沒有其他合適的成員方法可以定義爲純虛,我們只好將Hiberarchy定義爲純虛的。(當然,完全可以定義~Base函數,這就和上面的討論一樣了。)
另外,可以看到,在main中有兩種調用方法,第一種是普通的方式,進行動態鏈接,執行虛函數,得到結果"Derived::Hiberarchy";第二種是指定類的方式,就不再執行虛函數的動態鏈接過程了,結果是"Base::Hiberarchy"。
通過上面的分析可以看出,定義純虛函數的真正目的是爲了定義抽象類,而並不是函數本身。
最後,總結一下關於虛函數的一些常見問題:
1) 虛函數是動態綁定的,也就是說,使用虛函數的指針和引用能夠正確找到實際類的對應函數,而不是執行定義類的函數。這是虛函數的基本功能。
2) 構造函數不能是虛函數。因爲虛函數的意思是對象產生後開啓動態綁定,程序會根據對象的動態類型來選擇要調用的方法。然而在構造函數運行時,這個對象根本不存在,所以不能產生動態綁定。而且,在構造函數中調用虛函數,實際執行的是父類的對應函數,因爲自己還沒有構造好, 多態是被disable的。
3) 析構函數可以是虛函數,而且,在一個複雜類結構中,這往往是必須的。
4) 將一個函數定義爲純虛函數,實際上是將這個類定義爲抽象類,不能實例化該類的對象。
5) 純虛函數通常沒有定義體,但也完全可以擁有。
6) 析構函數可以是純虛的,但純虛析構函數必須有定義體,因爲析構函數的調用是在子類中隱含的。
7) 非純的虛函數必須有定義體,不然是一個錯誤。
8) 派生類的override虛函數定義必須和父類完全一致。除了一個特例,如果父類中返回值是一個指針或引用,子類override時可以返回這個指針(或引用)的派生。例如,在上面的例子中,在Base中定義了 virtual Base* clone(); 在Derived中可以定義爲 virtual Derived* clone()。可以看到,這種放鬆對於Clone模式是非常有用的。
發佈了24 篇原創文章 · 獲贊 8 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章