2008-09-24 89 views
57

我已經在C++中定義了一個接口,即僅包含純虛函數的類。GNU編譯器警告「類具有虛擬函數但非虛擬析構函數」

我想明確地禁止界面的用戶通過一個指向接口刪除的對象,所以我宣佈的接口保護和非虛析構函數,是這樣的:

class ITest{ 
public: 
    virtual void doSomething() = 0; 

protected: 
    ~ITest(){} 
}; 

void someFunction(ITest * test){ 
    test->doSomething(); // ok 
    // deleting object is not allowed 
    // delete test; 
} 

的GNU編譯器給我一個警告說:

類「ITEST」有虛函數,但非虛析構函數

一旦析構函數被保護,它在虛擬還是非虛擬方面有什麼區別?

您是否認爲這個警告可以安全地忽略或沉默?

回答

64

這或多或少是編譯器中的一個bug。請注意,在更新的編譯器版本中,此警告不會被拋出(至少在4.3版本中不會)。讓析構函數受到保護並且非虛擬的在你的情況下是完全合法的。

查看here Herb Sutter關於這個問題的出色文章。從文章:

準則#4:基類析構函數應該是public和virtual,或protected和nonvirtual。

0

如果析構函數是虛擬的,它會確保基類析構函數也被稱爲執行清理,否則可能會導致該代碼產生一些泄漏。所以你應該確保程序沒有這樣的警告(完全沒有警告)。

9

對這個答案的一些評論與我給出的早期答案有關,這是錯誤的。

受保護的析構函數意味着它只能從基類中調用,而不能通過刪除。這意味着ITest *不能直接刪除,只有派生類可以。派生類可能需要虛擬析構函數。你的代碼完全沒有問題。

但是,由於您無法在GCC中本地禁用警告,並且您已經有了一個vtable,因此您可以考慮只是使析構函數變爲虛擬。它會花費你4個字節的程序(而不是每個類實例),最大值。既然你可能已經給了你的派生類一個虛擬的Dtor,你可能會發現它沒有花費任何東西。

+1

我總是想知道爲什麼析構函數不總是虛擬的... – INS 2008-09-24 14:23:26

+2

2個原因。 (1)您只需支付您在C++中使用的內容。如果虛擬機是虛擬的,那麼很多類都會有不需要的虛擬機。 (2)我認爲某些框架(如CORBA)現在依賴於當前的行爲。 – 2008-09-24 14:28:39

+0

Richard和Airsource,我刪除了引用上一個答案的評論。我現在對你的答案沒有任何問題,並將我的初始-1改爲+1 ... – 2008-09-24 16:49:38

0

如果你有ITest的方法之一,試圖delete本身(一個壞主意,但合法)的代碼,派生類的析構函數將不會被調用。即使你不打算通過基類指針刪除派生實例,你仍然應該使你的析構函數變爲虛擬的。

4

如果您堅持這樣做,請繼續並將-Wno-non-virtual-dtor傳遞給GCC。此警告似乎並未默認打開,因此您必須使用-Wall-Weffc++啓用它。不過,我認爲這是一個有用的警告,因爲在大多數情況下,這將是一個錯誤。

2

它是一個接口類,因此您不應該通過該接口刪除實現該接口的對象。這種情況的一個常見情況是由工廠創建的應該返回工廠的對象的接口。 (讓對象包含一個指向他們工廠的指針可能會相當昂貴)。

我同意GCC發牢騷的觀察。相反,它應該只是在您刪除ITest *時發出警告。這就是真正的危險所在。

2

我個人認爲你會做正確的事情,編譯器壞了。如果可能的話,我會禁用警告(本地定義界面的文件)

我發現我使用這種模式(小'p')相當多。事實上,我發現我的接口具有受保護的dtors比擁有公共接口更常見。然而,我不認爲它實際上是一種常見的習慣用法(它沒有講得那麼多),我猜想當警告被添加到GCC時,適當的做法是嘗試執行更老的'如果你有虛擬功能「的規則。就我個人而言,我更新了這個規則,'如果你有虛擬功能必須是虛擬的,並且希望用戶能夠通過接口刪除接口的實例,否則應該保護該虛擬設備並且非常虛擬');