Inherite form stl container classes

A

ademirzanetti

Hi there !!!

I would like to listen your opinions about inherit from a STL class
like list. For example, do you think it is a good approach if I
inherit from list to create something like "myList" as in the example
below ?

#include "Sector.h"

using namespace boost;

typedef std::list<Setor>::iterator ListaSetorIterator;
typedef std::list<Setor>::const_iterator ListaSetorConstIterator;

class SectorList: private std::list<Sector> {
public:

using std::list<Sector>::begin;
using std::list<Sector>::end;

SectorList();
~SectorList();

result add( const Sector& );
result modify( const Sector& );
result get( Sector&, const uint32_t ) const;
result remove( const uint32_t );
private:
using std::list<Sector>::push_back;
};

I do not see a problem on it. I believe it is better to inherit in
this way than try to create my own list from scratch, but I also want
to listen more opinions about it.

Thanks in advance ;-)
 
A

ademirzanetti

I am not sure what it is you're expecting us to tell you. Private
inheritance is a method of implementing your class in terms of the other
class. You're essentially creating an Adapter (see Adapter pattern, Goa
book or on the web). Another way to program an Adapter in C++ is
containment.

You've followed one of the two ways commonly accepted, why wouldn't it
be a good approach?

I suppose you're not expecting us to criticise your code on its own
merits, like the degree of completeness of the interface without any
given requirements, or typos, or extraneousness of some declarations...

V

Actually, the questions is, Is there any issue inheriting from STL
classes ?
I read some papers which say that the "STL classes were not created to
be inherited", but they did not mention the reasons. They says that
there are some pitfalls when it is used in this way that are hard to
avoid.

Again, I always used it in this way for years without problems and I
just expected to listen if someone already had any issues with it.

Thanks for the comments.
 
K

Kai-Uwe Bux

ademirzanetti said:
Actually, the questions is, Is there any issue inheriting from STL
classes ?
I read some papers which say that the "STL classes were not created to
be inherited", but they did not mention the reasons. They says that
there are some pitfalls when it is used in this way that are hard to
avoid.

a) That paper probably referred to public inheritance. Private inheritance
from STL containers does not present any problems.

b) As for public inheritance from STL container, there are two commonly
mentioned problems:

b1) STL containers do not have virtual destructors.

This is not really a problem. Just don't delete pointers to the derived
classes through pointers of the base class (and why would you have any use
for such polymorphic container pointers in the first place). It's about as
problematic in practice as inheriting from std::iterator.


b2) Functions of the type

template < typename T >
std::vector<T> reverse ( std::vector<T> const & v );

will match any object derived from std::vector but usually have the wrong
return type.

This is a more serious objection. However, it is not at all clear whether
inheritance from std::vector is to blame or whether the function reverse is
just ill-designed. Maybe, it should be:

template < typename Sequence >
Sequence reverse ( Sequence const & v );

possibly enriched by some template magic to make sure it only matches
sequences. Therefore, it depends by and large on your code base whether
this problem is easy to avoid or not. If you have functions like reverse
whose signatures have been frozen, it can be cumbersome to derive from
container classes.



With public inheritance from standard containers, you have to be aware of
the above caveats. If you are (and you are confident that the maintenance
programmers who will have to deal with your code are, too), then inheriting
publicly from standard containers can be justified in certain cases. E.g.,
if you are doing linear algebra, you might want to overload operator+ to do
element wise addition. It would be a bad idea to just dump that overload
into global namespace for all vectors (e.g., it might interfere with the
idea of someone else to have operator+ denote concatenation of sequences).
In that case, a quick

template < typename ArithmeticType >
struct arithmetic_vector : public std::vector< ArithmeticType > {
// some constructors
};

template < typename ArithmeticType >
arithmetic_vector< ArithmeticType >
operator+ ( arithmetic_vector< ArithmeticType > const & lhs,
arithmetic_vector< ArithmeticType > const & rhs ) {
assert( lhs.size() == rhs.size() );
...
}

can serve as a templated typedef that does not create just an alias but a
true independent type (that will convert transparently to std::vector in
cases needed and with the right constructor, conversion the other way
around is also no problem). However, private inheritance with a complete
set of forwarding methods is considered cleaner by many.



