首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C++ >> C++ 入門

Effective C++ 條款 09:構造與析構期間絕不調用虛函數

     編輯:C++ 入門

核心觀點

在構造函數和析構函數中調用虛函數是未定義行為的來源。此時對象的動態類型尚未完全構建或已部分銷毀,虛函數調用不會下降到派生類,而是綁定到當前構造/析構層級的類版本。這與 Java/C# 行為截然不同。

問題場景:交易審計日誌

假設有一個交易類層次結構,基類 Transaction 在構造時需記錄審計日誌:

// 錯誤示範
class Transaction {
public:
    Transaction() { logTransaction(); } // 調用虛函數!
    virtual void logTransaction() const = 0;
};

class BuyTransaction : public Transaction {
public:
    BuyTransaction(/* params */) { /* ... */ }
    void logTransaction() const override { /* 記錄買入日誌 */ }
private:
    std::string security_; // 派生類成員
};

當構造 BuyTransaction 時,基類 Transaction 構造函數先執行,此時 BuyTransaction 成員 security_ 尚未初始化。若 logTransaction() 被調用,它綁定到 Transaction::logTransaction(純虛函數),導致運行時錯誤;即使不是純虛,也無法訪問派生類未初始化的成員。

正確解決方案:非虛函數 + 參數傳遞

將日誌邏輯改為非虛函數,由派生類構造函數通過參數傳遵必要信息給基類:

class Transaction {
public:
    explicit Transaction(const std::string& logInfo) {
        logTransaction(logInfo); // 非虛函數調用,安全
    }
    void logTransaction(const std::string& logInfo) const { // non-virtual
        // 記錄通用審計信息
    }
    virtual ~Transaction() = default;
};

class BuyTransaction : public Transaction {
public:
    BuyTransaction(/* params */) 
        : Transaction(createLogString(/* params */)) // 傳遞日誌信息
    { /* 初始化自身成員 */ }
    
private:
    static std::string createLogString(/* params */) {
        // 構造日誌字符串,不依賴 this 指針
        return "BUY: " + /* ... */;
    }
};

關鍵技巧:私有靜態輔助函數

使用 private static 函數構造基類所需參數,優勢在於:

  • 無 this 指針,不可能誤訪問未初始化的派生類成員
  • 邏輯封裝在派生類內部,不污染基類介面
  • 編譯期保證不依賴對象狀態

析構函數同理

析構過程中,派生類成員已銷毀,基類析構函數調用虛函數同樣只會綁定到基類版本。解決方案相同:避免在析構函數中調用虛函數,或改為非虛函數並傳遞必要信息。

深度解析:對象構造/析構的動態類型變化

總結

構造/析構期間,對象的動態類型隨構造/析構進度變化。
絕不在構造函數和析構函數中調用虛函數。
替代方案:非虛函數 + 派生類通過參數傳遞信息給基類(可用私有靜態輔助函數構造參數)。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
階段動態類型虛函數調用行為
基類構造函數執行中基類綁定基類版本
派生類構造函數體執行中派生類綁定派生類版本
派生類析構函數體執行中派生類綁定派生類版本
基類析構函數執行中基類綁定基類版本