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

发布于 2024-08-08 23:07:02 字数 864 浏览 3 评论 0原文

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

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

  • 它通过一个指针(指向虚拟方法表)减少了实例的大小,并且
  • 使破坏和构造速度更快了一点。

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

我的问题可以重新表述的另一种方式是“为什么 STL 容器没有设计为允许继承?”

因为它们不支持继承,所以当你想要一个需要 STL 功能加上少量附加功能的新容器时(比如一个专门的构造函数或具有映射默认值的新访问器,或其他):

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

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

Does anyone know why the STL containers don't have virtual destructors?

As far as I can tell, the only benefits are:

  • it reduces the size of an instance by one pointer (to the virtual method table) and
  • it makes destruction and construction a tiny bit faster.

The downside is that it's unsafe to subclass the containers in the usual way.

Another way my question could be rephrased is "Why weren't STL containers designed to allow for inheritance?"

Because they don't support inheritance, one is stuck with the following choices when one wants to have a new container that needs the STL functionality plus a small number of additional features (say a specialized constructor or new accessors with default values for a map, or whatever):

  • Composition and interface replication: Make a new template or class that owns the STL container as a private member and has one pass-through inline method for each STL method. This is just as performant as inheritance, avoids the cost of a virtual method table (in the cases where that matters). Unfortunately, the STL containers have fairly broad interfaces so this requires many lines of code for something that should seemingly be easy to do.
  • Just make functions: Use bare (possibly templated) file-scoped functions instead of trying to add member functions. In some ways this can be a good approach, but the benefits of encapsulation are lost.
  • Composition with public STL access: Have the owner of the STL container let users access the STL container itself (perhaps guarded through accessors). This requires the least coding for the library writer, but it's much less convenient for users. One of the big selling points for composition is that you reduce coupling in your code, but this solution fully couples the STL container with the owner container (because the owner returns a true STL container).
  • Compile-time polymorphism: Can be somewhat tricky to do right, requires some code gymnastics, and isn't appropriate for all situations.

As a side question: is there a standards-safe way of subclassing with non-virtual destructors (let's assume that I don't want to override any methods, just that I want to add new ones)? My impression is that there is no generic and safe way of doing this if one does not have the power to change the code defining the non-virtual class.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

妥活 2024-08-15 23:07:02

虚拟析构函数仅适用于继承场景。 STL 容器并非设计为继承(也不是受支持的场景)。因此他们没有虚拟析构函数。

A virtual destructor is only useful for inheritance scenarios. STL containers are not designed to be inherited from (nor is it a supported scenario). Hence they don't have virtual destructors.

枕梦 2024-08-15 23:07:02

我认为 Stroustrup 在他精彩的论文中间接回答了这个问题:为什么 C++ 不仅仅是一种面向对象编程语言

7 结束语
是否有各种
上述设施
是否面向对象?哪些?
使用什么定义
面向对象?在大多数情况下,我
认为这些问题都是错误的。
重要的是你可以提出什么想法
清楚地表达,你可以多么容易
结合不同的软件
来源,以及如何有效和
可维护的结果程序
是。换句话说,你如何支持
良好的编程技术和良好的
设计技术比设计更重要
标签和流行语。基本的
想法只是为了改进设计和
通过抽象进行编程。你
想要隐藏细节,你想要
利用系统中的任何共性,
并且您想让这个价格变得实惠。
我想鼓励你不要
让面向对象变得毫无意义
学期。 “面向对象”的概念
经常被贬低

——由
将其等同于好,

——通过等式
使用单一语言,或者

——由
接受一切作为
面向对象。

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

STL 的构建主要考虑了三种概念工具。 通用编程+函数式风格+数据抽象== STL风格。毫不奇怪,OOP 并不是表示数据结构和数据结构的最佳方式。算法库。尽管标准库的其他部分也使用了 OOP,但 STL 的设计者发现,上述三种技术的组合比单独的 OOP 更好。简而言之,该库在设计时并未考虑到 OOP,并且在 C++ 中,如果您不使用它,它就不会与您的代码捆绑在一起。您无需为不使用的东西付费。类 std::vector、std::list...在 Java/C# 意义上不是 OOP 概念。在最好的解释中,它们只是抽象数据类型

I think Stroustrup answered this question indirectly in his fantastic paper: Why C++ is not just an ObjectOriented Programming Language:

7 Closing Remarks
Are the various
facilities presented above
objectoriented or not? Which ones?
Using what definition of
objectoriented? In most contexts, I
think these are the wrong questions.
What matters is what ideas you can
express clearly, how easily you can
combine software from different
sources, and how efficient and
maintainable the resulting programs
are. In other words, how you support
good programming techniques and good
design techniques matters more than
labels and buzz words. The fundamental
idea is simply to improve design and
programming through abstraction. You
want to hide details, you want to
exploit any commonality in a system,
and you want to make this affordable.
I would like to encourage you not to
make objectoriented a meaningless
term. The notion of ‘‘objectoriented’’
is too frequently debased

