2009-10-30 96 views
30

有誰知道爲什麼STL容器沒有虛擬析構函數?爲什麼STL容器沒有虛擬析構函數?

據我所知,唯一的好處是:

  • 它由一個指針(到虛擬方法表)和
  • 減少一個實例的大小它使破壞和結構的小位更快。

缺點是以通常的方式子集化容器是不安全的。

編輯: 也許我的問題可以改寫爲什麼不是STL容器設計允許繼承?

因爲它們不支持繼承,所以當想要有一個需要STL功能的新容器加上少量附加功能時(例如,使用默認值的專門構造函數或新訪問器對地圖,或其他):

  • 組成和接口複製:使擁有該STL容器作爲私有成員,每種方法STL的一個交叉聯方法一個新的模板或類。這與繼承一樣有效,避免了虛擬方法表(在重要的情況下)的成本。不幸的是,STL容器有相當寬泛的接口,所以這需要許多代碼來完成一些看起來應該很容易的事情。
  • 只是使功能:使用裸(可能模板化)文件範圍的功能,而不是嘗試添加成員函數。在某些方面,這可能是一個很好的方法,但封裝的好處將會丟失。
  • 具有公共STL訪問的組合:讓STL容器的所有者允許用戶訪問STL容器本身(可能通過訪問者保護)。這需要庫編程人員的最少編碼,但對用戶來說不太方便。組合的最大賣點之一是可以減少代碼中的耦合,但是該解決方案將STL容器與所有者容器完全耦合(因爲所有者返回了真正的STL容器)。
  • 編譯時多態性:寫操作可能有點棘手,需要一些代碼體操,並且不適合所有情況。

作爲一個方面的問題:是否有與非虛析構函數(我們假設我不希望覆蓋任何方法,只是我要添加新的)繼承的標準安全方式?我的印象是,如果沒有能力改變定義非虛擬類的代碼,那麼沒有通用的和安全的方法來做到這一點。

編輯2:

作爲@doc points out,C++ 11的發燒友using聲明低的組合物的成本有所。

+5

你錯過的 「它不鼓勵使用繼承不當程序員」 的好處。從STL容器繼承可能有一個合理的理由,但我從來沒有找到過。 – Tom 2009-10-30 03:14:25

+2

並回答你的問題:'喜歡組成繼承'。 – 2009-10-30 07:22:39

+0

你爲什麼想要繼承它? – jalf 2009-10-30 12:03:06

回答

12

我想它遵循不支付你不使用的功能的C++哲學。根據平臺的不同,如果您不關心虛擬析構函數,虛擬表的指針可能會帶來巨大的代價。

+0

你是誰?如果我想使用?如果這是最終答案,爲什麼要提供虛擬表格,類和所有這些東西?所有這一切都需要花費。仍然具有洞察力,但不是最後一個...... – ribamar 2015-05-20 08:27:30

+0

「設計不支付你不使用的功能」確實是設計C++時的一個設計目標,並沒有爲STL容器添加虛擬表格。 – 2015-05-20 15:42:50

29

虛擬析構函數僅用於繼承場景。 STL容器不是爲了繼承而設計的(也不是支持的場景)。因此他們沒有虛擬析構函數。

+5

我從面試官那裏發現的一件事是,大多數程序員似乎並不知道繼承必須由基類設計。當人們意識到燈泡開啓時,它很棒,「它沒有虛擬析構函數,因爲我不應該繼承它。」 – Tom 2009-10-30 03:03:48

+1

s/interviewers/interviews/ – Tom 2009-10-30 03:04:53

+4

您能否提供STL容器不是爲繼承而設計的聲明? – doc 2012-12-14 15:19:09

-2

沒有虛擬析構函數阻止該類正確地作爲子類。

+3

不是。你可以繼承任何沒有虛擬析構函數的類。你將遇到的唯一問題是在使用多態性的情況下,將不會調用繼承類的析構函數。所以沒有虛擬析構函數並不妨礙正確的繼承。 – 2010-09-01 17:04:33

17

我認爲斯特勞斯在他神奇的紙間接地回答了這個問題:Why C++ is not just an ObjectOriented Programming Language

