avoid inheritance from std::map

H

Hicham Mouline

we have bits of code where we inherit from std::map.

Why again shouldn't one inherit from STL containers in the C++ runtimes?


The proposal is to write a
template <typename Key, typename Value>
class MapDecorator {
//replicate map's interface here and simply forward all calls to m_
private:
std::map<Key,Value> m_;
}
and then inherit from MapDecorator <....>


I agree one shouldn't inherit from std::map, though I don't remember why,
However the above seems to me redundant?

regards,
 
A

AnonMail2005

we have bits of code where we inherit from std::map.

Why again shouldn't one inherit from STL containers in the C++ runtimes?

The proposal is to write a
template <typename Key, typename Value>
class MapDecorator {
  //replicate map's interface here and simply forward all calls to m_
private:
  std::map<Key,Value> m_;}

and then inherit from MapDecorator <....>

I agree one shouldn't inherit from std::map, though I don't remember why,
However the above seems to me redundant?

regards,

Ths simplest answer is that std::map does not have a virtual
destructor. So if you are using pointers to the base class and you
delete them, the derived class detructor will not be properly called.

HTH
 
R

Rolf Magnus

Ths simplest answer is that std::map does not have a virtual
destructor. So if you are using pointers to the base class and you
delete them, the derived class detructor will not be properly called.

That's the reason I'm hearing quite often. But which of those classes do you
actually use polymorphically? I never allocated an std::map dynamically. Why
would I? And why would I want to have a pointer to std::map pointing to an
object of a class derived from it? Since I don't see any reason for doing
that, I also don't see any reason to not derive from standard containers.
 
J

Juha Nieminen

Hicham said:
we have bits of code where we inherit from std::map.

Why again shouldn't one inherit from STL containers in the C++ runtimes?


The proposal is to write a
template <typename Key, typename Value>
class MapDecorator {
//replicate map's interface here and simply forward all calls to m_
private:
std::map<Key,Value> m_;
}
and then inherit from MapDecorator <....>


I agree one shouldn't inherit from std::map, though I don't remember why,
However the above seems to me redundant?

It may be a question of abstraction.

By inheriting directly from std::map you may be (rather ironically)
breaking good object-oriented design principles related to modularity
and abstraction. That's because you are fully exposing the data
container you are using in your class (as your class *is* the data
container).

Sometimes that doesn't matter. If your class really *is* a map, just
with added functionality, then it usually is ok.

However, if your class is trying to represent some higher concept,
then by exposing to the outside that it really is a std::map, you are
lessening its abstraction. In some cases this can make it very difficult
to, for example, change the data container implementation in the future.

Usually you don't really want to replicate the entire std::map
interface in your class. You want to implement what you class can do.
Whether it internally uses an std::map or something else (such as a
hash_map or whatever) should be a hidden implementation detail.
 
K

Kai-Uwe Bux

Rolf said:
That's the reason I'm hearing quite often. But which of those classes do
you actually use polymorphically? I never allocated an std::map
dynamically. Why would I? And why would I want to have a pointer to
std::map pointing to an object of a class derived from it? Since I don't
see any reason for doing that, I also don't see any reason to not derive
from standard containers.

The best reason I heard is about unwanted implicit conversions and matching
functions. Suppose, you have a function template

template < typename T >
std::vector<T> reverse ( std::vector<T> const & v ) {
...
}

then the function will match any class derived from std::vector, but the
return type will be std::vector<> and not the derived class. The standard
case would likely be not what you want.

Arguably, the signature of the function reverse() is at fault, but
inheritance from std::vector gets to take the blame. In any case, functions
like the above and inheritance from containers do not mix all that well. To
ease the problem, one could provide a constructor of the derived class from
a vector, but that that would preclude the existence of other data members.
Also, one cannot impose a restricted invariant (like all elements of the
vector are _even_ numbers). By and large, that leaves only two reasonable
scenarios for inheritance:

a) For convenience by extending the interface. Here, free standing functions
are usually better.

b) As a way to distingush types. E.g., one could define

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

template < typename LetterType >
struct word : public std::vector< LetterType > {
// some constructor
};

The nice thing is that now one could overload operator+ to mean elementwise
addition for linalg_vector and concatenation for word.


Best

Kai-Uwe Bux
 
H

Hicham Mouline

Juha Nieminen said:
It may be a question of abstraction.

By inheriting directly from std::map you may be (rather ironically)
breaking good object-oriented design principles related to modularity
and abstraction. That's because you are fully exposing the data
container you are using in your class (as your class *is* the data
container).

Sometimes that doesn't matter. If your class really *is* a map, just
with added functionality, then it usually is ok.

