條款9:絕不要在構造函數和析構函數中調用virtual函數
為什麼不要在構造函數和析構函數中調用virtual函數呢?下面請大家帶着上述問題來看如下代碼:
class Base{
public:
Base();
virtual void hello();
...
}
private:
int x;
int y;
};
Base::Base(){
...
hello();
}
class ABase:public Base{
public:
virtual void hello();//這兒可以聲明為virtual,也可以不用聲明為virtual,因為在基類中已經聲明為virtual,那麼子類中相應的所有函數都就變味virtual
...
};
class BBase::public Base{
public:
virtual void hello();
...
}
ABase a;
試想上面代碼運作時候會發生什麼問題?ABase的構造函數被調用,ABase的基類Base構造函數一定更早調用,即ABase中從Base中繼承的x,y變量通過Base的構造函數被初始化,ABase中的z變量尚未被初始化,此時好戲上演。
1)Base類的構造函數中調用的虛函數hello()函數是基類Base的,還是子類ABase的呢?答案當然是基類Base的,因為在基類Base的構造函數執行期間,virtual函數絕對不會下降到子類ABase階層,如果這樣比較枯燥的話,我們可以反向解釋:2)基類Base的構造函數執行期間,子類ABase中的成員變量z并沒有被初始化,如果在基類Base的構造函數中調用的virtual函數已經下降到了子類ABase階層,那麼Base中調用的這個virtual函數一定要取取用子類ABase中的成員變量,而這些成員變量并沒有被初始化,别掙紮了,C++不允許這樣,可以這樣了解:在Base類構造期間,virtual函數不是virutal函數,3)其實最主要的原因是:在子類ABase對象的基類構造函數執行期間,對象的類型是基類Base的,而不是子類ABase的,不止virtual函數會被解至基類Base中,也會把對象視為基類Base類型,在本例中,當子類ABase的構造函數執行起來時,一定先調用基類Base的構造函數來初始化子類ABase中的基類成分,這時候對象的類型是基類Base的,同時這時子類ABase中的專屬成分如z尚未被初始化,對象在子類構造函數(即子類中的基類構造函數執行結束後)開始執行之前一定不會成為一個子類對象。
同樣對着适用于析構函數,C++的析構函數先執行子類的析構函數,後執行基類的析構函數,執行和構造函數執行的順序剛好相反,一旦子類析構函數開始執行,對象中的子類成員變量呈現未定義值,C++視它們不存在,進入基類析構函數之後就成為一個基類對象,C++的任何部分包括virtual函數,dynamic_casts也同樣這樣看待它。
**
需要注意的是,有的編譯器對這種情況報錯,而有的并不報錯,很坑的!
**
由于這樣并沒有執行virtual函數,達到相應的效果,是以,C++明确規定不能在構造函數和析構函數中調用virtual函數。
其他方法可以解決這個問題,其中之一是在基類Base中virtual函數hello聲明為non-virtual,然後要求子類ABase構造函數傳遞必要的資訊給Base構造函數,因為基類Base中調用了Base的成員函數hello,不可能下降至子類ABase中,是以安全,為了雙重安全,我們也可以選擇利用static函數提供資料給基類Base資料,這樣在子類ABase中一定不會存在未被初始化的資料,而後這個構造函數就可以安全的調用non-virtual函數hello,如:
class Base{
public:
Base(int x,int y);
void hello();//現在是個non-virtual函數
...
private:
int x;
int y;
};
Base::Base(int x,int y):x(x),y(y){
hello();//現在是個non-virtual調用
}
class ABase:public Base{
public:
ABase(params):Base(createParams(params)){
...
}
...
private:
static *** createParams(parameters);
};
這種方式是怎樣的呢?你無法使用virtual函數從基類Base向下調用,在構造期間,你可以籍由“令子類将必要的構造資訊向上傳遞值基類構造函數”替換加以彌補,這裡令createParams函數為static,也就不可能意外指向“初期未成熟之子類ABase對象中尚未初始化的成員變量”。
總結如下:在構造函數中不要調用virtual函數,因為這類調用從不下降至子類derived class這層。