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

Effective C++ 條款 09:建構與解構期間勿呼叫 virtual 函式

     編輯:關於C++

核心規則

在建構函式或解構函式中呼叫 virtual 函式是危險的——此時虛擬函式機制不會下降到衍生類別,而是呼叫當前類別(基底類別)版本的實作。

建構/解構順序回顧

  • 建構:基底類別 → 衍生類別(由內而外)。
  • 解構:衍生類別 → 基底類別(由外而內)。

當基底類別建構函式執行時,衍生類別成員尚未初始化;若此時呼叫虛擬函式,物件的動態型別仍為基底類別,故解析至基底類別版本。

錯誤示範

class Transaction {
public:
    Transaction() { logTransaction(); } // 建構函式呼叫 virtual
    virtual void logTransaction() const = 0; // 純虛擬
};

class Sell : public Transaction {
public:
    virtual void logTransaction() const { /* 實作 */ }
};

Sell s; // 錯誤:Transaction::Transaction() 呼叫 Transaction::logTransaction() → 純虛擬呼叫 → 連結錯誤或執行期崩潰

解決方案:傳遞初始化資訊給基底類別

將虛擬函式改為非虛擬,並透過建構函式參數傳遞衍生類別所需資訊:

class Transaction {
public:
    explicit Transaction(const std::string& logInfo) {
        logTransaction(logInfo); // 非 virtual
    }
    void logTransaction(const std::string& logInfo) const { /* 實作 */ }
};

class Sell : public Transaction {
public:
    Sell() : Transaction(createLogInfo()) {} // 衍生類別提供資訊
private:
    static std::string createLogInfo() { return "Sell log"; }
};

替代作法:Two-Phase Initialization(兩階段初始化)

若必須在建構後呼叫虛擬函式,可提供 init() 非虛擬函式,由使用者在建構完成後顯式呼叫:

class Base {
public:
    Base() {} // 不呼叫虛擬函式
    void initialize() { onInitialize(); } // 非 virtual 入口
private:
    virtual void onInitialize() = 0; // 衍生類別實作
};

class Derived : public Base {
private:
    virtual void onInitialize() override { /* 真正初始化 */ }
};

// 使用
Derived d;
d.initialize(); // 安全呼叫虛擬函式

解構函式同理

解構函式中呼叫虛擬函式同樣只會解析至當前類別版本(衍生類別部分已被銷毀)。若需清理邏輯,應在衍生類別解構函式中直接實作,或採用非虛擬介面模式(NVI)。

總結

  • 建構/解構期間,物件的動態型別為「目前正在建構/解構的那個類別」。
  • 虛擬函式呼叫不會下降至衍生類別。
  • 解決途徑:移除虛擬屬性、透過參數傳遞資訊、或採用兩階段初始化。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved