2016-12-06 113 views
24

大家都知道基類的破壞者通常必須是虛擬的。但是派生類的析構函數是什麼?在C++ 11中,我們有關鍵字「覆蓋」和明確使用默認析構函數的能力。虛擬析構函數的默認覆蓋

struct Parent 
{ 
    std::string a; 
    virtual ~Parent() 
    { 
    } 

}; 

struct Child: public Parent 
{ 
    std::string b; 
    ~Child() override = default; 
}; 

在Child類的析構函數中使用關鍵字「override」和「= default」是否正確?編譯器會在這種情況下生成正確的虛擬析構函數嗎?

如果是的話,那麼我們是否可以認爲這是很好的編碼風格,並且我們應該總是這樣聲明派生類的析構函數以確保基類析構函數是虛擬的?

+5

不妨做'static_assert(STD :: has_virtual_destructor ::價值,「違反合同」);' – milleniumbug

+3

注意,它並不總是要求基類析構函數是虛擬的。所以這只是(可能)一個好主意,如果這是一個要求。 – juanchopanza

+3

如果它有效,我喜歡它,但milleniumbug的更好(更明確的意圖)。另一方面,Stroustrup討厭防止常見錯誤的「編碼標準」結構,並堅持編譯器應該生成適當的警告。 –

回答

10

override不過是一個安全網。如果基類析構函數是虛擬的,無論它是如何聲明的 - 或者根本沒有聲明(即使用隱式聲明的),子類的析構函數將始終是虛擬的。

+4

問題在於基類必須具有虛擬析構函數但不正確的情況。 –

+4

@SergeyA這不是問題的答案。 –

-1

CPP Reference表示override確保函數爲virtual,並且確實覆蓋了虛函數。因此override關鍵字將確保析構函數是虛擬的。

如果你指定override而不是= default,那麼你將得到一個鏈接錯誤。

你不需要做任何事情。離開Child析構函數未定義的作品就好了:

#include <iostream> 

struct Notify { 
    ~Notify() { std::cout << "dtor" << std::endl; } 
}; 

struct Parent { 
    std::string a; 
    virtual ~Parent() {} 
}; 

struct Child : public Parent { 
    std::string b; 
    Notify n; 
}; 

int main(int argc, char **argv) { 
    Parent *p = new Child(); 
    delete p; 
} 

將輸出dtor。如果您刪除處的virtual,但它不會輸出任何內容,因爲這是未定義的行爲,正如評論中指出的那樣。

好的風格應該是沒有提到Child::~Child。如果你不能相信基類聲明它是虛擬的,那麼你的建議override= default將工作;我希望有更好的方法可以確保不會用這些析構函數聲明來亂拋垃圾代碼。

+2

如果您在'Parent ::〜Parent'刪除'virtual',您將*具有未定義的行爲*。它可能什麼都不輸出。它可能會顯示一個致命的錯誤對話框。或覆蓋您正在使用的數據文件。 –

+0

有趣!我假設它只是在子類上調用基本的dtor,這會導致基礎成員定義良好但可能不希望的清理,而不是任何派生成員。我已經更新了我的答案,以納入您的評論。 –

0

儘管析構函數沒有被繼承,但標準中明確寫出派生類的虛析構函數會覆蓋基類的析構函數。

從C++標準(10.3虛函數)

6即使析構函數不被繼承,在派生 類覆蓋基類析構函數聲明虛擬析構函數;見12.4和 12.5。

在另一方面存在也被寫入(9.2類部件)

8.一種的virt-SPECI音響ER-SEQ應包含至多每個的virt-SPECI音響ER之一。 虛擬成員函數(10.3)只能在a 聲明中出現。

雖然析構函數被稱爲特殊成員函數,但它們也是成員函數。

我相信C++標準應該被編輯成這樣一種方式,它是明確的,一個析構函數是否可以具有virt-specifier override。目前尚不清楚。

+0

「我測試過的編譯器發出一個錯誤...」,這正是* override的點*。你用非'virtual'析構函數寫你的基類;編譯器的行爲是正確的。 –

+0

@KyleStrand我刪除了關於編譯器的說明,因爲我可以使用舊的編譯器。 –

+0

我懷疑你錯過了我的觀點。您可以將您的代碼複製並粘貼到在線編譯器中(例如[Coliru](http://coliru.stacked-crooked.com/))嗎? –

14

在Child類的析構函數中使用關鍵字「override」和「= default」是否正確?編譯器會在這種情況下生成正確的虛擬析構函數嗎?

是的,它是正確的。在任何一個健全的編譯器中,如果代碼編譯沒有錯誤,這個析構函數定義將是一個無操作:它的缺席不能改變代碼的行爲。

,我們可以認爲這是良好的編碼風格

它喜好的問題。對我而言,只有基類類型是模板化的纔有意義:然後,它會強制要求基類具有虛擬析構函數。否則,當基類型被修復時,我會認爲這樣的代碼是噪聲。這不像基類會奇蹟般地改變。 但是如果你有不想檢查取決於它們可能中斷的代碼而改變某些事情的死腦筋隊友,最好留下析構函數定義 - 作爲額外的保護層。

+3

你不需要魔法,你只需要死氣沉沉的隊友;) –

+0

@LightnessRacesinOrbit哦,男孩。好,點了。 –

+4

請注意,有時候,你是自己的死腦筋的隊友:( –

5

在這裏使用override有(至少)一個原因 - 您確保基類的析構函數始終是虛擬的。如果派生類的析構函數相信它正在重寫某個東西,但是沒有什麼可以重寫,那將會是一個編譯錯誤。如果你這樣做的話,它還爲你提供了一個方便的地方來留下生成的文檔。

在另一方面,我能想到的兩個原因不要這樣做:

  • 這是一個有點怪異和向後派生類的強制執行從基類的行爲。
  • 如果您在頭文件中定義了一個destuctor(或者如果您將它設置爲內聯),那麼確實會引入編譯錯誤的可能性。比方說,你的類看起來是這樣的:

    struct derived { 
        struct impl; 
        std::unique_ptr<derived::impl> m_impl; 
        ~derived() override = default; 
    }; 
    

    你可能會得到一個編譯器錯誤,因爲析構函數(這是內嵌這裏的類)將被尋找的析構不完整的類,derived::impl

    這是我的一種說法,即每一行代碼都可能成爲責任,也許最好只是在功能上什麼都不做時跳過一些東西。如果你確實需要在父類中強制執行基類中的虛析構函數,有人建議使用static_assertstd::has_virtual_destructor一致,這將產生更一致的結果,恕我直言。

0

我認爲「重寫」是一種在析構函數上的誤導。 當您覆蓋虛擬功能時,請將其替換。 的析構函數鏈,所以你不能覆蓋的析構函數字面

+1

我不會這麼說,重寫函數應該執行相同的語義任務(儘管派生版本應該更加專業化),而且替換也不是全部。在默認情況下,調用會解析爲對象的類型,您仍然可以使用scope運算符顯式調用基類函數。請參見https://stackoverflow.com/questions/38010286/how-can-i-call-virtual-函數的定義 - 的基級 - 即具備的,定義-i的 –