– by
equating it with good,

– by equating
it with a single language, or

– by
accepting everything as
objectoriented.

I have argued that
there are – and must be – useful
techniques beyond objectoriented
programming and design. However, to
avoid being totally misunderstood, I
would like to emphasize that I
wouldn’t attempt a serious project
using a programming language that
didn’t at least support the classical
notion of objectoriented programming.
In addition to facilities that support
objectoriented programming, I want –
and C++ provides – features that go
beyond those in their support for
direct expression of concepts and
relationships.

STL was built with three conceptual tools in mind mainly. Generic Programming + Functional Style + Data Abstraction == STL Style. It is not strange that OOP is the not the best way to represent a Data Structure & Algorithms library. Although OOP is used in other parts of the standard library, the designer of STL saw that the mix of the three mentioned techniques is better than OOP alone. In short, the library wasn't designed with OOP in mind, and in C++ if you don't use it, it doesn't get bundled with your code. You don't pay for what you don't use. The classes std::vector, std::list,... are not OOP concepts in the Java/C# sense. They are just Abstract Data Types in the best interpretation.

贪恋 2024-08-15 23:07:02

我想它遵循了 C++ 的哲学,即不为不使用的功能付费。根据平台的不同,如果您不关心虚拟析构函数,则虚拟表的指针可能会付出高昂的代价。

I guess it follows the C++ philosophy of not paying for features that you don't use. Depending on the platform, a pointer for the virtual table could be a hefty price to pay if you don't care about having a virtual destructor.

ぶ宁プ宁ぶ 2024-08-15 23:07:02

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

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

Why weren't STL containers designed to allow for inheritance?

In my humble opinion they are. If they wouldn't, they had been made final. And when I look into stl_vector.h source I can see that my STL implementation uses protected inheritance of _Vector_base<_Tp, _Alloc> to grant access for derived classes:

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

Wouldn't it use private inheritance if subclassing was not welcome?


is there a standards-safe way of subclassing with non-virtual destructors (let's assume that I don't want to override any methods, just that I want to add new ones)?

Why not use protected or private inheritance and expose desired part of interface with using keyword?

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.
};

This approach ensures that the user of the class will not upcast an instance to std::vector<int> and he is safe, since the only problem with non-virtual destructor is that it won't call derived one, when object gets deleted as an instance of parent class.

...I have also loose idea, that you may even inherit publicly if your class doesn't have a destructor. Heresy?

零崎曲识 2024-08-15 23:07:02

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

you're not supposed to blindly add a virtual destructor to every class. If that were the case, the language wouldn't allow you any other option. When you add a virtual method to a class that doesn't have any other virtual methods, you just increased the size of the class instances by the size of a pointer, typically 4 bytes. That's expensive depending on what you're doing. The size increase happens because a v-table is created to hold the list of virtual methods, and each instance needs a pointer back to the v-table. It's typically located at the first cell of the instance.

触ぅ动初心 2024-08-15 23:07:02

能够从 STL 容器子类化的另一种解决方案是 Bo Qi 给出的使用智能指针的解决方案。

高级 C++:虚拟析构函数和智能析构函数

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;
}

完全避免了虚拟析构函数的困境。

Another solution to be able to subclass from STL containers is one given by Bo Qian using smart pointers.

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;
}

This avoids the dillema with virtual destructors altogether.

执手闯天涯 2024-08-15 23:07:02

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

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

As has been pointed out, the STL containers are not designed to be inheritable. No virtual methods, all data members are private, no protected getters/setters/helpers.. And as you've discovered, no virtual destructors..

I'd suggest you should really be using the containers via composition rather than implementation inheritance, in a "has-a" way rather than an "is-a" one.

橘亓 2024-08-15 23:07:02

如果您确实需要虚拟析构函数,则可以将其添加到从 Vector<> 派生的类中,然后在需要虚拟接口的任何地方使用该类作为基类。通过执行此操作,编译器将从基类调用虚拟析构函数,而基类又将从向量类调用非虚拟析构函数。

示例:

#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

If you really need virtual destructor, you can add it in class derived from vector<>, and then use this class as a base class everywhere you need virtual interface. By doing this compilator will call virtual destructor from your base class, which in turn will call non-virtual destructor from vector class.

Example:

#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;
}

This code gives following output:

Creating BaseVector
Creating FooVector
Creating Test 1
Creating copy of Test 1
Destructing Test 1
Destructing FooVector
Destructing BaseVector
Destructing Test 1
独闯女儿国 2024-08-15 23:07:02

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

No virtual destructor prevents the class from being subclasses correctly.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文