Re: Solving the data inheritance problem
Greg Herlihy wrote:
Hello, I'd like to discuss about solutions to the following general
Given is an abstract class A (that is, contains at least one virtual
function). The derived classes of A are called B[i] (indexed by i).
The main purpose of inheritance is interface inheritance: this is to say
that all B[i] have commonly describable behaviour.
I think most C++ experts would disagree. Inheritance (or creating a
subtype) is about substitutability - B is an A and can be used
wherever an A is needed. So inheritance is best thought of in terms of
behavior (or more formally, a contract) than in terms of interfaces and
code re-use. See the Liskov Substitution Principle"
http://en.wikipedia.org/wiki/Liskov_Substitution_Principle and "C++
Coding Standards" by Sutter and Alexandrescu (especially #32 and
Just to be clear here, when talking about a sub-type as defined by LSP
this is not synonymous with a sub-class in OO terms. LSP talks about
the substitutability of one type for another where the client code
makes use of a property (or set of properties) of the given type.
Whether or not another type is substitutable in the LSP sense depends
on whether the properties that the client code relies on are the same
in the two types, and nothing to do with any other expression of
connection between the two types.
In terms of substitutability we need to realise that when we design a
class hierarchy in order to use inclusional polymorphism (that is a
sub-class is 'included' within the type of a super-class) and when we
make use of a hierarchy in order to help us to enforce operational
polymorphism (that is what the classes do) they are both covered by
LSP, but our thinking and reasoning about the design of the classes
changes between the two.
For a class like std::map< key, value > we know that there are
constraints that the map implementation (and definition in the
standard) enforces on the abstract type 'key' and that we must conform
to in order to substitute any other type in its place. This has nothing
to do with sub-classing from some common ancestor.
Languages like Java which lack generic programming enforce the use of
hierarchies that switch an operational polymorphism design into
inclusional polymorphism implementation because the langauge lacks the
tools to express the design clearly (and Java interfaces are just a
(nasty) way of doing this). In C++ we don't have this constraint
because our language enables us to express the design clearly and
exactly to start with.
inclusional polymorphism at all - everything is done in terms of
operational polymorphism. Even Smalltalk which at first glance looks
like a language that enforces inclusional polymorphism is actually
Class hierarchies are most useful when we look at issues of static data
modeling and issues of definition. For behavioural/dynamical issues we
should normally switch to an operational polymorphism view.
The final point then becomes whether we choose to implement the
operational polymorphism through templates (as the STL does) or through
virtual functions. In C++ this becomes an implementation detail with
one input becoming how and when the client code that uses our classes
gets used and another being which forms of behavioural extension we
wish to support.
If you mix up the issues between inclusional and operational
polymorphism in your design then it will never be clean and easy to
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]