1. 程式人生 > >C++建構函式與解構函式呼叫虛擬函式的注意事項

C++建構函式與解構函式呼叫虛擬函式的注意事項

雖然可以對虛擬函式進行實呼叫,但程式設計師編寫虛擬函式的本意應該是實現動態聯編。在建構函式中呼叫虛擬函式,函式的入口地址是在編譯時靜態確定的,並未實現虛呼叫。但是為什麼在建構函式中呼叫虛擬函式,實際上沒有發生動態聯編呢?

第一個原因,在概念上,建構函式的工作是為物件進行初始化。在建構函式完成之前,被構造的物件被認為“未完全生成”。當建立某個派生類的物件時,如果在它的基類的建構函式中呼叫虛擬函式,那麼此時派生類的建構函式並未執行,所呼叫的函式可能操作還沒有被初始化的成員,將導致災難的發生。

第二個原因,即使想在建構函式中實現動態聯編,在實現上也會遇到困難。這涉及到物件虛指標(vptr)的建立問題。在Visual C++中,包含虛擬函式的類物件的虛指標被安排在物件的起始地址處,並且虛擬函式表(vtable)的地址是由建構函式寫入虛指標的。所以,一個類的建構函式在執行時,並不能保證該函式所能訪問到的虛指標就是當前被構造物件最後所擁有的虛指標,因為後面派生類的建構函式會對當前被構造物件的虛指標進行重寫,因此無法完成動態聯編。

同樣的,在解構函式中呼叫虛擬函式,函式的入口地址也是在編譯時靜態決定的。也就是說,實現的是實呼叫而非虛呼叫。
考察如下例子。

#include <iostream>
using namespace std;

class A 
{
public:
    virtual void show(){
        cout<<"in A"<<endl;
    }
    virtual ~A(){show();}
};

class B:public A 
{
public:
    void show(){
        cout<<"in B"
<<endl; } }; int main() { A a; B b; }

程式輸出結果是:

in A
in A

在類B的物件b退出作用域時,會先呼叫類B的解構函式,然後呼叫類A的解構函式,在解構函式~A()中,呼叫了虛擬函式show()。從輸出結果來看,類A的解構函式對show()呼叫並沒有發生虛呼叫。

從概念上說,解構函式是用來銷燬一個物件的,在銷燬一個物件時,先呼叫該物件所屬類的解構函式,然後再呼叫其基類的解構函式,所以,在呼叫基類的解構函式時,派生類物件的“善後”工作已經完成了,這個時候再呼叫在派生類中定義的函式版本已經沒有意義了。

因此,一般情況下,應該避免在建構函式和解構函式中呼叫虛擬函式,如果一定要這樣做,程式猿必須清楚,對虛擬函式的呼叫其實是實呼叫。

參考文獻

[1]陳剛.C++高階進階教程[M].武漢:武漢大學出版社,2008[8.6(P299-P302)]