gcc4.9.2's libstdc++ implementation of std::vector inherits from _Vector_base (non-virtual destuctor). Why is this OK? [duplicate]
Asked Answered
U

6

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?

Upbuild answered 6/12, 2014 at 18:11 Comment(8)
You can, no problem with that whatsoever, as long as inheritance is not public.Clarance
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.Seal
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 deletes 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...Freezedry
@cornstalks The only thing that distinguishes this question from the linked ones is that this is harping on libstdc++, explaining the upvotes.Cheops
@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.Freezedry
@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?Upbuild
@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!Upbuild
@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.Seal
F
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.

Freezedry answered 6/12, 2014 at 19:30 Comment(0)
O
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.

Oocyte answered 6/12, 2014 at 18:24 Comment(0)
H
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.

Homologate answered 6/12, 2014 at 20:19 Comment(0)
S
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.

Selfexcited answered 6/12, 2014 at 22:6 Comment(0)
L
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.

Leveille answered 6/12, 2014 at 20:28 Comment(1)
Nice, I particularly like your simple wording and logic of the first part.Freezedry
A
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?

Ama answered 6/12, 2014 at 18:32 Comment(12)
Having virtual methods has nothing to do with whether or not you need a virtual destructor.Oocyte
of course it does. Without virtual methods you have no polymorphism. Without polymorphism you have no need of virtual destructor.Ama
@Ama 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.Sisneros
yes, this is true. But having pointer to the base class instead the actual one, makes sense only if you have polymorphism.Ama
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 virtualAma
A complete discussion of this issue.Christenachristendom
@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<base> later, or even if you do shared_ptr<base>(new derived). Then you don't need a virtual destructor.Scandalize
@LightnessRacesinOrbit, no, your first comment was correct. Notice that there is no shared_ptr<T>::shared_ptr(T*) constructor, just the template<class Y> shared_ptr<T>::shared_ptr<Y>(Y* p) one, which "owns the pointer p" i.e. it will call delete p not delete (T*)pScandalize
@JonathanWakely: Ahhh, crap.Seal
@LightnessRacesinOrbit, it's a very very intentional part of the shared_ptr design, see the note at boost.org/doc/libs/1_57_0/libs/smart_ptr/… [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]Scandalize
@Ama "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.Onwards
@Onwards Thank you. What I said was to contradict what JorenHeitn said, I do not actually mean that.Ama

© 2022 - 2024 — McMap. All rights reserved.