inheritance pure - 為什麼我應該在C++中聲明一個抽像類的虛析構函數?



virtual destructors (7)

我知道在C ++中為基類聲明虛析構函數是一種很好的做法,但是對於聲明作為接口函數的抽像類的virtual析構函數總是很重要的? 請提供一些原因和示例。


Answers

凱文,首先,請不要在我們之前的討論中親自跳過你的評論,我不打算這麼苛刻。 無論如何,這個問題的主要答案。

這並不總是必需的,但我認為這是一個好習慣。 它的作用是允許派生對象通過基類型的指針安全地刪除。

例如:

Base *p = new Derived;
// use p as you see fit
delete p;

如果Base沒有虛擬析構函數,它就會形成格式,因為它會嘗試刪除該對象,就好像它是一個Base *


我決定做一些研究並嘗試總結你的答案。 下面的問題將幫助你決定你需要什麼樣的析構函數:

  1. 你的類是否打算用作基類?
    • 否:聲明公共的非虛擬析構函數以避免類的每個對像上的v指針*
    • 是的:閱讀下一個問題。
  2. 你的基類是抽象的嗎? (即任何虛擬純粹的方法?)
    • 否:嘗試通過重新設計類層次結構來使基類抽象化
    • 是的:閱讀下一個問題。
  3. 你想通過基指針來允許多態刪除嗎?
    • 否:聲明受保護的虛擬析構函數以防止不需要的使用。
    • 是的:聲明公共虛擬析構函數(在這種情況下沒有開銷)。

我希望這有幫助。

*重要的是要注意,在C ++中沒有辦法將類標記為final(即非子類),所以如果您決定將析構函數聲明為非虛擬的和公共的,請記得明確警告您的其他程序員反對從你的課堂派生出來。

參考文獻:


是的,它總是重要的。 派生類可以分配內存或保存對其他資源的引用,這些資源在對像被銷毀時將需要清理。 如果你不給你的接口/抽像類虛擬析構函數,那麼每次通過基類句柄刪除派生類實例時,派生類的析構函數將不會被調用。

因此,你正在開放內存洩漏的可能性

class IFoo
{
  public:
    virtual void DoFoo() = 0;
};

class Bar : public IFoo
{
  char* dooby = NULL;
  public:
    virtual void DoFoo() { dooby = new char[10]; }
    void ~Bar() { delete [] dooby; }
};

IFoo* baz = new Bar();
baz->DoFoo();
delete baz; // memory leak - dooby isn't deleted

你的問題的答案往往是,但並非總是如此。 如果你的抽像類禁止客戶在指向它的指針上調用delete(或者如果它在文檔中說明的話),你可以自由地不聲明虛擬析構函數。

您可以禁止客戶端通過使其析構函數受到保護來調用指向它的指針的delete。 像這樣工作,省略虛擬析構函數是完全安全合理的。

最終你最終將沒有虛擬方法表,並最終告訴你的客戶你有意通過指向它的指針使它不可刪除,所以你確實有理由不在這些情況下聲明它是虛擬的。

[見本文第4條: http://www.gotw.ca/publications/mill18.htmhttp://www.gotw.ca/publications/mill18.htm ]


答案很簡單,你需要它是虛擬的,否則基類不會是一個完整的多態類。

    Base *ptr = new Derived();
    delete ptr; // Here the call order of destructors: first Derived then Base.

你更喜歡上面的刪除,但是如果基類的析構函數不是虛擬的,那​​麼只有基類的析構函數會被調用,並且派生類中的所有數據都將保持未刪除狀態。


對於界面來說更重要。 你的類的任何用戶可能會持有一個指向該接口的指針,而不是指向具體實現的指針。 當它們刪除它時,如果析構函數是非虛擬的,它們將調用接口的析構函數(或者編譯器提供的默認值,如果沒有指定),而不是派生類的析構函數。 即時內存洩漏。

例如

class Interface
{
   virtual void doSomething() = 0;
};

class Derived : public Interface
{
   Derived();
   ~Derived() 
   {
      // Do some important cleanup...
   }
};

void myFunc(void)
{
   Interface* p = new Derived();
   // The behaviour of the next line is undefined. It probably 
   // calls Interface::~Interface, not Derived::~Derived
   delete p; 
}

如果您不熟悉以抽像或甚至學術方式學習計算機語言,那麼語義上的差異可能會顯得深奧。

在最高級別,引用的想法是它們是透明的“別名”。您的計算機可能會使用一個地址來使它們工作,但您不應該擔心:您應該將它們視為現有對象的“另一個名稱”,並且語法反映了這一點。它們比指針更嚴格,因此當您要創建懸空引用時,編譯器可以更可靠地警告您,而不是在您創建懸空指針時。

除此之外,指針和引用之間當然存在一些實際差異。使用它們的語法明顯不同,你不能“重新定位”引用,引用虛無,或指向引用。





c++ inheritance virtual-destructor