7閉幕詞
高於 提出的各種 設施面向對象的或沒有?哪個? 使用什麼定義 面向對象?在大多數情況下,我認爲這些都是錯誤的問題。 重要的是什麼想法,你可以 表達清楚,多麼容易,你可以從 不同 源相結合,軟件,以及如何高效, 維護所產生的程序 是。換句話說,你如何支持 好的編程技巧和好的 設計技術比 更重要的標籤和熱門詞彙。根本的想法僅僅是通過抽象來改進設計和編程。你想要隱藏的細節,你想 利用系統中的任何共同點, ,你想讓這個負擔得起。 我想鼓勵你不要讓 使面向對象無意義的 術語。通過 具有良好的等同它,

- - 通過一個單一的語言等同 ,或

- 的'面向對象' 概念過於頻繁地貶低

通過 接受一切, 面向對象。

我認爲 有 - 並且必須是 - 有用的 技術超出了面向對象的 編程和設計。然而, 避免被誤解完全,我 想強調的是,我 不會使用一種編程語言, 沒有至少支持經典 概念對象編程的是嚴肅認真的項目 。 除了支持設施 對象編程,我想 - 和C++提供了 - 那去 超出他們的 的概念直接表達和 關係支持功能。

STL主要以三個概念工具爲主。 泛型編程+功能風格+數據抽象== STL風格。 OOP不是代表數據結構&算法庫的方式並不奇怪。儘管在標準庫的其他部分使用了OOP,但STL的設計人員看到,上述三種技術的組合優於單獨使用OOP 。簡而言之,這個庫並不是用OOP設計的,在C++中如果你不使用它,它不會與你的代碼捆綁在一起。你不支付你不使用的東西。類std :: vector,std :: list,...是而不是 Java/C#意義上的OOP概念。他們只是抽象數據類型在最好的解釋。

+7

詳細和有點離題的答案。 – Sake 2009-10-30 00:52:05

+5

真正的問題是他們爲什麼需要他們?他們不。 – AraK 2009-10-30 00:59:42

+1

我認爲這個答案肯定需要一個編輯來糾正Stroustrup的文章的名稱:爲什麼C++不是**只是一個**面向對象的編程語言。 – rturrado 2010-10-12 16:23:18

1

正如已經指出的那樣,STL容器並非設計爲可繼承的。沒有虛擬方法,所有數據成員都是私有的,沒有受保護的getters/setters/helpers ..並且正如你所發現的,沒有虛擬析構函數..

我建議你真的應該通過合成來使用容器,而不是實現繼承,採用「一種」方式而不是「一種」方式。

+1

您可以請求源語言STL容器不是爲繼承而設計的嗎?數據成員幾乎都是私人的。沒有虛擬方法,因爲它們在模板編程中通常很少見。缺乏受保護的方法也不是說明階級不可繼承的原因。 – doc 2012-12-14 15:46:34

1

你不應該盲目地爲每個類添加一個虛擬析構函數。如果是這樣的話,這種語言不會讓你有任何其他選擇。將虛擬方法添加到沒有任何其他虛擬方法的類中時,只需按指針大小(通常爲4個字節)增加類實例的大小。這很貴,取決於你在做什麼。尺寸增加是因爲創建了一個v表來保存虛擬方法列表,並且每個實例都需要一個指向v表的指針。它通常位於實例的第一個單元格中。

7

爲什麼STL容器設計不允許繼承?

在我的愚見中他們是。如果他們不這樣做,他們已經取得最後的。當我看着stl_vector.h源我可以看到我的STL實現使用保護繼承_Vector_base<_Tp, _Alloc>授予派生類的訪問:

template<typename _Tp, typename _Alloc = allocator<_Tp> > 
class vector : protected _Vector_base<_Tp, _Alloc> 

那豈不是用私人繼承,如果子類是不歡迎?


有非虛析構函數(我們假設我不希望覆蓋任何方法,只是我要添加新的)繼承的標準安全方式?

爲什麼不使用protectedprivate繼承與using關鍵字暴露接口的所需部分?

