Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 737
doc07b5 Avatar asked Dec 06 '14 18:12

doc07b5


1 Answers

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.

like image 55
Cornstalks Avatar answered Sep 28 '22 23:09

Cornstalks