A final real issue with public inheritance from std::vector is the
introduction of additional invariants (e.g., the condition that the sum of
all elements be 0). That cannot work since the underlying base class allows
client code to invalidate the invariant.

Again, I always used it in this way for years without problems and I
just expected to listen if someone already had any issues with it.

As Victor said, there are no issues with private inheritance from standard
containers.



Best

Kai-Uwe Bux
 
J

James Kanze

Just wondering, but what does the above statement do? (As I
understand it, it makes the base class function available for
private use in the derived class. But the base class function
was already available for private use. The using declaration
would make sense if it were public, and it would make sense if
the SectorList class also had a push_back function, which would
otherwise hide the base class function, but neither of those
seems to be the case here.)
a) That paper probably referred to public inheritance. Private
inheritance from STL containers does not present any problems.
b) As for public inheritance from STL container, there are two
commonly mentioned problems:
b1) STL containers do not have virtual destructors.
This is not really a problem. Just don't delete pointers to
the derived classes through pointers of the base class (and
why would you have any use for such polymorphic container
pointers in the first place). It's about as problematic in
practice as inheriting from std::iterator.

I disagree. There is nothing you could conceivably do with an
std::iterator itself. You'd never have a reference to
std::iterator as a parameter to a function, for example; in
fact, you'd never have a reference or a pointer to an
std::iterator anywhere in any reasonable code. The same thing
cannot be said of std::vector. And while I can't think of any
case where you'd ever dynamically allocate an std::vector (and
thus, invoke delete on a pointer to std::vector), it's still a
risk I'd prefer avoiding.

Maybe the solution is to forbid dynamic allocation of such
objects in your coding guidelines, so that any delete of an
std::vector would be considered an error, and caught by code
review.
b2) Functions of the type
template < typename T >
std::vector<T> reverse ( std::vector<T> const & v );
will match any object derived from std::vector but usually
have the wrong return type.
This is a more serious objection. However, it is not at all
clear whether inheritance from std::vector is to blame or
whether the function reverse is just ill-designed. Maybe, it
should be:
template < typename Sequence >
Sequence reverse ( Sequence const & v );
possibly enriched by some template magic to make sure it only
matches sequences.

You mean by maybe using begin() and end() in its implementation
(or more likely rbegin() and rend(), in this case---and the
sequence must support at least bi-directional iteration).

More generally, if I'm deriving publicly from std::vector, I'm
saying my class isA std::vector, so there's probably no problem
with the return type above. All you're seeing is the same sort
of slicing that always occurs when you use derived classes by
value.

In practice, the only cases I've seen where it would make sense
at the design level to publicly derive from std::vector is to
provide special, initializing constructors. Once the object has
been constructed, it *is* an std::vector, for all intents and
purposes.

Come to think of it: I have a case---the FieldArray at my
site---where the constructors and assignment operators are
different from std::vector, but the class does use std::vector
in its implementation, and the non-mutable interface is exactly
that of std::vector. The class goes back to long before the
STL, and is widely used in my code, so I'm not going to change
it now. But thinking about it... the restriction to the
non-mutable interface isn't really necessary, and public
derivation from std::vector< std::string > would make perfect
sense. And thinking about it even more: perhaps just having the
client code declare an std::vector<std::string> directly, and
providing free functions to set its value, would also make
sense.
Therefore, it depends by and large on your code base whether
this problem is easy to avoid or not. If you have functions
like reverse whose signatures have been frozen, it can be
cumbersome to derive from container classes.
With public inheritance from standard containers, you have to
be aware of the above caveats. If you are (and you are
confident that the maintenance programmers who will have to
deal with your code are, too), then inheriting publicly from
standard containers can be justified in certain cases. E.g.,
if you are doing linear algebra, you might want to overload
operator+ to do element wise addition. It would be a bad idea
to just dump that overload into global namespace for all
vectors (e.g., it might interfere with the idea of someone
else to have operator+ denote concatenation of sequences). In
that case, a quick
template < typename ArithmeticType >
struct arithmetic_vector : public std::vector< ArithmeticType > {
// some constructors
};
template < typename ArithmeticType >
arithmetic_vector< ArithmeticType >
operator+ ( arithmetic_vector< ArithmeticType > const & lhs,
arithmetic_vector< ArithmeticType > const & rhs ) {
assert( lhs.size() == rhs.size() );
...
}
can serve as a templated typedef that does not create just an
alias but a true independent type (that will convert
transparently to std::vector in cases needed and with the
right constructor, conversion the other way around is also no
problem). However, private inheritance with a complete set of
forwarding methods is considered cleaner by many.

