天天看點

C++之構造函數和析構函數中不要調用virtual函數(9)---《Effective C++》

條款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這層。

繼續閱讀