首先結論如標題所示。
如果你有一個類的繼承體系,用來模擬股票交易市場的買進賣出,並且每一筆交易都需要進行記錄,那麼可能會有如下的類繼承關係:
class Transaction {
public:
Transaction()
{
//...
LogTransaction();
}
virtual void LogTransaction() const = 0;
};
class BuyTransaction : public Transaction
{
public:
BuyTransaction(){}
void LogTransaction() const override {
//...
}
};
class SellTransaction : public Transaction
{
public:
SellTransaction(){}
void LogTransaction() const override {
//...
}
};
當執行到下面這一行時,會發生什麼事情呢?
BuyTransaction buyTrans;
程序在運行的時候就會報錯:
錯誤 LNK2019 無法解析的外部符號 "public: virtual void __thiscall Transaction::LogTransaction(void)const " (?LogTransaction@Transaction@@UBEXXZ),該符號在函數 “public: __thiscall Transaction::Transaction(void)” (??0Transaction@@QAE@XZ) 中被引用
究其原因,如下:
子類 BuyTransaction 的構造函數在調用的時候,會先調用父類 Transaction 的構造函數。而在父類的構造函數中有一個虛函數 LogTransaction 的調用,這個時候,虛函數 LogTransaction 的調用是調用的父類 Transaction 的版本,而不是我們預想的子類 BuyTransaction 的版本,即使目前即將建立的對象是 BuyTransaction 類型。而在父類中 LogTransaction 它是一個 pure virtual function,並沒有實現,所以連接器纔會找不到實現,提示無法解析。
我們也可以稍微修改一個上面的代碼,來驗證以上說法:
class Transaction {
public:
Transaction()
{
//...
LogTransaction();
}
virtual void LogTransaction() const
{
std::cout << "class \"Transaction\" called function \"LogTransaction\"";
}
};
class BuyTransaction : public Transaction
{
public:
BuyTransaction(){}
void LogTransaction() const override
{
std::cout << "class \"BuyTransaction\" called function \"LogTransaction\"";
}
};
class SellTransaction : public Transaction
{
public:
SellTransaction(){}
void LogTransaction() const override
{
std::cout << "class \"SellTransaction\" called function \"LogTransaction\"";
}
};
int main()
{
BuyTransaction buyTrans;
system("pause");
}
將 pure virtual function 修改爲了 normal virtual function,即父類提供 LogTransaction 的實現,這個時候,輸出的正是:
class "Transaction" called function "LogTransaction“
驗證了在 base class 的構造期間,virtual 函數並不是 virtual 函數,無法實現多態。也就說,在這個期間,對象的類型不是 derived class,而是 base class。
其實這個原因也很好想。base class 的構造函數要先於 derived class 的構造函數執行,當 base class 構造函數在執行時,derived class 的成員變量都還沒有進行初始化。我們知道多態的實現是利用指向虛函數表的指針實現的,這個時候子類都還沒有構造成功,這個虛函數指針都沒有初始化正確指向虛函數表,怎麼可能會實現多態的調用呢?
其實,不只是 virtual 函數會被編譯器解析至 base class,想其他的運行期的類型信息,如 dynamic_cast,typeid,也會把對象視作 base class 類型。
相同的道理也適用於析構函數。一旦 derived class 的析構函數開始執行,對象內的 derived class 成員變量便會呈現未定義值,進入 base class 析構函數之後對象便成爲了一個 base class 對象。
那如何解決這個問題呢?一種做法是將 virtual 函數變爲 non-virtual 函數,然後要求 derived class 傳遞必要的信息給 base class,然後 base class 的構造函數就可以放心的調用這個 non-virtual 版本:
class Transaction {
public:
//讓子類傳遞必要的構造信息
explicit Transaction(const std::string& logInfo)
{
//...
LogTransaction(logInfo);
}
private:
//變爲 non-virtual 函數
void LogTransaction(const std::string& logInfo) const
{
//記錄信息
}
};
class BuyTransaction : public Transaction
{
public:
BuyTransaction() : Transaction(createLogString(parameters))
{
}
private:
static std::string createLogString(parameters);
};