2012-03-28 54 views
7

我一直聽說你不應該從沒有虛擬析構函數的類繼承,而且我也沒有多加註意,因爲我不經常使用繼承。即使你不想使用多態性,這個規則是否適用,但你只是想要所有類的功能,並且你想添加更多?具體而言,只要我沒有多態使用它,下面的類是否安全,具有明確定義的行爲? (即不刪除基地指針派生的對象)從沒有虛擬析構函數的類繼承

template<typename T> 
class SomewhatSafeVector : public std::vector<T> 
{ 
public: 
    typedef std::vector<T> base; 

    T& operator[](unsigned n) { 
     if (n >= base::size()) 
     { 
      throw IndexOutOfBounds(); 
     } 
     return base::operator[](n); 
    } 
}; 
+1

不要介意它是否正常,但你仍然不應該從標準庫容器派生。此外,如果您在訪問動態容器時遇到困難,您可能更願意考慮您的全局算法思維(想一想「0-1-many」和「ranges」),因爲out-bound-bound訪問是通常是一個*邏輯*錯誤。 – 2012-03-28 14:31:13

+4

我認爲在你的具體例子中,繼承並不是一個非常優雅的解決方案,因爲繼承意味着接口重用,而不是實現重用。你顯然不會重用接口,因爲你的'operator []'拋出了一個'std :: vector'不會的異常。如果要重用代碼,只需使用普通共享函數或(如本例中那樣),使'std :: vector'成爲'SomewhatSafeVector'的成員。 – 2012-03-28 14:31:40

+0

@KerrekSB:首先,爲什麼不呢?第二,我沒有這樣的麻煩。但是我認爲邊界檢查容器對於教學和調試目的來說是個好主意。 – 2012-03-28 14:34:22

回答

6

我一直聽說,你不應該不虛析構函數

這是給初學者,因爲解釋所有複雜花費太多時間,這是一個經驗法則從類繼承只是更安全(對於實踐項目來說並不那麼昂貴),實際上只給他們幾條工作時間很長的基線(儘管可能是矯枉過正)。

在基類中,如果不使用virtual析構函數,則可以完美地使用繼承。另一方面,如果基類完全沒有virtual方法,那麼繼承可能是工作的錯誤工具。 例如:你的情況,如果我用SafeVector<T> sv; sv[3];那麼它是安全的,但如果我這樣做std::vector<T>& v = sv; v[3];它不是...這是因爲你只是躲在基類的方法,而不是覆蓋它(殺青你的警告級別,他們會讓你知道)。

這裏的正確方法是使用組合,然後爲實際使用的方法爲實現成員創建轉發方法。在實踐中,它會變得累人,因爲C++不支持委託(using attribute.insert;),所以很多人都會繼承...

另一種方法是提供更安全的方法作爲自由方法,因爲您可以隨時添加免費方法而不受限制。對於那些擁有「面向對象」思維的人來說,這可能不那麼習慣,而且有些操作員不能這麼加。

5

如果你不打算使用多態類(不刪除基礎指針派生的對象),那麼它是不是未定義行爲。

參考:

C++ 03標準:5.3.5刪除

5.3.5/1:

該刪除表達式運算符銷燬大多數派生對象(1.8)或由新表達式創建的數組。
刪除表達式:
::選擇刪除鑄表達
::選擇刪除[]鑄表達

5.3.5/3:

在第一如果操作數的靜態類型與其動態類型不同,靜態類型應該是操作數動態類型的基類,並且靜態類型應該具有虛擬析構函數或行爲未定義。在第二種方式(刪除陣列)如果動態類型的對象從它的靜態類型刪除不同,該行爲是undefined.73)

4

歡迎您多態的使用對象,你才能它是多態的。如果您避免通過std::vector<>*刪除指向您班級對象的指針,那麼您是安全的。

除了:您可能簡化您operator[]這樣的:

T& operator[](unsigned n) { return this->at(n); } 
2

是的,如果你從來沒有使用多態(即從來沒有上溯造型引用或指針),沒有辦法進行不安全的破壞。

Mixin類通常以這種方式使用,並且CRTP很少隱含虛擬析構函數,以命名一些模式。