在建構函式或解構函式中呼叫 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"; }
};
若必須在建構後呼叫虛擬函式,可提供 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)。