18

So I have been using a container derived from std::vector for some time. Perhaps this is a poor design decision for several reasons, and the question of whether or not you should do such a thing has be discussed extensively here:

Thou shalt not inherit from std::vector

Subclass/inherit standard containers?

Is there any real risk to deriving from the C++ STL containers?

Is it okay to inherit implementation from STL containers, rather than delegate?

I am sure I have missed some of the discussions…but reasonable arguments for both viewpoints are found in the links. As far as I can tell, the "because ~vector() is non-virtual" is the foundation for the "rule" that you shouldn't inherit from stl containers. However, if I look at the implementation for std::vector in g++ 4.9.2, I find that std::vector inherits from _Vector_base, and _Vector_base a non-virtual destructor.

template<typename _Tp, typename _Alloc = std::allocator<_Tp> >
class vector : protected _Vector_base<_Tp, _Alloc>
{
...
  ~vector() _GLIBCXX_NOEXCEPT
  { std::_Destroy(this->_M_impl._M_start, this->_M_impl._M_finish,
    _M_get_Tp_allocator()); }

...
}

where:

template<typename _Tp, typename _Alloc>
struct _Vector_base
{
...
  ~_Vector_base() _GLIBCXX_NOEXCEPT
  { _M_deallocate(this->_M_impl._M_start, this->_M_impl._M_end_of_storage
    - this->_M_impl._M_start); }

...
}

So the gcc 4.9.2 implementation of std::vector inherits from base classes with a non-virtual destructor. This leads me to believe that it is an acceptable practice. Why is this OK? What are the specific conditions by which such a practice is not dangerous?

Community
  • 1
  • 1