However, if your class is trying to represent some higher concept,
then by exposing to the outside that it really is a std::map, you are
lessening its abstraction. In some cases this can make it very difficult
to, for example, change the data container implementation in the future.

Usually you don't really want to replicate the entire std::map
interface in your class. You want to implement what you class can do.
Whether it internally uses an std::map or something else (such as a
hash_map or whatever) should be a hidden implementation detail.


If we just replicate part of map's public interface in MapDecorator<> above,
and inherit from MapDecorator<>, then aren't we in the same situation?

MapDecorator also hasn't been designed to be a base class.

Should we change tjhe map's interface that we replicate to be virtual
functions insead,
and to add a virtual destructor,
then we'd get rid of 1 reason why we shouldn't inherit from STL containers.

Our classes are curves of datapoints (x,y)
 
J

James Kanze

[...]
By and large, that leaves only two reasonable
scenarios for inheritance:
a) For convenience by extending the interface. Here, free
standing functions are usually better.

Usually doesn't mean always. I don't derive from a standard
container that often, but when I do, the most frequent reason is
to add constructors, i.e. a pre-filled vector or map. (Usually,
it's quite adequate to use the two iterator constructor of the
container, copying some array of POD-types into the container,
possibly with an implicit conversion. But deriving provides the
ultimate flexibility, supporting algorithmic initialization.)
b) As a way to distingush types. E.g., one could define
template < typename ArithmeticType >
struct linalg_vector : public std::vector< ArithmeticType > {
// some constructors
};
template < typename LetterType >
struct word : public std::vector< LetterType > {
// some constructor
};
The nice thing is that now one could overload operator+ to
mean elementwise addition for linalg_vector and concatenation
for word.

Conceptually, the correct solution here is to use containment,
and redefine the interface in the containing class.
Practically, I don't know if it's really worth all that extra
work.
 
A

AnonMail2005

If we just replicate part of map's public interface in MapDecorator<> above,
and inherit from MapDecorator<>, then aren't we in the same situation?

MapDecorator also hasn't been designed to be a base class.

Should we change tjhe map's interface that we replicate to be virtual
functions insead,
and to add a virtual destructor,
then we'd get rid of 1 reason why we shouldn't inherit from STL containers.

Our classes are curves of datapoints (x,y)- Hide quoted text -

- Show quoted text -

The correct way to do this IMHO (and picking up on Juha's
suggestions)...

Make MapDecorator *contain* a map as part of it's (private) data and
expose what you need of it's functionality via accessor functions.
This gets you encapsulation.

If MapDecorator is going to function as a base class for derived
classes, then, and only then, make it have a virtual destructor. Make
other functions virtual (or not) only if needed. There are clear
guidelines as to whether you make a non-destructor function non-
virtual, virtual, or pure-virtual. I'm sure they're in the FAQ but I
learned the guidelines from Meyers.

Also, I would rename MapDecorator to something more domain specific so
it's clearer.

HTH
 
K

Kai-Uwe Bux

James said:
Conceptually, the correct solution here is to use containment,
and redefine the interface in the containing class.

Which one is more correct actually depends on your code base and your
intentions. If there is a reversal function

template < typename T >
void reverse ( std::vector<T> & vec );

then you have to answer the question whether it should match a word or not.
Depending on the answer, inheritance may or may not be the more correct way
to go.
Practically, I don't know if it's really worth all that extra
work.

That's an additional consideration.


Best

Kai-Uwe Bux
 
J

James Kanze

Which one is more correct actually depends on your code base
and your intentions. If there is a reversal function
template < typename T >
void reverse ( std::vector<T> & vec );
then you have to answer the question whether it should match a
word or not. Depending on the answer, inheritance may or may
not be the more correct way to go.

Conceptually, it shouldn't. It says it wants an std::vector<T>.
std::vector<T> is not some arbitrary interface; it's a concrete
class.

Practically, of course, design is often the art of compromize,
and rather than write a completely new function, one might
prefer inheritance for that reason as well. (I'm not really
sure that "conceptually" is the word I'm looking for here. I
mean something that is really pure and abstract, independently
of any practical considerations.)
 
K

Kai-Uwe Bux

James said:
Conceptually, it shouldn't. It says it wants an std::vector<T>.
std::vector<T> is not some arbitrary interface; it's a concrete
class.

Yes, that's what the signature of the function says. The question that one
has to answer with regard to the type 'word' is precisely whether a word
_is_ a vector or not. Your contention that it never is (conceptually) just
presupposes an answer that IMO cannot be given without knowing more about
the code base and the problem at hand.
Practically, of course, design is often the art of compromize,
and rather than write a completely new function, one might
prefer inheritance for that reason as well.

That, too.
(I'm not really
sure that "conceptually" is the word I'm looking for here. I
mean something that is really pure and abstract, independently
of any practical considerations.)

