Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Proper way to extend the functionality of a container (like std::vector) in C++, without inheriting from it?

I have repeatedly read that inheriting from STL containers is a Bad Thing.
(I know what the reasons behind this are, that's not my question.)

Keeping the above in mind, what is the proper way to extend the functionality of a container?

For example, if I want a vector-like container that automatically resizes when the argument of operator[] is greater than or equal to the size of the container, what would I do?

To me, the most obvious solution in C++11 is:

template<class T, class A = std::allocator<T> >
class auto_vector : public std::vector<T, A>
{
    typedef std::vector<T, A> base_type;
public:
    using base_type::vector;
    typename base_type::reference operator[](typename base_type::size_type n)
    // I don't need const version, so I'll just ignore it here
    {
        if (n >= this->base_type::size())
        { this->base_type::resize(n + 1); }
        return this->base_type::operator[](n);
    }
};

In C++03, it would be:

template<class T, class A = std::allocator<T> >
class auto_vector : public std::vector<T, A>
{

    typedef std::vector<T, A> base_type;
public:
    explicit auto_vector(
        typename base_type::allocator_type const &alloc =
            typename base_type::allocator_type())
        : base_type(alloc) { }
    explicit auto_vector(
        typename base_type::size_type n,
        value_type const &val = value_type(),
        typename base_type::allocator_type const &alloc =
            typename base_type::allocator_type())
        : base_type(n, val, alloc) { }
    template<class InIt>
    auto_vector(InIt f, InIt l, typename base_type::allocator_type const &alloc =
                                typename base_type::allocator_type())
        : base_type(f, l, alloc) { }
    auto_vector(auto_vector const &v) : base_type(v) { }
    typename base_type::reference operator[](typename base_type::size_type n)
    // I don't need const version
    {
        if (n >= this->base_type::size())
        { this->base_type::resize(n + 1); }
        return this->base_type::operator[](n);
    }
};

Both of these are bad practice because they inherit from std::vector.

What, then, is the "proper" way to implement something like this?

like image 221
user541686 Avatar asked Dec 14 '13 03:12

user541686


1 Answers

First, regarding …

“ I have repeatedly read that inheriting from STL containers is a Bad Thing.”

… with the reference providing the reason that STL containers do not have virtual destructors.

It's certainly good advice for novices to not derive from classes without virtual destructors. That makes them unable to e.g. access the protected member in std::stack, it makes them unable to use Microsoft COM technology, etc., but all in all, for the novice the net advantages are tremendous. Likewise we advice novices to not use raw arrays and direct new and delete, which when followed can be a tremendous net advantage for the novice, but still, some – more experienced – have to do it, have to implement the abstractions that the novices are (ideally) constrained to use, lest there be no dynamic allocation in any C++ program.

So, with classes like std::stack clearly demonstrating that an absolute rule about this is nonsense, the experienced programmer must weight the pros and cons, and when deciding on inheritance, must choose between public, protected and private inheritance.

Con public inheritance: if a novice allocates an auto_vector dynamically, and then attempts to destroy it by using delete on a pointer to std::vector, then the design has failed to guide the novice to correct usage. So if it is a goal to strongly guide novices to correct usage, then either don't do this, or also add functionality that makes dynamic allocation difficult for novices. E.g. add an operator new with additional argument of inaccessible type, or itself inaccessible.

For the case at hand other polymorphic access as std::vector is not a problem, because code that indexes a std::vector beyond its size already has Undefined Behavior.

For other cases, cases that one can imagine, that are different from the OP's case, one must consider the particulars of those other cases: general absolute rules don't cut it in programming (well, at least not in C++ programming).

Pro public inheritance in this case: it's, well, the easiest and most direct and clear expression of "I want all the base class' functionality".

like image 155
Cheers and hth. - Alf Avatar answered Sep 21 '22 10:09

Cheers and hth. - Alf