35

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.

Mr Fooz
  • 109,094
  • 6
  • 73
  • 101
  • 5
    You missed the benefit of "it discourages programmers from using inheritance inappropriately". There may be a valid reason to inherit from an STL container, but I have never found one. – Tom Oct 30 '09 at 03:14
  • 2
    And to answer your question: `Prefer Composition to Inheritance`. – Matthieu M. Oct 30 '09 at 07:22
  • 9
    "In some ways [non-member functions] can be a good approach, but the benefits of encapsulation are lost". This is false. Encapsulation is completely respected when you work using only the public interface. You can either manipulate the container (like the search/sort algorithms), or you can adapt it (like std::stack). Don't mistake member-function syntax for encapsulation of abstractions, they're completely independent things. – Steve Jessop Oct 30 '09 at 14:38
  • 1
    Because the STL is not an OO-framework. – fredoverflow Apr 25 '10 at 14:13
  • 1
    @MatthieuM. Composition and inheritance are not interchangeable concepts and there's no such rule as `prefer Inheritance\Composition over Composition\Inheritance`. If your class is extending functionality of the former class, then you should inherit. – mip Dec 14 '12 at 14:28
  • 1
    @doc: no, extending functionality *is not* a reason for inheriting. Inheritance means that you want to *pass for* the base class in a number of circumstances, which is only valid (in most cases) if the base class was explicitly designed for it and if your invariants allow it. In C++, **extending does not require classes**, free-functions are fine! If the base class has no `protected` or `virtual` method, you gain nothing by inheriting from it rather than writing free functions apart from a stronger coupling (!). – Matthieu M. Dec 14 '12 at 15:37
  • 2
    @MatthieuM. If your class is best described as a subtype of the former class then you should inherit. If you are creating a class called `MyVector`, which adds one method to standard `std::vector`, then don't tell me that composition fits to this scenario. And you may gain a lot by proper use of inheritance. If STL classes don't have virtual dtors/methods to keep vtable away, then you can have the very same by using inheritance. And with composition your class will grow by the size of composition member - a cost similar to creation of vtable. – mip Dec 14 '12 at 16:12
  • 2
    @MatthieuM. I am afraid that it's not me, who don't understand. You should refer to my arguments and not just blindly repeat a "mantra" (as you properly call it). And don't mess inheritance with polymorphism. If class doesn't have virtual methods, it doesn't mean that you can't inherit. – mip Dec 14 '12 at 17:27
  • @doc: Well, I have Herb Sutter and Andrei Alexandrescu on my side [C++ Coding Standards, Item 34, Prefer Composition To Inheritance](http://www.artima.com/cppsource/codestandards3.html). Note that there is a list of exceptions attached, where inheritance brings advantages over composition and thus cannot be bypassed. So, let's refute the exceptions: (1) and (4) are not applicable (no `virtual` here), (2) is not applicable (no `protected` here), (3) is not applicable (single base class here), (5) is not applicable (`vector` is not empty); this leaves us with (6)... – Matthieu M. Dec 14 '12 at 19:32
  • 1
    @doc: as for your args: *And with composition your class will grow by the size of composition member* => non-sense, in the C++ object model non-virtual inheritance is implemented by composition under the hood. *a cost similar to creation of vtable* => there is a **v-pointer** only if there is a `virtual` method somewhere. The problem of STL not having `virtual` methods is that you **cannot override non-virtual methods**. Particularly troublesome for destructors since calling `delete` on a non-virtual destructor is **undefined behavior**. I was not sarcastic, read about the C++ object model. – Matthieu M. Dec 14 '12 at 19:36
  • 2
    @MatthieuM. this is not necessarily true. Please read: http://www.cantrip.org/emptyopt.html . And according to your previous comment I must say that Alexandrescu is wrong here, perhaps he he forget why inheritance was invented. If coupling is evil, then maybe we shall value plain functions over composition and then value assembler over functions. He's missing whole idea behind object oriented programming. In fact it's the opposite - strong coupling is good and it helps create more robust, more productive, more intuitive, more logical and reusable code. It's all about design. – mip Dec 15 '12 at 09:44
  • @MatthieuM. " Particularly troublesome for destructors since calling delete on a non-virtual destructor is undefined behavior." - can you please source this? This would mean that you are not able to create object with non-virtual dtor on a heap and I never heard about such thing :O. – mip Dec 15 '12 at 11:52
  • @doc: Regarding calling `delete`, I am afraid I clipped the sentence. In: `Base* b = new Derived(); delete b;` the call to `delete b` is undefined behavior if `Base` does not have a `virtual` destructor. Regarding coupling: it's bad. And I won't say more because I am tired of those tiny boxes: they are clearly note appropriate for extended discussions. I am afraid I could never work with you, so let's agree to disagree. – Matthieu M. Dec 15 '12 at 13:53
  • 1
    A non-friend free function is *no less* encapsulated than a member function, if there are any `protected` members it's *more* encapsulated. In the case of deriving from a standard container, it's a wash as there's nothing `protected` for you to meddle with. – Caleth Jan 21 '22 at 16:21
  • @MatthieuM. I see I didn't mention back then Liskov Substitution Principle and ability to reuse functions, which work for base classes... – mip Jun 29 '22 at 22:34
  • @mip: LSP is an argument against inheritance of concrete classes (for specialization), it creates a brittle relationship. – Matthieu M. Jun 30 '22 at 08:46

9 Answers9

29

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.

JaredPar
  • 733,204
  • 149
  • 1,241
  • 1,454
  • 6
    One thing I've discovered from interviewers is that most programmers don't seem to grasp that inheritance must be designed-for by the base class. It's great seeing the lightbulb turn on when people realize, "it doesn't have a virtual destructor, because I'm not supposed to inherit from it." – Tom Oct 30 '09 at 03:03
  • 6
    Can you please source the statement that STL containers are not designed for inheritance? – mip Dec 14 '12 at 15:19
18

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.

iceiceice
  • 119
  • 3
  • 11
Khaled Alshaya
  • 94,250
  • 39
  • 176
  • 234
  • 7
    Verbose and somewhat off-topic answer. – Sake Oct 30 '09 at 00:52
  • 5
    The real question is why do they *need* them?! They don't. – Khaled Alshaya Oct 30 '09 at 00:59
  • 2
    I think this answer definitely needs an edit to correct the name of Stroustrup's article: Why C++ is not **just an** Object-Oriented Programming Language. – rturrado Oct 12 '10 at 16:23
  • So, I would vote Stroustrup down. I can't believe the reason to prevent people from inheriting from STL classes is that he wanted "features that go beyond those in their support for direct expression of concepts and relationships". But still this answer is insightful -- it really looks to me the answer is a matter of design. – ribamar May 20 '15 at 08:23
14

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.

Jim Buck
  • 20,482
  • 11
  • 57
  • 74
  • You who? And if I would like to use? If that is the final answer, why to provide virtual tables, classes, and all of those stuff? All of that comes at some expense. Answer still insightful, but not the final one... – ribamar May 20 '15 at 08:27
  • 1
    "not paying for features you don't use" was indeed a design goal when C++ was designed, and not adding a virtual table for the STL containers logically follows from this goal. – Jim Buck May 20 '15 at 15:42
7

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?

mip
  • 8,355
  • 6
  • 53
  • 72
  • 2
    From Alexander Stepanov, creator of STL http://www.stlport.org/resources/StepanovUSA.html "Yes. STL is not object oriented. I think that object orientedness is almost as much of a hoax as Artificial Intelligence. I have yet to see an interesting piece of code that comes from these OO people." – Joe Oct 17 '17 at 16:05
1

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.

zumalifeguard
  • 8,648
  • 5
  • 43
  • 56
1

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.

Al Conrad
  • 1,528
  • 18
  • 12
  • What for is factory pattern? One could simply do `std::shared_ptr p = std::make_shared();`. There won't be any performance gain compared to virtual dtor, rather the opposite, because `shared_ptr` does a lot of stuff in the background such as reference counting. – mip Aug 06 '16 at 20:49
  • You're right, the factory pattern is not needed, but it's a good way to enforce an interface. I just copied Bo Qian's example. The point is not regarding performance gain. Using smart pointers is just another way to subclass from STL containers without having to worry about virtual dtors. – Al Conrad Aug 07 '16 at 02:57
0

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.

ScaryAardvark
  • 2,855
  • 4
  • 30
  • 43
  • 1
    Can you please source the statement that STL containers are not designed for inheritance? Data members are almost always private. There are no virtual methods cause they are generally rare in templates programming. Lack of protected methods also isn't the reason to tell that class is not inheritable. – mip Dec 14 '12 at 15:46
-2

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
Daniel Frużyński
  • 2,091
  • 19
  • 28
  • With this solution it is still possible to delete the true base class (std::vector) and leak. – U007D Oct 26 '16 at 05:10
-3

No virtual destructor prevents the class from being subclasses correctly.

psychotik
  • 38,153
  • 34
  • 100
  • 135
  • 4
    Not really. You can subclass any class not having a virtual destructor. The only problem you will have is that destructor of inherited class will not be called in case when polymorphism is used. So not having virtual destructor does not prevent correct inheritance in any way. –  Sep 01 '10 at 17:04