In my experience, something like that does not exist in programming :)


Best

Kai-Uwe Bux
 
J

James Kanze

James Kanze wrote:

[...]
Yes, that's what the signature of the function says. The
question that one has to answer with regard to the type 'word'
is precisely whether a word _is_ a vector or not.

The standards committee has already defined what is or is not a
vector. If you add or remove functionality (or even if you
change the name), it is not an std::vector.

(Maybe. I'm really just speculating here. But I do believe
that the author of the class has the responsibility of defining
what that class is, i.e. its contract. What that means exactly,
in a case like this, is open to discussion; suppose I derive
just to provide a convenience constructor. Is the result an
std::vector or not?)
Your contention that it never is (conceptually) just
presupposes an answer that IMO cannot be given without knowing
more about the code base and the problem at hand.

Yes. The question is Socratic: designed to stimulate thought,
rather than have a concrete or precise answer.
That, too.
In my experience, something like that does not exist in
programming :)

That's because you're a software engineer, and not a (pure)
computer scientist:).

(In practice, of course, I can definitely think of cases where
I'd derive publicly from std::vector. My question is just to
what degree such derivation is a pragmatic compromise, rather
than something conform to some possibly overidealist theory.
Mental masturbation, I know.)
 
K

Kai-Uwe Bux

James said:
James Kanze wrote:
[...]
template < typename T >
void reverse ( std::vector<T> & vec );
then you have to answer the question whether it should
match a word or not. Depending on the answer, inheritance
may or may not be the more correct way to go.
Conceptually, it shouldn't. It says it wants an
std::vector<T>. std::vector<T> is not some arbitrary
interface; it's a concrete class.
Yes, that's what the signature of the function says. The
question that one has to answer with regard to the type 'word'
is precisely whether a word _is_ a vector or not.

The standards committee has already defined what is or is not a
vector. If you add or remove functionality (or even if you
change the name), it is not an std::vector.

(Maybe. I'm really just speculating here. But I do believe
that the author of the class has the responsibility of defining
what that class is, i.e. its contract. What that means exactly,
in a case like this, is open to discussion; suppose I derive
just to provide a convenience constructor. Is the result an
std::vector or not?)

Assuming that deriving from std::vector is possible: The committee has
decided what a vector is, but only the author of 'word' can decide whether
a word is a vector (and in my view, that is shorthand for 'matches
functions with vector& arguments, has conversions vectors, ...'; stuff that
the language says about public derivation). I guess, the point is that I
would decide whether to publicly inherit or not just based upon whether the
technical consequences are desired or not.

Yes. The question is Socratic: designed to stimulate thought,
rather than have a concrete or precise answer.




That's because you're a software engineer, and not a (pure)
computer scientist:).

Actually, I am a mathematician (even worse); but right now, I am wearing my
programmers hat.
(In practice, of course, I can definitely think of cases where
I'd derive publicly from std::vector. My question is just to
what degree such derivation is a pragmatic compromise, rather
than something conform to some possibly overidealist theory.
Mental masturbation, I know.)

We definitely agree that public derivation from vectors can be justified by
pragmatic reasons, usually involving trade offs of some kind. I also would
contend that there are cases where _all_ consequences of public derivation
(in particular, bindings and conversions) are desired, so that no trade
offs are involved.

For the Platonic ideal, that probably is not enough as it depends on the
code base and in some way can change with time (of course, one could then
blame future changes instead of public derivation from containers). Now,
ultimately, I think all justifications in programming are pragmatic;
nonetheless there is a difference between justifying a trade off on one
hand and noting that _all_ consequences are desired on the other (even
though one falls a little short of an ideal). I guess, the platonic warning
is that: the consequences of public derivation depend on context and may
vary as the project evolves; in particular, undesired consequences can show
up at some later point. One should keep that in mind.


Best

Kai-Uwe Bux
 
H

Hicham Mouline

Jeff Schwab said:
Others have pointed out the main problems with public inheritance from
std::map: (1) deletion of the derived type through a pointer-to-base will
cause UB (since std::map has a non-virtual destructor), and (2) undesired
implicit conversions will become possible.

The best solution (IMO) is just to derive privately from std::map, and
provide using-declarations for desired parts of the interface. This is a
lot less syntax than using a member map with a bunch of nearly identical
forwarding calls.

Deriving privately does not then allow (1) nor (2), right?

Things is we have many classes that would do this, and so the segment of
code
writing the using-declarations will be repeated in all of the derived
classes.
By inheriting from MapDecorator, that interface will not be duplicated.
 

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,780
Messages
2,569,611
Members
45,277
Latest member
VytoKetoReview

Latest Threads

Top