effective C++ 條款 9:絕不在析構和建構函式中調用virtual函數

來源:互聯網
上載者:User

每當建立一個交易對象,在審計日誌中也要建立一筆適當記錄。下面是一個看起來比較合理的做法:

class Transaction
{
public:
    Transaction()
    {
        init();
    }
    virtual void logTransaction() const { std::cout << "transaction" ;};
protected:

private:
    void init()
    {
        logTransaction();
    }
};

class BuyTransaction : public Transaction
{
public:
    BuyTransaction():a(5){}
    virtual void logTransaction() const { std::cout << "BUytransaction"<< a;};
protected:
private:
    int a;
};

class SellTransaction : public Transaction
{
public:
    virtual void logTransaction() const;
protected:
private:
};

inline void testItem9()
{
    BuyTransaction b;
}

base class構造期間virtual函數絕不會下降到derived classes階層。取而代之的是,對象的作為就像隸屬base類型一樣。

非正式的說法:在base class構造期間,virtual函數不是virtual函數。

由於base class 的建構函式的執行更早於derived class建構函式,當base class的建構函式執行時,derived class的成員變數尚未初始化。如果期間調用的virtual函數下降至derived class階層, derived class的函數幾乎必然取用local成員變數,而那些成員變數尚沒有初始化。這會導致不明確行為和徹夜調試。

其實還有更根本的原因,在derived對象的base class 構造期間,對象的類型是base class而不是derived class。不只virtual函數會被編譯器解析至base class,若使用運行期類型資訊(runtime type information,例如dynamic_cast和typeid),也會把對象視為base class類型。本例中當Transaction建構函式正執行起來打算初始化“BuyTransaction對象內的base class成分”時,該對象的類型是Transaction。這個對象內的“BuyTransaction專屬成分”尚未被初始化,所以面對這些,最安全的做法是視他們不存在。對象在derived class建構函式開始執行之前不會成為一個derived class對象。

相同道理也適用於解構函式。一旦derived class 解構函式開始執行, 對象內的derived class成員變數便呈現未定義值,

所以c++視他們彷彿不存在,進入base class解構函式後,對象就變成一個base class對象。

如何確保每次一有Transaction繼承體繫上的對象被建立,就會有適當版本的logTransaction被調用呢?在建構函式中調用virtual函數是一種錯誤的做法。

其他方案可以解決這個問題。一種做法是在Transaction內將logTransaction修改為non-virtual,然後要求derived class 構造

函數傳遞必要的資訊給Transaction建構函式, 然後那個建構函式便可安全的調用non-virtual的logTransaction。像這樣:

class Transaction
{
public:
    explicit Transaction(const std::string &logInfo);
    void logTransaction(const std::string &logInfo) const;
protected:

private:
};
Transaction::Transaction(const std::string &logInfo)
{
    logTransaction(logInfo)
}

class BuyTransaction : public Transaction
{
public:
    BuyTransaction(parameters)
        : Transaction(createLogString(parameters))
    {
        ...;
    }
protected:
private:
    static std::string createLogString(parameters);
    int a;
};

換句話說由於你無法使用virtual函數從base class向下調用,在構造期間,你可以藉由“令derived class將必要的構造資訊向上傳遞至base class建構函式”替換之而加以彌補。

注意本例之BuyTransaction內的private static函數createLogString的運用。是的,比起在成員初始化列表裡給予base class

所需資料。利用輔助函數建立一個值傳給base class建構函式往往比較方便。令此函數為static,也就不可能意外指向“初值未成熟的BuyTransaction對象內尚未初始化的成員變數”。

在建構函式和解構函式期間不要調用virtual函數,因為這類調用從不下降至derived class(比起當前執行構造和析構的那層)。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.