條款 09:絕不在構造和析構過程中調用 virtual 函數

條款 09:絕不在構造和析構過程中調用 virtual 函數

Never call virtual functions during construction or destruction.

假設子類各有不同的記錄日誌實現,有可能會出現下面這種代碼

class T {
public:
    T();
    virtual void Log() const = 0;
};

T::T() {
    ...
    Log();
};

class A : public T {
public:
    virtual void Log() const;
    ...
};

class B : public T {
public:
    virtual void Log() const;
    ...
};

A a;

當創建 a 時,調用的是 T 的 Log 而非 A 的,因爲此時 A 尚未被初始化,C++ 編譯器仍只解析到 T,此時對象等同於 base class,因此會引起非預期行爲。析構函數同理。

class T {
public:
    T() { Init(); }
    virtual void Log() const = 0;
private:
    void Init() {
        ...
        Log();
    }
};

有時候爲了減少重複代碼,有可能會將部分初始化代碼寫到一個函數中,然後讓構造函數調用,此時會使這種錯誤更加隱蔽,甚至在基類對 Log 有實現時,編譯器和連接器都不會拋錯。
解決方法可以將該函數改爲 non-virtual,然後要求 derived-class 構造函數傳遞必要信息給 base-class 構造函數,此時 base-class 構造函數便可以安全地調用 non-virtual 函數。

class T {
public:
    explicit T(const std::string& info) { Log(info) }
    void Log(const std::string& info) const;
    ...
}

class A : public T {
public:
    A(const std::string& info)
    : T(info) { ... }
}

A a("Hello World!");
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章