doc07b5
  • 600
  • 1
  • 7
  • 18
  • You can, no problem with that whatsoever, as long as inheritance is not public. – n. m. could be an AI Dec 06 '14 at 18:21
  • 7
    There are several flaws in your argument, one being a false transitivity and another being a failure to consider the black-box nature of a standard library implementation. – Lightness Races in Orbit Dec 06 '14 at 18:23
  • This really is a duplicate of all those other questions you linked to. In addition to a non-virtual `~vector()` (which you're right isn't a problem if there's no polymorphic `delete`s in the code), one big issue is the potential of object slicing. `vector` is such a commonly used type, it might be copied at times in code you don't realize; if you pass in your custom class, that copy will be done on a `vector`, and will cause object slicing. But those questions you linked to talk about this... – Cornstalks Dec 06 '14 at 18:59
  • @cornstalks The only thing that distinguishes this question from the linked ones is that this is harping on libstdc++, explaining the upvotes. –  Dec 06 '14 at 19:00
  • @remyabel: indeed, that's the only difference. Unfortunately, it doesn't change the substance of the question. A better question would be: why is it okay for `vector` to inherit from `_Vector_base`? If this were the question, then the focus would be different enough to let this question stand on its own. Otherwise, it's just asking the same question with slightly different words. – Cornstalks Dec 06 '14 at 19:02
  • @Cornstalks. Yeah…I had originally intended to be a little more focused on the "why is it okay for vector to inherit from _Vector_base?" question, but took it a little to far towards the ramifications of the answer to that question. Is it poor form for me to go back simplify the question and change the title? – doc07b5 Dec 06 '14 at 19:55
  • @LightnessRacesinOrbit I am sure you are making a great point, but can you elaborate on your "false transitivity" statement? I am not really clear on what that means when applied here. Thanks! – doc07b5 Dec 06 '14 at 19:57
  • @doc07b5: Your title suggests a belief that, just because `std::vector` inherits from `_Vector_base`, we can use this fact to decide whether other types should inherit from `std::vector`. In fact, the two are unrelated. – Lightness Races in Orbit Dec 06 '14 at 20:06

6 Answers6

15

1: std::vector does not inherit from _Vector_base

Or rather, not in my standard library implementation. libc++ implements vector like so:

namespace std
{

template <class T, class Allocator = allocator<T> >
class vector
{
...

Sure, your implementation might inherit from a base class, but my implementation does not. This leads to the first error of your interpretation of the "rule":

The C++ standard library can be implemented in a variety of ways, and we really can't make broad, sweeping assumptions or statements about the standard library (the one defined by ISO/IEC 14882:2014) from one implementation of it.

So, before we get any further, let's remember: we're not going to focus on a single implementation in this answer. We're going to consider all implementations (by focusing on following the definitions in the C++ standard; not the definitions of a particular header file on your hard drive).

2: Okay, yeah, so std::vector inherits from _Vector_base

But before we continue, let's admit that your implementation might inherit from _Vector_base, and that's okay. So why is std::vector allowed to inherit from a base class, but you aren't allowed to inherit from std::vector?

3: You can inherit from std::vector, if you want (just be careful)

std::vector can inherit from _Vector_base for the same reasons you can inherit from std::vector: the library implementors are very careful (and you should be too).

std::vector is not deleted polymorphically with a _Vector_base pointer. So we don't have to worry about ~vector() not being virtual. If you don't delete your inherited class with a polymorphic std::vector pointer, then ~vector() being non-virtual is a non-issue. It's fine. No worries. Let's not fuss about it. No polymorphic deletes means we don't have to worry about destructors being virtual or not.

But beyond this, the library implementors have ensured that std::vector is never sliced from using a _Vector_base reference. Same for you: if you can ensure your custom vector class is never sliced (by someone using a std::vector reference to it), then you're fine here too. No slicing means we don't have to worry about bad copies being made.

4: Why you shouldn't inherit from std::vector

This has been answered a lot in other questions, but I'll say it again here (and with the focus of the whole _Vector_base inheritance issue): you (probably) shouldn't inherit from std::vector.

The problem is that if you do, someone using your custom vector class might polymorphically delete it (they might have a std::vector pointer to it, and delete it, which is A Bad Thing if they do). Or they might have a std::vector reference to your custom object and try to make a copy of it (which would slice the object, which would also probably be A Bad Thing) (I keep assuming a std::vector reference to your custom object is needed to cause object slicing when copying, because I keep assuming that you're careful enough to never accidentally slice an object with a careless assignment or function call; you would never be so careless, I'm sure, but someone else with a std::vector reference might be (and yes, I'm being a little facetious here)).

You can control what you do with your code. And if you can control it (carefully) enough to ensure there are no polymorphic deletes or object slicing, you're fine.

But sometimes you can't really control what others do with your code. If you're working on a team, this might be problematic if one team member unwittingly does one of these things. Which is why I keep bringing up the "be careful" thing.

Even worse, if your code is used by a client, you really can't control what they do, and if they do one of these bad things, you're the one who is probably going to be blamed and tasked with fixing it (have fun refactoring all your code that used to rely on your custom class inheriting from std::vector) (or just tell the client they can't do that, but you'll probably have a grumpy client who wasted time debugging a weird issue they didn't expect to run into).

Your C++ standard library implementers can get away with this inheritance, though, because they can control things very well: no one is allowed to use _Vector_base. You can use std::vector. Only std::vector. Since you're not allowed to ever (ever) use _Vector_base, the standard library implementers don't have to worry about object slicing or polymorphic deletes. And since they're being very careful in their implementation in a controlled environment, things work out just fine.

But even better, by not making assumptions about how std::vector is implemented, and instead treating it like a (useful) black box, we can make sure that how we use std::vector is portable to other standard library implementations. If you make assumptions about std::vector inheriting from some base class, you're going to be limiting the portability of your code.

Community
  • 1
  • 1
Cornstalks
  • 37,137
  • 18
  • 79
  • 144
4

Because it's illegal for user code to try to destroy a _Vector_base, as it's a stdlib internal type. This prevents any issues with destructors. The same cannot be said for you.

Put simply, the Standard library internals are a special case, both in the language rules, and in what's reasonable for them. You can't generalize from what they do to your own code.

Puppy
  • 144,682
  • 38
  • 256
  • 465
4

The problem that the rule "never inherit from a type without a virtual destructor" tries to solve is this:

  • Whenever you delete an object without a virtual destructor, the destructor that is called does not depend on the dynamic type.
    That is, if the declared type of the pointer that you delete differs from the dynamic type of the object, the wrong destructor gets called.

Banning subclassing of types without a virtual destructor is equivalent to restricting these types to being stand-alone classes: classes without a parent and without derived subclasses. It is obvious that the declared type of a pointer to one of these classes can never be different from the dynamic type of the object, hence no danger of calling the wrong destructor.

However, this rule is a bit too strict: What needs to be avoided is calling the wrong destructor, not subclassing in and of itself. Here is a class hierarchy that does not allow the wrong destructor to be called:

class Foo {
    protected:
        Foo();
        ~Foo();
};

class Bar : public Foo {
    public:
        Bar();
        ~Bar();
};

With these two classes, it is perfectly possible to create a Bar object and to manipulate it via a Foo pointer or reference, however, a Foo* cannot be used to destroy the object - the class Foo itself is not instanciable or destructable from other code.

Now back to std::vector<>. The class std::vector<> is the one that is meant to be used, and it does not have a virtual destructor. But that does not require that there is no base class to std::vector<>. A library is free to implement std::vector<> by dividing its implementation across two classes, following the pattern of Foo and Bar above. The only thing that needs to be avoided is, that a user uses a base-class pointer to destroy a derived object. Of course, the type _Vector_base is private to your standard libraries implementation, and users should never use it, so it's ok.

However, subclassing std::vector<> is a whole different story: The destructor of std::vector<> is public, so you can't stop users of a class

template <class T> class MyVector : public std::vector<T> {
    ...
};

to use a base class pointer they might obtain to destroy a MyVector<>. You could use private or protected inheritance to avoid the problem, but that does not provide the benefits of public inheritance. So, yes, you should never subclass std::vector<> even though it is perfectly fine for std::vector<> to inherit from another private class.

cmaster - reinstate monica
  • 38,891
  • 9
  • 62
  • 106
4

Just a data point.

In "A Tour of C++" Bjarne Stroustrup defines a class template that derives from std::vector<T>. The purpose being to have range checking when using operator []. Everything else is delegated to the base class.

user515430
  • 298
  • 1
  • 3
  • 7
3

The answer has 2 parts:

  1. Because _Vector_base knew it was going to be inherited by std::vector and was designed as such

  2. There's an exception to every rule. If inheriting from std::vector makes sense in your case then just do what makes sense.

user541686
  • 205,094
  • 128
  • 528
  • 886
0

There is no such rule "do not inherit from base classes with non-virtual destructors". If there is a rule it will be: "if you have even one virtual method, make your destructor virtual, which also means do not inherit from base classes with non-virtual destructors". IMHO it is ok to inherit stl containers if you follow this.

It seems like some compiler architects also agree with this. There is literally a warning that states it - for reference: What does 'has virtual method ... but non-virtual destructor' warning mean during C++ compilation?

Community
  • 1
  • 1
gsf
  • 6,612
  • 7
  • 35
  • 64
  • Having virtual methods has nothing to do with whether or not you need a virtual destructor. – Puppy Dec 06 '14 at 18:34
  • 5
    of course it does. Without virtual methods you have no polymorphism. Without polymorphism you have no need of virtual destructor. – gsf Dec 06 '14 at 18:35
  • @gsf No it does not. When passing a pointer to a base class to, e.g. a `std::shared_ptr`, you rely on it to take ownership of the pointer and destroy it properly when needed. When the destructor is non-virtual it will call only the base destructor while you want it to call the derived destructor. – JorenHeit Dec 06 '14 at 18:41
  • 3
    yes, this is true. But having pointer to the base class instead the actual one, makes sense only if you have polymorphism. – gsf Dec 06 '14 at 18:44
  • Based on what you saying every class in c++ should be with a virtual destructor. Then why bother explicitly declaring it - lets ask to change the language all destructors to be virtual – gsf Dec 06 '14 at 18:47
  • 1
    A [complete discussion](http://www.gotw.ca/publications/mill18.htm) of this issue. – arayq2 Dec 06 '14 at 19:05
  • 2
    @JorenHeitn, if you're passing a pointer to base class to `shared_ptr` you're doing it wrong. You should be passing the result of a `new` expression straight to `shared_ptr` (or using `make_shared`) and then it guarantees to destroy it correctly, even if converted to `shared_ptr` later, or even if you do `shared_ptr(new derived)`. Then you don't need a virtual destructor. – Jonathan Wakely Dec 06 '14 at 20:25
  • @LightnessRacesinOrbit, no, your first comment was correct. Notice that there is no `shared_ptr::shared_ptr(T*)` constructor, just the `template shared_ptr::shared_ptr(Y* p)` one, which "owns the pointer `p`" i.e. it will call `delete p` not `delete (T*)p` – Jonathan Wakely Dec 06 '14 at 20:57
  • @JonathanWakely: Ahhh, crap. – Lightness Races in Orbit Dec 06 '14 at 20:58
  • 1
    @LightnessRacesinOrbit, it's a very very intentional part of the shared_ptr design, see the note at http://www.boost.org/doc/libs/1_57_0/libs/smart_ptr/shared_ptr.htm#pointer_constructor _[This constructor is a template in order to remember the actual pointer type passed. The destructor will call `delete` with the same pointer, complete with its original type, even when `T` does not have a virtual destructor, or is `void`]_ – Jonathan Wakely Dec 06 '14 at 21:02
  • @gsf "lets ask to change the language all destructors to be virtual" The reason virtual dtors are opt-in in C++ is because it incurs some (albeit tiny) overhead, and isn't always necessary. C++'s tendency is for high-level features to be opt-in, not opt-out. – fluffy Dec 07 '14 at 00:36
  • @fluffy Thank you. What I said was to contradict what JorenHeitn said, I do not actually mean that. – gsf Dec 07 '14 at 02:07