为什么STL容器没有虚拟析构函数?

时间:2009-10-30 00:11:33

标签: c++ stl destructor

有谁知道为什么STL容器没有虚拟析构函数?

据我所知,唯一的好处是:

  • 它通过一个指针(到虚拟方法表)和
  • 来减小实例的大小
  • 它使破坏和构造更快一点。

缺点是以通常的方式对容器进行子类化是不安全的。

修改 也许我的问题可以改写为“为什么不是STL容器设计为允许继承?”

因为它们不支持继承,所以如果想要拥有一个需要STL功能的新容器以及少量附加功能(比如一个专门的构造函数或具有默认值的新访问器),就会遇到以下选择。地图,或其他):

  • 组合和接口复制:创建一个拥有STL容器作为私有成员的新模板或类,并为每个STL方法提供一个直通内联方法。这与继承一样高效,避免了虚方法表的成本(在重要的情况下)。不幸的是,STL容器具有相当广泛的接口,因此需要许多代码行才能看起来容易做到。
  • 只需创建函数:使用裸(可能是模板化的)文件范围的函数,而不是尝试添加成员函数。在某些方面,这可能是一个很好的方法,但封装的好处将丢失。
  • 具有公共STL访问权限的组合:让STL容器的所有者允许用户访问STL容器本身(可能通过访问者保护)。这需要编写库编写器的编码最少,但对用户来说却不太方便。组合的一大卖点是减少代码中的耦合,但此解决方案将STL容器与所有者容器完全耦合(因为所有者返回一个真正的STL容器)。
  • 编译时多态性:写作可能有些棘手,需要一些代码体操,并不适合所有情况。

作为一个附带问题:是否存在一种使用非虚拟析构函数进行子类化的标准安全方法(让我们假设我不想覆盖任何方法,只是我想添加新方法)?我的印象是,如果没有能力更改定义非虚拟类的代码,就没有通用和安全的方法。

编辑2:

作为@doc points out,C ++ 11的发烧友using声明会稍微降低作曲成本。

8 个答案:

答案 0 :(得分:30)

虚拟析构函数仅对继承方案有用。 STL容器不是为继承而设计的(也不是受支持的方案)。因此他们没有虚拟析构函数。

答案 1 :(得分:17)

我认为Stroustrup在他的精彩论文中间接地回答了这个问题:Why C++ is not just an ObjectOriented Programming Language

  

7结束语
  是各种各样的   上面提到的设施   面向对象与否?哪个?   使用什么定义   面向对象?在大多数情况下,我   认为这些是错误的问题。   重要的是你可以提出什么想法   表达清楚,你能多轻松   结合不同的软件   来源,以及如何有效和   维护由此产生的程序   是。换句话说,你是如何支持的   良好的编程技巧和良好的   设计技术不仅仅重要   标签和流行语。根本   想法只是改善设计和   通过抽象编程。您   想隐藏细节,你想要   利用系统中的任何共性,   而且你想让这个价格实惠。   我想鼓励你不要   使面向对象变得毫无意义   术语。 “面向对象”的概念   经常贬值

     

- by   把它等同于好,

     

- 通过等同   用一种语言,或

     

- by   接受一切   面向对象的。

     

我认为   有 - 而且必须 - 有用   超越面向对象的技术   编程和设计。但是,要   避免被完全误解,我   我想强调一下   不会尝试一个严肃的项目   使用编程语言   至少不支持古典音乐   面向对象编程的概念。   除了支持的设施   面向对象的编程,我想 -   和C ++提供 - 功能   超出他们的支持   直接表达概念和   关系。

STL主要是以三个概念工具为基础构建的。 通用编程+功能样式+数据抽象== STL样式。 OOP不是代表数据结构的最佳方式并不奇怪。算法库。尽管OOP用于标准库的其他部分,但STL的设计者发现,上述三种技术的混合优于单独的OOP 。简而言之,该库不是为OOP而设计的,而在C ++中如果不使用它,它就不会与您的代码捆绑在一起。你不支付你不使用的东西。类std :: vector,std :: list,...是 Java / C#意义上的OOP概念。它们只是抽象数据类型的最佳解释。

答案 2 :(得分:12)

我想它遵循C ++的理念,即不支付你不使用的功能。根据平台的不同,如果您不关心虚拟析构函数,虚拟表的指针可能需要支付高昂的代价。

答案 3 :(得分:7)

  

为什么STL容器不能设计为允许继承?

我认为他们是。如果他们不这样做,他们就会成为 final 。当我查看stl_vector.h源代码时,我可以看到我的STL实现使用_Vector_base<_Tp, _Alloc> protected 继承来授予派生类的访问权限:

 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>并且他是安全的,因为非虚拟析构函数的唯一问题是当对象被删除时它不会调用派生的析构函数作为父类的一个实例。

...我也有一个宽松的想法,如果你的班级没有析构函数,你甚至可以公开继承。异端?

答案 4 :(得分:1)

正如已经指出的那样,STL容器的设计不是可继承的。没有虚拟方法,所有数据成员都是私有的,没有受保护的getter / setters / helpers ..正如你所发现的,没有虚拟析构函数..

我建议你应该通过组合而不是实现继承来使用容器,而不是“a-a”方式。

答案 5 :(得分:1)

你不应该盲目地为每个班级添加虚拟析构函数。如果是这种情况,该语言将不允许您任何其他选项。将虚方法添加到没有任何其他虚方法的类时,只需按指针大小增加类实例的大小,通常为4个字节。这取决于你正在做什么,这是昂贵的。大小增加的原因是创建了一个用于保存虚拟方法列表的v表,并且每个实例都需要一个指向v表的指针。它通常位于实例的第一个单元格。

答案 6 :(得分:-1)

如果您确实需要虚拟析构函数,可以在派生自vector&lt;&gt;的类中添加它,然后在需要虚拟接口的任何地方使用此类作为基类。通过执行此操作,compilator将从您的基类调用虚拟析构函数,而后者将从向量类调用非虚拟析构函数。

示例:

#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

答案 7 :(得分:-2)

没有虚析构函数可以防止该类成为正确的子类。