Is There Any Real Risk to Deriving from the C++ Stl Containers

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

The risk is deallocating through a pointer to the base class (delete, delete[], and potentially other deallocation methods). Since these classes (deque, map, string, etc.) don't have virtual dtors, it's impossible to clean them up properly with only a pointer to those classes:

struct BadExample : vector<int> {};
int main() {
vector<int>* p = new BadExample();
delete p; // this is Undefined Behavior
return 0;
}

That said, if you're willing to make sure you never accidentally do this, there's little major drawback to inheriting them—but in some cases that's a big if. Other drawbacks include clashing with implementation specifics and extensions (some of which may not use reserved identifiers) and dealing with bloated interfaces (string in particular). However, inheritance is intended in some cases, as container adapters like stack have a protected member c (the underlying container they adapt), and it's almost only accessible from a derived class instance.

Instead of either inheritance or composition, consider writing free functions which take either an iterator pair or a container reference, and operate on that. Practically all of <algorithm> is an example of this; and make_heap, pop_heap, and push_heap, in particular, are an example of using free functions instead of a domain-specific container.

So, use the container classes for your data types, and still call the free functions for your domain-specific logic. But you can still achieve some modularity using a typedef, which allows you to both simplify declaring them and provides a single point if part of them needs to change:

typedef std::deque<int, MyAllocator> Example;
// ...
Example c (42);
example_algorithm(c);
example_algorithm2(c.begin() + 5, c.end() - 5);
Example::iterator i; // nested types are especially easier

Notice the value_type and allocator can change without affecting later code using the typedef, and even the container can change from a deque to a vector.

Are STL containers designed to allow inheritance?

Are STL containers designed to allow inheritance or not?

Standard library containers allow Inheritance. Nothing stops you from inheriting from a standard library container class. You will not get any compilation errors if you do so.

But what they are not designed for is to allow is destruction of your derived class object through Base class pointer. So if you want to use inheritance for such a scenario(in short for dynamic polymorphism) then standard library containers are clearly not designed for it.

Is virtual destructor required for inheritance?

Base class destructor is only required to be virtual if you intend to call delete on base class pointer pointed to a derived class object. It will result in Undefined behavior if base class destructor is not virtual.

So to summarize, the rule is:

If you need inheritance for dynamic polymorphism standard library container classes are not designed for it, but If you don't need that you can safely inherit from them.

Note: Your analysis in the answer link you provided is correct. It just didn't get responses probably because the answer was posted long(a few years) after the original Q was posted. You have my +1 there now.

Subclass/inherit standard containers?

Maybe many people here will not like this answer, but it is time for some heresy to be told and yes ... be told also that "the king is naked!"

All the motivation against the derivation are weak. Derivation is not different than composition. It's just a way to "put things together".
Composition puts things together giving them names, inheritance does it without giving explicit names.

If you need a vector that has the same interface and implementation of std::vector plus something more, you can:

  • use composition and rewrite all the embedded object function prototypes implementing function that delegates them (and if they are 10000... yes: be prepared to rewrite all those 10000) or...

  • inherit it and add just what you need (and ... just rewrite constructors, until C++ lawyers will decide to let them be inheritable as well: I still remember 10 year ago zealot discussion about "why ctors cannot call each other" and why it is a "bad bad bad thing" ... until C++11 permitted it and suddenly all those zealots shut up!) and let the new destructor be non-virtual as it was in the original one.

Just like for every class that has some virtual method and some not, you know you cannot pretend to invoke the non-virtual method of derived by addressing the base, the same applies for delete. There is no reason just for delete to pretend any particular special care.

A programmer who knows that whatever is not virtual isn't callable addressing the base, also knows not to use delete on your base after allocating your derived.

All the "avoid this", "don't do that", always sound as "moralization" of something that is natively agnostic. All the features of a language exist to solve some problem. The fact a given way to solve the problem is good or bad depends on the context, not on the feature itself.

If what you're doing needs to serve many containers, inheritance is probably not the way (you have to redo for all). If it is for a specific case ... inheritance is a way to compose. Forget OOP purisms: C++ is not a "pure OOP" language, and containers are not OOP at all.

Is there still a need to provide default constructors to use STL containers?

This quote is from the C++ Programming Language, Special edition , 2005 by Bjarne Stroustrup in section 16.3.4:

If a type does not have a default constructor, it is not possible to create a vector with elements of that type, without explicitly providing the value of each element.

So it was indeed a standard requirement. It was also required that (section 17.1.4) :

To be an element of a container, an object must be of a type that allows the container implementation to copy it. The container may copy it using a copy constructor or an assignment; in either case the result of the copy must be an equivalent object.

