《Effective C++》讀書筆記

田世豪發表於2024-04-21

《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;
}

相關文章