C++ 虛擬解構子在多型物件的重要性 - Virtual Destructor

如果你使用了一個多型物件,而沒有為它的解構子帶上 virtual 的宣告的話,會有兩種類型的錯誤會發生。

只運行 Base 的解構子

若是你的物件含有一些成員指標變數,指向著存在於 Heap 空間的實體,你可能會在解構子裡面定義好一些回收資源的操作,來讓那些動態產生的實體,能夠隨著你的物件生命結束時,能夠一起被釋放掉,而不會有 memory leak 的情況發生。

那麼如果你沒有將你的解構子宣告成為一個 virtual 的型態,那麼你的多型物件在被 delete 的時候,就只會執行 Base class 的解構子,而不會如你所願地運行你特地在解構子所填寫的那些行為。

沒有宣告 virtual 的解構子
class Animal
{public:
    ~Animal(){ cout << "A animal is going to die." << endl; };
};
class Cat : public Animal
{public:
    ~Cat(){ 
        cout << "A cat is going to die. " << endl; 
        cout << "Burning its cloth, scratching post, and bone ash." << endl;
    };
};
int main(){
    Animal *c = new Cat();
    delete c;
    return 0;
}
執行結果
A animal is going to die.

Compiler 不知道釋放多少空間,而產生的 Undefined Behavior

在《Modern C++ Design》第四章有提到,在 C++ 標準中,物件的 operator delete 有兩種的重載介面:
- void operator delete(void* p)
- void operator delete(void* p, std::size_t size)

當你在 delete 物件時, compiler 需要知道要釋放多少的空間,而這個資訊就是需要由 size 來提供。那麼一般在重載第一種沒有提供大小資訊的 operator delete 介面時,compiler 會依照許多種作法來幫你自動產生些許的程式碼,來提供相當於 size 的資訊。在書中有提到 compiler 可能會有下列作法:
1. 由 virtual ~Cat 來提供 sizeof(Cat) 的大小。
2. 將 Cat 的大小放在 vtable 裡。
以上跟書中所提到的另外兩種作法,都是在擁有 virtual 解構子的前提下。

結論就是,若是你不宣告一個 virtual 解構子,那麼 delete Cat 只會釋出 Animal 大小的空間,而造成一些 Undefined Behavior。不過一般良好強大的 compiler 都會偵測到這種錯誤,而噴出 warnning(而不是 error),但也是讓你編譯過,反正就是通知的義務己盡,但是運行後所產生的 UB 就要後果自負。

導出一個事實

不要繼承沒有虛擬解構子的 Class ,像是 STL 中的 string 與 container,否則會有意想不到的錯誤在等著你。

comments powered by Disqus