I very much appreciate this example. It's the sort of thing
that wouldn't occur to me, because it's in a domain I'm not
familiar with. Perhaps the key is the idea that an std::vector
(and its derived classes) shouldn't be allocated dynamically.
Ban such dynamic allocation, and you eliminate the most serious
problem: a delete through the pointer to the base class. (And
you can even ban such allocation, by declaring a private
operator new in your derived class.)
A final real issue with public inheritance from std::vector is
the introduction of additional invariants (e.g., the condition
that the sum of all elements be 0). That cannot work since the
underlying base class allows client code to invalidate the
invariant.

That's a different issue. If you need additional invariants,
then you must derive privately or use containment; since you
also have to replace all modifier functions so that you enforce
the invariant, it's probably just as simple to use containment.
But it does raise an interesting question: is there any way to
promote just the "const" interface of a base class, so that the
derived class "isA" base, but only for clients which don't
modify it in any way. (That would correspond to what I want in
my FieldArray as well.)
 
K

Kai-Uwe Bux

James said:
Just wondering, but what does the above statement do? (As I
understand it, it makes the base class function available for
private use in the derived class. But the base class function
was already available for private use. The using declaration
would make sense if it were public, and it would make sense if
the SectorList class also had a push_back function, which would
otherwise hide the base class function, but neither of those
seems to be the case here.)






I disagree. There is nothing you could conceivably do with an
std::iterator itself. You'd never have a reference to
std::iterator as a parameter to a function, for example; in
fact, you'd never have a reference or a pointer to an
std::iterator anywhere in any reasonable code. The same thing
cannot be said of std::vector. And while I can't think of any
case where you'd ever dynamically allocate an std::vector (and
thus, invoke delete on a pointer to std::vector), it's still a
risk I'd prefer avoiding.

First, we seem to agree that dynamically allocated vectors are weird (and
would go as far as saying its a smell). That is _why_ I don't think the
absence of a virtual destructor is a good argument agains public derivation
from std::vector. However, I do see that this very much depends on coding
guidelines and local culture.

Maybe the solution is to forbid dynamic allocation of such
objects in your coding guidelines, so that any delete of an
std::vector would be considered an error, and caught by code
review.

That would be my inclination.

You mean by maybe using begin() and end() in its implementation
(or more likely rbegin() and rend(), in this case---and the
sequence must support at least bi-directional iteration).

More generally, if I'm deriving publicly from std::vector, I'm
saying my class isA std::vector, so there's probably no problem
with the return type above. All you're seeing is the same sort
of slicing that always occurs when you use derived classes by
value.

Actually, in the cases where I derive from std::vector, there won't be any
slicing since I never add data member. In that case, a function like
reverse is no problem at all. You just add conversions:

template < typename ArithmeticType >
struct arithmetic_vector : public std::vector< ArithmeticType > {

...

arithmetic_vector ( std::vector< ArithmeticType > const & other )
: std::vector< ArithmeticType >( other )
{}

arithmetic_vector &
operator= ( std::vector< ArithmeticType > const & rhs ) {
...
}

};

Then, you can do:

In practice, the only cases I've seen where it would make sense
at the design level to publicly derive from std::vector is to
provide special, initializing constructors. Once the object has
been constructed, it *is* an std::vector, for all intents and
purposes.

The cases where I derive from std::vector are a little different, but it is
still true that the derived class *is* a std::vector and in particular does
not add any data members. I usually use it only to distinguish semantically
different vectors in the type system.


[snip]

I very much appreciate this example. It's the sort of thing
that wouldn't occur to me, because it's in a domain I'm not
familiar with.

Well, the more general pattern is this: I like the type system to
distinguish types that have different meaning. Now, a std::vector<double>
can _mean_ many things. I like those things to be different. Thus:

struct xxx_vector : public std::vector< double > {
// boilerplate code
};

is (in some ways) just an alternative to:

typedef std::vector< double > xxx_vector;

The derivation trick has the advantage that I can choose whether I want to
allow conversions or not. Also (although it is not clear whether it always
is an advantage), I can overload functions on the semantics of vector.

