I have been thinking about inheriting from STL container in C++11. I know that this should not be done without some considerations since there is no virtual destructors.
Using typedefs is, from what I have understood the preferred way to give names to STL containers.
However typedefs are not without problems by themselves. First of all they can not easily be forward declared and two typedefs might accidently be the same type.
Ponder the following:
typedef std::vector<int> vec_a_t;
typedef std::vector<float> vec_b_t;
void func(const vec_a_t& v);
void func(const vec_b_t& v);
The two functions should behave differently depending on the logical type vec_a_t
or vec_b_t
This situation will work fine until someone changes vec_a_t
to
typedef std::vector<float> vec_a_t;
Now a call to func()
is suddenly ambiguous. A realistic example for func()
is
std::ostream& operator<<(std::ostream& ost, const vec_a_t& v);
Now if we instead inherit like
class Vector : public std::vector<int>
{};
std::ostream& operator<<(std::ostream& ost, const Vector& v);
It will be possible do also declare
class Vector2 : public std::vector<int> {};
std::ostream& operator<<(std::ostream& ost, const Vector2& v);
Which will clearly be disambigious.
However since std::vector
does not have virtual destructors deriving from them like this is wrong and can cause problems.
Instead we try
class Vector : private std::vector<int>
{
public:
using::size;
//Add more using declarations as needed.
};
Before C++11 there was issues with this also, since we would have to redeclare the constructors and it would have been possible to subclass our Vector class.
However in C++11 it is possible to do the following
class Vector final : private std::vector<int>
{
public:
using std::vector<value_type>::vector;
using std::vector<value_type>::size;
//More using directives as needed.
};
From what I can see this solves a lot of the problems of why one should not derive from STL containers. It also has the following benefits:
using
).push_back
My questions, based on the previous discussion, are:
Do you see anything wrong with deriving STL containers like this in C++11?
Am I missing something or can this style of coding cause any problems down the line?
Would it cause any problems if this new type had a state of its own
(e.g. track the number of calls to push_back
)?
EDIT:
I know the standard answer is "Use a private field". I was wondering what the realistic downsides of the proposed solution in C++11 is? The downside of a private field is to have to re-implement a whole range of methods that just forwards to the underlying type.
This approach would not be an option either.
class Vector
{
private:
std::vector<int> m_type
public:
std::vector<int>& get_type(){return m_type;}
};
EDIT:
Do not use the typedef coll_t
in the final solution to avoid answers that my new typedef causes problems, it was just there to ease the typing.
struct BobsContainer {
typedef std::vector<int> data_type;
data_type data;
};
We now have a typed std::vector<int>
. Yes, access to it requires typing .data.
, but in exchange we get rid of a LOT of boilerplate and nasty behavior.
If we want to construct the underlying std::vector
, for implicit constructors we simply:
{ {blah, blah, blah} }
this does prefer to invoke the list initialization over standard constructors, so:
{ std::vector<int>( 3 ) }
can be used if we want to avoid them.
Hiding that you are a std::vector
is relatively pointless. If your implementation is "I'm a secret std::vector
and I redirect all of my methods to it", skip the secret?
It is true you can enforce some invariants by hiding some std::vector<int>
but not others: but if you are going that far, go with the private
solution. Writing forwarding methods, especially in C++1y, gets ridiculously easy:
template<typename... Args> decltype(auto) insert( Args&&... args ) { return data.insert( std::forward<Args>(args)... ); }
which is a bit more boilerplate than using std::vector<int>::insert;
, but only a bit. And in exchange you are no longer doing strange things with 'is-a' and inheritance.
For methods with both const
and non-const
overloads:
template<typename... Args> decltype(auto) insert( Args&&... args ) const { return data.insert( std::forward<Args>(args)... ); }
and if you want to get really silly, include &&
and &
overloads (standard containers don't use them yet).
So if you are forwarding almost everything, just contain a vector
. If you are hiding almost everything, just contain a private vector
and forward. Only in the strange unstable case where you are hiding about half of the class and exposing the other half does the using container;
and private
inheritance get reasonable.
Composition via inheritance is important when you want to exploit the empty base class optimization in generic code. It usually isn't a good idea otherwise. None of the standard containers where designed to be inherited from.
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