So yes, there were "official" constructor requirementsand the library implementation were supposed to be interchangeable and not add other requirements. (Already in the very first proposal for STL in 1995, the authors tried as much as possible to clearly indicate specifications and narrow down the implementation dependent flexibility.)

You therefore had to provide a default constructor in the case where you declared other constructors:

If a user has declared a default constructor, that one will be used; otherwise, the compiler will try to generate one if needed and if the user hasn't declared other constructors.

Nowadays, this requirement is relaxed. Since C++11:

The requirements that are imposed on the elements depend on the actual operations performed on the container.

So you can define a vector for a class without default constructor, if it doesn't make sense. This for example perfectly works (online demo):

class MyClass {
public:
MyClass(int x) {}
};
int main() {
vector<MyClass> v;
MyClass test{1};
v.push_back(test);
}

But it works only as long as you don't use any operation that would need the default constructor. For instance v.resize(6); would fail to compile.

std::find vs. deriving template from vector

What about you do what you want to achieve and still not go into the dubious path of inheriting from std::vector

define a freestanding function

template <typename T>
typename std::vector<T>::const_iterator find( const std::vector<T>& v, const T& value )
{
return std::find( v.begin(), v.end(), value );
}

you can put this into namespace std(which, technically speaking is not allowed), or in some other namespace (with the tradeoff that it won't be found by ADL, so you will need to qualify it). HTH

P.S. by the way you could generalize this for all containers

template <typename Container, typename T>
typename Container::const_iterator find( const Container& c, const T& value )
{
return std::find( c.begin(), c.end(), value );
}

How should a size-limited stl-like container be implemented?

A simple solution would be encapsulating a vector inside your own limited size container. You could use private composition or private inheritance --note that private inheritance models implemented in terms of and does not have some of the shortcomings of public inheritance.

EDIT: Sketch of the solution with private inheritance

template <typename T, unsigned int N>
class fixed_vector : std::vector<T>
{
typedef std::vector<T> vector_type;
public:
typedef typename vector_type::reference reference;
typedef typename vector_type::const_reference const_reference;
typedef typename vector_type::iterator iterator;
typedef typename vector_type::const_iterator const_iterator;
typedef typename vector_type::value_type value_type;
typedef typename vector_type::size_type size_type;

fixed_vector() : vector_type() {}
fixed_vector( size_type size, value_type const & value = value_type() )
: vector_type(size,value)
{}

void push_back( value_type v ) {
ensure_can_grow();
vector_type::push_back( v );
}
iterator insert( iterator position, value_type const & v ) {
ensure_can_grow();
vector_type::insert( position, v );
}
void reserve( size_type size ) {
if ( size > N ) throw std::invalid_argument();
vector_type::reserve( size );
}
size_type capacity() const {
// In case the default implementation acquires by default
// more than N elements, or the vector grows to a higher capacity
return std::min( vector_type::capacity(), N );
}
// provide other insert methods if required, with the same pattern
using vector_type::begin;
using vector_type::end;
using vector_type::operator[];
using vector_type::erase;
using vector_type::size;
using vector_type::empty;
private:
void ensure_can_grow() const {
// probably a different exception would make sense here:
if ( this->size() == N ) throw std::bad_alloc();
}
};

There is quite a bit of hand-waving there... std::vector take more arguments that could be added to the façade. If you need any of the other methods or typedefs, you can just bring them into scope with a using declaration, redefine the typedef, or implement the adaptor with your particular test.

Also, in this implementation the size is a compile time constant, but it would be really simple to modify it into a constructor parameter.

Need a vector that derives from a vector

It's not clear from your example if inheritance is needed. You may also not realize it is dangerous, because std::vector does not have a virtual destructor. That means V1's destructor will not be called upon deletion of a pointer to the base class and you may end up leaking memory/resources. See here for more info.

class A {
};

class V1: vector<A *>{
// my nice functions
};

if I have a instance of V1, then any object derived from A can be
inserted into the vector, ok here.

Yes, correct.

Now, lets say I have two simple
classes called B and C both derives
from A; if I have a instance of V1,
then both pointers of B and C can be
inserted into this vector, I guess
this is right to afirm?

Yes, correct.

if so, how can I derive a vector from
V1 to make sure only B pointers are
inserted? I was thinking about using
templates, but in this case I already
know the base of the class and in
tempaltes you can use anything, right?

Why not use a

std::vector<B*> m_bVector;

for this case? Here's how it would work:

B* bInstance = new B();
A* aInstance = new A();
m_bVector.push_back(bInstance);
m_bVector.push_back(aInstance); //< compiler error

Maybe you have a good reason for inheriting from vector, but I don't see it right now... If you need added functionality, it may be better to have V1 wrap the std::vector, ie:

class V1
{
private:
std::vector<A*> m_aVec;
public:
// use AVec
}


Related Topics



Leave a reply



Submit