Perhaps the key is the idea that an std::vector
(and its derived classes) shouldn't be allocated dynamically.

Yup. And why would you want to do that anyway?

Ban such dynamic allocation, and you eliminate the most serious
problem: a delete through the pointer to the base class. (And
you can even ban such allocation, by declaring a private
operator new in your derived class.)

That's an interesting idea. Maybe, I should do that (just as a safeguard).


[snip]


Best

Kai-Uwe Bux
 
J

James Kanze

James Kanze wrote:

[...]
First, we seem to agree that dynamically allocated vectors are
weird (and would go as far as saying its a smell). That is
_why_ I don't think the absence of a virtual destructor is a
good argument agains public derivation from std::vector.
However, I do see that this very much depends on coding
guidelines and local culture.

Yes. After reading your posting, and thinking it over, I more
or less came to the conclusion that the rule in the guidelines
should be "never allocate a standard container dynamically",
rather than "never derive publicly from a standard container".
This isn't the "generally accepted" rule, however, so it may not
be trivial to get it accepted locally. And if you allow dynamic
allocation of a standard container, then you don't want to
derive from it.

[...]
Well, the more general pattern is this: I like the type system
to distinguish types that have different meaning. Now, a
std::vector<double> can _mean_ many things. I like those
things to be different.

Agreed, but in the domains I work in, those different things
also tend to have different behavior; they're not std::vector,
but rather only support a subset of the operations on
std::vector. (But this may be domain specific. I have
practically no experience in scientic processing, for example.)
struct xxx_vector : public std::vector< double > {
// boilerplate code
};
is (in some ways) just an alternative to:
typedef std::vector< double > xxx_vector;
The derivation trick has the advantage that I can choose
whether I want to allow conversions or not. Also (although it
is not clear whether it always is an advantage), I can
overload functions on the semantics of vector.

That's the point that's bothering me. I would have imagined
(being somewhat naïve in this domain) that many of these vectors
would have constraints, and that you'd want to replace all of
the non-const functions of vector to enforce those constraints.
At which point, you don't want the client code to be able to
access it as a vector, and avoid your enforcement (and from the
client's point of view, the isA relation to vector doesn't
exist, since there are operations which would be legal on vector
but are not allowed here). Which argues for private
inheritance, with using declarations for the const functions.

I rather suspect that this case is fairly frequent, and that
some means of publicly inheriting the const interface, but
keeping the non-const interface private, might be useful. But I
can't think of what it would look like (and you'd really want
the non-const interface to not be inherited at all, so that it
wouldn't be considered in overload resolution).
 
K

Kai-Uwe Bux

James said:
James Kanze wrote:
[...]
b) As for public inheritance from STL container, there are
two commonly mentioned problems:
b1) STL containers do not have virtual destructors.
This is not really a problem. Just don't delete pointers to
the derived classes through pointers of the base class (and
why would you have any use for such polymorphic container
pointers in the first place). It's about as problematic in
practice as inheriting from std::iterator.
I disagree. There is nothing you could conceivably do with
an std::iterator itself. You'd never have a reference to
std::iterator as a parameter to a function, for example; in
fact, you'd never have a reference or a pointer to an
std::iterator anywhere in any reasonable code. The same
thing cannot be said of std::vector. And while I can't
think of any case where you'd ever dynamically allocate an
std::vector (and thus, invoke delete on a pointer to
std::vector), it's still a risk I'd prefer avoiding.

First, we seem to agree that dynamically allocated vectors are
weird (and would go as far as saying its a smell). That is
_why_ I don't think the absence of a virtual destructor is a
good argument agains public derivation from std::vector.
However, I do see that this very much depends on coding
guidelines and local culture.

Yes. After reading your posting, and thinking it over, I more
or less came to the conclusion that the rule in the guidelines
should be "never allocate a standard container dynamically",
rather than "never derive publicly from a standard container".
This isn't the "generally accepted" rule, however, so it may not
be trivial to get it accepted locally.

That, I think, is just part of a bigger problem: pointer handling. I have
tried various things to make that easier and safer. E.g., at one point I
devised a pointer_to<> template that would trigger compile time failures in
an assignment

pointer_to< Base> <-- pointer_to< Derived >

if Base did not have a virtual destructor. However, none of those tricks
works without discipline and code review. In the end, you need local coding
guidelines; and as long as they are reasonable, many things will work.

