2009-10-30 168 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

相关问题