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
.
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".
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With