Now, I do think that banning dynamic allocation for container is better than
banning derivation from containers; but that might be because I have
encountered the need for the latter and never felt a need for the former.
Someone with a different code base might justifiably come to a different
conclusion.

And if you allow dynamic allocation of a standard container, then you
don't want to derive from it.

Right. Whatever your coding guidelines are, they are meant to keep you from
harm.

[...]
Well, the more general pattern is this: I like the type system
to distinguish types that have different meaning. Now, a
std::vector<double> can _mean_ many things. I like those
things to be different.

Agreed, but in the domains I work in, those different things
also tend to have different behavior; they're not std::vector,
but rather only support a subset of the operations on
std::vector. (But this may be domain specific. I have
practically no experience in scientic processing, for example.)
struct xxx_vector : public std::vector< double > {
// boilerplate code
};
is (in some ways) just an alternative to:
typedef std::vector< double > xxx_vector;
The derivation trick has the advantage that I can choose
whether I want to allow conversions or not. Also (although it
is not clear whether it always is an advantage), I can
overload functions on the semantics of vector.

That's the point that's bothering me. I would have imagined
(being somewhat naïve in this domain) that many of these vectors
would have constraints, and that you'd want to replace all of
the non-const functions of vector to enforce those constraints.
At which point, you don't want the client code to be able to
access it as a vector, and avoid your enforcement (and from the
client's point of view, the isA relation to vector doesn't
exist, since there are operations which would be legal on vector
but are not allowed here). Which argues for private
inheritance, with using declarations for the const functions.

Maybe a look at examples is helpful.

a) Linear Algebra.

[Usually, you would just go with a special library that has matrices and
vectors as ready to use templates and be done with it. So this example is
only for illustration.]

Here, a straight forward thing to consider is std::vector< double >. Now,
such a critter can denote at least two things: (a) a position in a vector
space of some dimension, or (b) a translation. Positions and translations
are semantically distinct. However, both allow for the same handling and
both have no restrictions on the coordinates.

The most common additional invariant that arises in linear algebra is fixing
the dimension. E.g., if you work in 3-space, you will want your vectors to
have size 3. The obvious solution is to use std::array< double, 3 > instead
of std::vector.

Typically, you will not have restrictions on the coordinates. What comes to
mind would be probability matrices: all entries between 0 and 1 and columns
add up to 1. In that case, I would use containment.



b) Word Processing.

There are some interesting algebraic structures whose elements can be
represented as words over a fixed alphabet. Concatenation will be the most
important basic operation. For instance, elements of a "free group of rank
two" can be represented as finite words over the alphabet {a,b,A,B}. To
make life more interesting, there are cancellation rules: pairs of adjacent
aA, Aa, bB, and Bb may be deleted or inserted at will. A word is "reduced"
if it does not contain such a pair. Theorem: every word can be reduced by a
sequence of cancellations (obvious) AND the result does not depend on the
sequence used (non-obvious). It is easy to implement functions like

bool is_reduced ( word const & w );

and

word reduce ( word const & w );

using that theorem.


Now for implementing the word class, one could be inclined to derive from
std::vector< short > with the convention

a = 1 b = 2 A = -1 B = -2

which would make it easy to check for possible cancellations: just add the
two adjacent letters and if the sum is 0, then they cancel. That would,
indeed give rise to additional invariants: all elements of the vector are
supposed to be 1,2,-1, or -2. This is bad, since vector< short > will not
enforce that.

One way to deal with that is to wrap the vector into a class that hides the
public interface. Another way is to create a Letter class that has only
four values (a,b,A,B) and use std::vector< Letter > instead. Both methods
are viable. I find that you will usually want to have the Letter class to
model the underlying alphabet anyway, in which case the second solution
suggests itself.

I rather suspect that this case is fairly frequent, and that
some means of publicly inheriting the const interface, but
keeping the non-const interface private, might be useful. But I
can't think of what it would look like (and you'd really want
the non-const interface to not be inherited at all, so that it
wouldn't be considered in overload resolution).

This idea of

class : public const Base { ... };

is intriguing. I have to think about that some more.


That also prompts the question: with private inheritance and using, is there
a way to only make visile the const member with a given name?



Best

Kai-Uwe Bux
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,579
Members
45,053
Latest member
BrodieSola

Latest Threads

Top