class MyVector : private std::vector<int> 
{ 
    typedef std::vector<int> Parent; 

    public: 
     using Parent::size; 
     using Parent::push_back; 
     using Parent::clear; 
     //and so on + of course required ctors, dtors and operators. 
}; 

這種做法確保了類的用戶不會垂頭喪氣實例std::vector<int>,他是安全的,因爲與非虛析構函數的唯一問題是,它不會叫得出的一個,當對象被刪除作爲父類的一個實例。

...我也有一個鬆散的想法,即使你的類沒有析構函數,你甚至可能會公開地繼承它。異端?

+0

來自STL的創建者Alexander Stepanov http://www.stlport.org/resources/StepanovUSA.html 「是的,STL不是面向對象的,我認爲面向對象幾乎和人工的一樣惡作劇智能我還沒有看到來自這些OO人員的一段有趣的代碼。「 – Joe 2017-10-17 16:05:26

0

另一種能夠從STL容器中進行子類化的解決方案是由Bo Qian使用智能指針提供的。

Advanced C++: Virtual Destructor and Smart Destructor

class Dog { 
public: 
    ~Dog() {cout << "Dog is destroyed"; } 
}; 

class Yellowdog : public Dog { 
public: 
    ~Yellowdog() {cout << "Yellow dog destroyed." << endl; } 
}; 


class DogFactory { 
public: 
    static shared_ptr<Dog> createYellowDog() { 
     return shared_ptr<Yellowdog>(new Yellowdog()); 
    }  
}; 

int main() { 
    shared_ptr<Dog> pd = DogFactory::createYellowDog(); 

    return 0; 
} 

這避免了與完全虛擬的析構函數面臨的困境。

+0

什麼是工廠模式?人們可以簡單地做'std :: shared_ptr p = std :: make_shared ();'。與虛擬dtor相比,不會有任何性能提升,相反,因爲'shared_ptr'在後臺做了很多事情,比如引用計數。 – doc 2016-08-06 20:49:46

+0

你是對的,工廠模式不是必需的,但它是強制執行界面的好方法。我只是複製了博謙的例子。關鍵不在於性能增益。使用智能指針只是另一種從STL容器進行子類化的方法,而不必擔心虛擬dtors。 – 2016-08-07 02:57:35

-1

如果你確實需要虛擬析構函數,你可以將它添加到從矢量<>派生的類中,然後在需要虛擬接口的任何地方使用這個類作爲基類。通過這樣做,編譯器會從你的基類中調用虛擬析構函數,而後者將從矢量類中調用非虛擬析構函數。

例子:

#include <vector> 
#include <iostream> 

using namespace std; 

class Test 
{ 
    int val; 
public: 
    Test(int val) : val(val) 
    { 
     cout << "Creating Test " << val << endl; 
    } 
    Test(const Test& other) : val(other.val) 
    { 
     cout << "Creating copy of Test " << val << endl; 
    } 
    ~Test() 
    { 
     cout << "Destructing Test " << val << endl; 
    } 
}; 

class BaseVector : public vector<Test> 
{ 
public: 
    BaseVector() 
    { 
     cout << "Creating BaseVector" << endl; 
    } 
    virtual ~BaseVector() 
    { 
     cout << "Destructing BaseVector" << endl; 
    } 
}; 

class FooVector : public BaseVector 
{ 
public: 
    FooVector() 
    { 
     cout << "Creating FooVector" << endl; 
    } 
    virtual ~FooVector() 
    { 
     cout << "Destructing FooVector" << endl; 
    } 
}; 

int main() 
{ 
    BaseVector* ptr = new FooVector(); 
    ptr->push_back(Test(1)); 
    delete ptr; 

    return 0; 
} 

該代碼給出了下面的輸出:

Creating BaseVector 
Creating FooVector 
Creating Test 1 
Creating copy of Test 1 
Destructing Test 1 
Destructing FooVector 
Destructing BaseVector 
Destructing Test 1 
+0

有了這個解決方案,它仍然可以刪除真正的基類(std :: vector)和泄漏。 – U007D 2016-10-26 05:10:35

相關問題