《Effective C++》讀書筆記
之前看過一遍,不過草草了事。近日看了《深度探索C++物件模型》,想起《Effective C++》中的內容已經有些忘記了,所以重新溫習一下。這篇筆記只挑選書中的一些重要內容進行記錄。
條款07:為多型基類宣告virtual解構函式
這一個條款幾乎是面試中的高頻問題,只需要回答,如果不將基類中的解構函式宣告為虛擬函式,那麼當我們透過基類指標刪除派生類物件時,只會呼叫基類的解構函式,而不會呼叫派生類的解構函式,這樣就會導致派生類中的資源無法釋放,即“區域性銷燬”,造成記憶體洩漏。可是,為什麼會這樣呢?以及為什麼將解構函式宣告為虛擬函式就可以解決這個問題呢?
讓我們結合《深度探索C++物件模型》中的內容來解釋這個問題。-我們知道C++透過虛擬機器制實現多型,而在表現上,多型就是“以一個public base class的指標或者引用尋指出一個derived class object”的意思。因此透過基類指標呼叫函式時,編譯器會檢查該函式是否在虛表中出現,以將該呼叫替換成正確的形式。下面來討論基類解構函式不是虛擬函式時,會發生什麼。
基類解構函式不是虛擬函式
不展現多型:基類中無其它虛擬函式
這種情況下,派生類只是簡單繼承了基類,沒有重寫基類的任何函式,派生類物件中也不會有vptr。因此,當我們透過基類指標刪除派生類物件時,只會呼叫基類的解構函式,派生類的解構函式不會被呼叫,這樣就會導致派生類中的資源無法釋放,即“區域性銷燬”,造成記憶體洩漏。
展現多型:基類中有其它虛擬函式
這種情況下,派生類重寫了基類的某個虛擬函式,派生類物件中會有vptr,vptr指向派生類的虛表。而因為基類的解構函式不是虛擬函式,即其沒有被重寫,所以派生類的虛表中解構函式那一項指向的是基類的解構函式。“區域性銷燬”的情況同樣會發生。
基類的解構函式是虛擬函式
那麼如果基類的解構函式是虛擬函式
下面是示例程式碼:
#include <cstdlib>
#include <iostream>
class Base {
public:
Base() {
_base_data = static_cast<char*>(malloc(100));
std::cout << "Base constructor" << std::endl;
}
virtual ~Base() {
free(_base_data);
_base_data = nullptr;
std::cout << "Base destructor" << std::endl;
}
private:
char* _base_data;
};
class Derived : public Base {
public:
Derived() {
_derived_data = static_cast<char*>(malloc(100));
std::cout << "Derived constructor" << std::endl;
}
virtual ~Derived() override {
free(_derived_data);
_derived_data = nullptr;
std::cout << "Derived destructor" << std::endl;
}
private:
char* _derived_data;
};
int main() {
Base* base{new Derived()};
delete base;
return 0;
}