Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

design question: deriving from a concrete class

2 views
Skip to first unread message

d...@pixar.com

unread,
Aug 15, 2005, 2:29:33 PM8/15/05
to
I've got a design issue that runs afoul of two issues:
(1) I've read many times that when writing a base class you
expect people to derive from, the class should be abstract.

(2) Lots of casting is bad.

Here's the story: I have a concrete base class TriangleMesh, which is
useful by itself. A TriangleMesh, among other things, owns/creates a
bunch of TriangleVertex objects. Because I expect the user might want
to extend the data in TriangleVertex, I make the TriangleMesh class
have a virtual "factory" function.

For example:

class TriangleVertex {
public:
virtual ~TriangleVertex();

// various accessors: e.g. position, etc.
TriangleVertex* GetClosestNeighbor();

...
};

class TriangleMesh {
public:
virtual ~TriangleMesh();

TriangleVertex* GetVertex(size_t index) {
return _vertices[index];
}

// imagine a whole bunch more accessors on TriangleVertex

private:
// default implementation:
virtual TriangleVertex* NewVertex() {
return new TriangleVertex;
}

vector<TriangleVertex*> _vertices;
};


OK, so now someone can derive off of both TriangleVertex, and
TriangleMesh, and in particular redefine NewVertex() to return their
derived TriangleVertex class:

class WeightedTriangleVertex : public TriangleVertex {
public:
double GetWeight();
void SetWeight(double);
private:
double _weight;
};

class WeightedTriangleMesh : public TriangleMesh {
private:
virtual TriangleVertex* NewVertex() {
return new WeightedTriangleVertex;
}

}:

So one thing is that when you're working in the derived classes, you
end up with a lot of casting going on, because you're constantly using
the accessors from the base mesh class, but you want back the derived
class to get to the extra state variables:

WeightedTriangleMesh* mesh = ...;
mesh->GetVertex(index)->SetWeight(...); // XX: needs a cast...

And, like I said, it bothers me slightly that TriangleMesh is useful on
its own, i.e. is concrete, because of the principle of trying to derive
from non-concrete classes.

Thoughts?


[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

Marco Manfredini

unread,
Aug 15, 2005, 5:51:31 PM8/15/05
to
d...@pixar.com wrote:

> I've got a design issue that runs afoul of two issues:
> (1) I've read many times that when writing a base class you
> expect people to derive from, the class should be abstract.
>
> (2) Lots of casting is bad.
>

> So one thing is that when you're working in the derived classes, you


> end up with a lot of casting going on, because you're constantly using
> the accessors from the base mesh class, but you want back the derived
> class to get to the extra state variables:
>
> WeightedTriangleMesh* mesh = ...;
> mesh->GetVertex(index)->SetWeight(...); // XX: needs a cast...
>

Let the user reimplement GetVertex(size_t) in WeigthedTriangleMesh to
return a WeightedTriangleVertex.

WeightedTriangleVertex* WeigthedTriangleMesh::GetVertex(size_t index)
{
return (WeightedTriangleVertex*) this->TriangleMesh::GetVertex(index);
}


> And, like I said, it bothers me slightly that TriangleMesh is useful
> on its own, i.e. is concrete, because of the principle of trying to
> derive from non-concrete classes.
>
> Thoughts?

What has the Mesh to know about the Vertex, except knowing how to create
new ones? Chance that Mesh<_VertexType_> could be a better idea?

Marco

Victor Bazarov

unread,
Aug 15, 2005, 5:54:26 PM8/15/05
to
d...@pixar.com wrote:
> I've got a design issue that runs afoul of two issues:
> (1) I've read many times that when writing a base class you
> expect people to derive from, the class should be abstract.

Where did you read that nonsense?

> (2) Lots of casting is bad.

That I agree with.

>
> [...]


> And, like I said, it bothers me slightly that TriangleMesh is useful on
> its own, i.e. is concrete, because of the principle of trying to derive
> from non-concrete classes.
>
> Thoughts?

Forget about not deriving from concrete classes and just do it.

V

Wu Yongwei

unread,
Aug 16, 2005, 7:06:00 AM8/16/05
to
d...@pixar.com wrote:
> I've got a design issue that runs afoul of two issues:
> (1) I've read many times that when writing a base class you
> expect people to derive from, the class should be abstract.

The suggestion comes from the fact that one might mistakenly slice an
object. Consider:

class A { ... };
class B : public A { ... };
A* p = new B;
A* q = new B;
...
*p = *q;

However, I do not think it justified. The fact that C++ allows object
copy is not a hindrance to design. Requiring all non-leaf class to be
abstract is counter-intuitive and the opposite works quite well in C++
and other OO languages. The real suggestion should be `avoid object
copy (use pointer assignment instead) if polymorphism is used'.

Best regards,

Yongwei

Frank Chang

unread,
Aug 16, 2005, 7:05:09 AM8/16/05
to
It appears that you are trying to create virtual constructors. The
theory behind your use of virtual constructors. As the thread from
www.parasoft.com below shows you will have to use Visual C++ 7.0 to
achieve what you want:

[20.8] What is a "virtual constructor"?
An idiom that allows you to do something that C++ doesn't directly
support.

You can get the effect of a virtual constructor by a virtual clone()
member function (for copy constructing), or a virtual create() member
function (for the default constructor).


class Shape {
public:
virtual ~Shape() { } // A virtual destructor
virtual void draw() = 0; // A pure virtual function
virtual void move() = 0;
...
virtual Shape* clone() const = 0; // Uses the copy constructor
virtual Shape* create() const = 0; // Uses the default constructor
};

class Circle : public Shape {
public:
Circle* clone() const; // Covariant Return Types; see below
Circle* create() const; // Covariant Return Types; see below
...
};

Circle* Circle::clone() const { return new Circle(*this); }
Circle* Circle::create() const { return new Circle(); }
In the clone() member function, the new Circle(*this) code calls
Circle's copy constructor to copy the state of this into the newly
created Circle object. (Note: unless Circle is known to be final (AKA a
leaf), you can reduce the chance of slicing by making its copy
constructor protected.) In the create() member function, the new
Circle() code calls Circle's default constructor.

Users use these as if they were "virtual constructors":


void userCode(Shape& s)
{
Shape* s2 = s.clone();
Shape* s3 = s.create();
...
delete s2; // You need a virtual destructor here
delete s3;
}
This function will work correctly regardless of whether the Shape is a
Circle, Square, or some other kind-of Shape that doesn't even exist
yet.

Note: The return type of Circle's clone() member function is
intentionally different from the return type of Shape's clone() member
function. This is called Covariant Return Types, a feature that was not
originally part of the language. If your compiler complains at the
declaration of Circle* clone() const within class Circle (e.g., saying
"The return type is different" or "The member function's type differs
from the base class virtual function by return type alone"), you have
an old compiler and you'll have to change the return type to Shape*.

Note: If you are using Microsoft Visual C++ 6.0, you need to change the
return types in the derived classes to Shape*. This is because MS VC++
6.0 does not support this feature of the language. Please do not write
me about this; the above code is correct with respect to the C++
Standard (see 10.3p5); the problem is with MS VC++ 6.0. Fortunately
covariant return types are properly supported by MS VC++ 7.0.

d...@pixar.com

unread,
Aug 16, 2005, 7:24:15 AM8/16/05
to
Marco writes:
What has the Mesh to know about the Vertex, except knowing how to
create
new ones? Chance that Mesh<_VertexType_> could be a better idea?


Sorry, this is a very very truncated example. Mesh needs to know a
*lot* about vertices, to do also sorts of comparisons. Mesh has to
create vertices, wire them together, form edges, faces (two more
datastructures I left out for brevity). This is not an example where
templating would be appropriate.

d...@pixar.com

unread,
Aug 16, 2005, 7:24:38 AM8/16/05
to
Victor Bazarov writes:
Where did you read that nonsense?

[Referring to: I 've read many times that when writing a base class
you expect people to derive from, the class should be abstract.]

Item 33: "Make non-leaf classes abstract."
More Effective C++.

Oh, Scott? ....

Victor Bazarov

unread,
Aug 16, 2005, 10:37:41 AM8/16/05
to
d...@pixar.com wrote:
> Victor Bazarov writes:
> Where did you read that nonsense?
>
> [Referring to: I 've read many times that when writing a base class
> you expect people to derive from, the class should be abstract.]
>
> Item 33: "Make non-leaf classes abstract."
> More Effective C++.
>
> Oh, Scott? ....

I think you misunderstood Item 33 or misstated your question. You should
only make your class abstract if it is not intended to be instantiated by
itself. Not if it is intended to be derived from. Those two concepts are
not the same.

I've written many classes that worked fine by themselves or if derived
from. They weren't abstract. Hey, std::basic_streambuf is not abstract
(is it?) and it _is_ intended to be derived from.

In his Item 32 (referred to in Item 33) Scott uses what I consider too
strong a tone. "Don't just recommend any particular use of your classes,
restrict the users". This is good in a kindergarten. In the real world,
you should rely on others not doing harm to themselves providing all kinds
of diagnostics instead of keeping them in a protective bubble.

Yes, it's possible to install all kinds of fences around the danger zone,
but that's not going to stop some folks to want to go there. Wouldn't it
be better to display a well-visible sign about the danger and let those
who do want to challenge themselves actually work around the difficulties
legally, without trying to break your code? As you can probably see, I am
all for safety wheels (that can be taken off) instead of reins and blinds.

V

Frank Chang

unread,
Aug 16, 2005, 10:40:40 AM8/16/05
to
Sorry about the typos in the last message. I meant to say that the
theory behind virtual constructors is well established. It is the
implementation of virtual constructors that varies from compiler to
compiler. The C++ standard/VC 7.0 compiler supports covariant return
types which you may want to incorporate in you design. For example ,
you can change the virtual constructor in :

class WeightedTriangleMesh : public TriangleMesh {
private:
virtual TriangleVertex* NewVertex() {
return new WeightedTriangleVertex;
}


to


class WeightedTriangleMesh : public TriangleMesh {
private:

virtual WeightedTriangleVertex* NewVertex() {
return new WeightedTriangleVertex;
}
}:

Finally, base classes do always not need to be abstract. However, in
the ARM , Margaret Ellis and Bjarne Stroustrup (page 214) write " The
abstract class mechanism supports the notion of a general concept, such
as a shape, of which only more concrete variants, such as circle and
square, can actually be used". So, you must ask yourself does your
class TriangleVertex support the notion of a general concept? Thank
you.

tony_i...@yahoo.co.uk

unread,
Aug 16, 2005, 1:59:42 PM8/16/05
to
Not sure if you noticed a crucial part of what Marco was trying to tell
you: a virtual function in a derived class can return a pointer to a
type derived from the function's return type in the base class. So,
your code changes as follows:

class WeightedTriangleMesh : public TriangleMesh {
private:

virtual WeightedTriangleVertex* NewVertex() { // <---- note new
return type
return new WeightedTriangleVertex;
}
}:

Tony

Greg

unread,
Aug 16, 2005, 7:33:47 PM8/16/05
to
d...@pixar.com wrote:
> Victor Bazarov writes:
> Where did you read that nonsense?
>
> [Referring to: I 've read many times that when writing a base class
> you expect people to derive from, the class should be abstract.]
>
> Item 33: "Make non-leaf classes abstract."
> More Effective C++.
>
> Oh, Scott? ....
>
>

Sutter's and Alexandrescu's "C++ Coding Standards" offers similar
advice: favoring abstract classes when inheriting. Regardless of its
endorsements, the advice itself is absolutely sound.

The difficulty presented when inheriting from a concrete class is
meeting the requirement of substitutability. The derived class must be
able to stand in for the base class everywhere the base class could be
used. When the base is an abstract class, this means that the derived
class must adhere to an interface, but when the base class is concrete
the derived class must adhere to both an interface and an
implementation. The derived class is often overly constrained in this
situation because its implementation must continue to meet the clients'
expectations that have been set by the base class's implementation. But
it is difficult often just to identify those expectations, much less
respect them while trying at the same time to differ from the base
class in some appreciable way

To return to the original post: I would advice not letting clients
subclass TriangleMesh just to overide one routine. Instead, place that
factory routine in a separate class and have it act as a "component"
that is passed to TriangeMesh's constructor. Clients could specify
their own components that would produce their own TriangeVertex
classes.

Alternately, make TriangleMesh a class template and clients can
specialize its behavior to suit their own custom triangle vertex types.


The solution to the casting problem (if you stick with inheritence) is
to use "covariant return types". Have the derived class override the
accessor method in the base class and declare the routine in the
derived class to return a type that is derived from the type returned
by the base class method.

Greg

Wu Yongwei

unread,
Aug 17, 2005, 4:59:33 AM8/17/05
to
Greg wrote:
> d...@pixar.com wrote:
> > Victor Bazarov writes:
> > Where did you read that nonsense?
> >
> > [Referring to: I 've read many times that when writing a base class
> > you expect people to derive from, the class should be abstract.]
> >
> > Item 33: "Make non-leaf classes abstract."
> > More Effective C++.
> >
> > Oh, Scott? ....
> >
> >
>
> Sutter's and Alexandrescu's "C++ Coding Standards" offers similar
> advice: favoring abstract classes when inheriting. Regardless of its
> endorsements, the advice itself is absolutely sound.

I really do not like this advice. Nor do I think it sound.

> The difficulty presented when inheriting from a concrete class is
> meeting the requirement of substitutability. The derived class must be
> able to stand in for the base class everywhere the base class could be
> used. When the base is an abstract class, this means that the derived
> class must adhere to an interface, but when the base class is concrete
> the derived class must adhere to both an interface and an
> implementation. The derived class is often overly constrained in this
> situation because its implementation must continue to meet the clients'
> expectations that have been set by the base class's implementation. But
> it is difficult often just to identify those expectations, much less
> respect them while trying at the same time to differ from the base
> class in some appreciable way

In a language where objects are always accessed with pointers and there
is no possibility of slicing (like Java), I do not feel
substitutability is ever a problem for concrete classes. In a more
powerful language like C++, the power of the language should not be a
hindrance to design. Just have a look at the famous class hierachies
whether there are non-leaf concrete classes. And imagine whether you
like std::exception or std::logic_error to be abstract just because
they will be inherited!

Best regards,

Yongwei

Palik Imre

unread,
Aug 17, 2005, 6:03:02 AM8/17/05
to
"Greg" <gre...@pacbell.net> writes:

> d...@pixar.com wrote:
> > Victor Bazarov writes:
> > Where did you read that nonsense?
> >
> > [Referring to: I 've read many times that when writing a base class
> > you expect people to derive from, the class should be abstract.]
> >
> > Item 33: "Make non-leaf classes abstract."
> > More Effective C++.
> >
> > Oh, Scott? ....
>
> Sutter's and Alexandrescu's "C++ Coding Standards" offers similar
> advice: favoring abstract classes when inheriting. Regardless of its
> endorsements, the advice itself is absolutely sound.

They are also saying "Consider making virtual functions nonpublic, and
public functions nonvirtual". This seems to go somewhat against
abstract base classes.

> The difficulty presented when inheriting from a concrete class is
> meeting the requirement of substitutability.

If all the public methods are nonvirtual, then it is hard not to meet
those requirement.

> The derived class must be
> able to stand in for the base class everywhere the base class could be
> used.

When one derives from an abstract base, then, using the same measure,
all the derived classes must be substitutable. Or else they can wreak
havoc in their users' code. With a concrete base, one has at least a
reference implementation.


ImRe

j

unread,
Aug 17, 2005, 7:17:42 PM8/17/05
to

"Palik Imre" <clcppm...@this.is.invalid> wrote in message
news:ammznhc...@automation.siemens.com...

> "Greg" <gre...@pacbell.net> writes:
>
> > d...@pixar.com wrote:
> > > Victor Bazarov writes:
> > > Where did you read that nonsense?
> > >
> > > [Referring to: I 've read many times that when writing a base class
> > > you expect people to derive from, the class should be abstract.]
> > >
> > > Item 33: "Make non-leaf classes abstract."
> > > More Effective C++.
> > >
> > > Oh, Scott? ....
> >
> > Sutter's and Alexandrescu's "C++ Coding Standards" offers similar
> > advice: favoring abstract classes when inheriting. Regardless of its
> > endorsements, the advice itself is absolutely sound.
>
> They are also saying "Consider making virtual functions nonpublic, and
> public functions nonvirtual". This seems to go somewhat against
> abstract base classes.

No - not really - this gives the author of the base class some
possibilities.

Making sure the base class is always accessed through a baseclass member
function allows derivees to implement protected virtual functions... And the
base class implementer can make sure something is always done before and/or
after the virtual function call... That can be something like setting up the
"window" for drawing in, or making sure there is a certain file or what ever
is needed by the derivees function... If you just make a virtual function
public, you cannot make these kind of niceties...
(This is important when you build long living frameworks, where you cannot
allow the interface to change. Implementation details can change due to OS
changes or a change in another library...)


>
> > The difficulty presented when inheriting from a concrete class is
> > meeting the requirement of substitutability.
>
> If all the public methods are nonvirtual, then it is hard not to meet
> those requirement.
>
> > The derived class must be
> > able to stand in for the base class everywhere the base class could be
> > used.
>
> When one derives from an abstract base, then, using the same measure,
> all the derived classes must be substitutable. Or else they can wreak
> havoc in their users' code. With a concrete base, one has at least a
> reference implementation.

By making a "reference" implementation you must be sure that no matter what
set of virtual functions the user chooses to implement, the new class will
still be valid... If they are all pure virtual functions, the implementor of
the derivee will have to decide for each function, how they will be
implemented.

A reference implementation should be another class inheriting from the
abstract class (in my opinion).
Leaving some functions virtual and implemented, can easily mislead the
implementor of a derivee. I try never to do "default" implementations, since
there seldom seems to be a good default implementation.
please consider: If you have a shape base class, what would the "default"
implementation of the
"virtual double Area()" function be??? Something like {return 0.0;};?? And
how often would that end up being the wrong implementation?? If that
function was pure virtual, the compiler would complain if it was not
considered during the implementation of a derivee.. That would make it so
much easier figuring out why all polygons seems to have an area of 0.0.....
:)
[excess quoting deleted --mod]
Jesper Madsen, SAXoTECH

Andrei Alexandrescu (See Website For Email)

unread,
Aug 18, 2005, 9:11:19 AM8/18/05
to
Palik Imre wrote:

> "Greg" <gre...@pacbell.net> writes:
>>Sutter's and Alexandrescu's "C++ Coding Standards" offers similar
>>advice: favoring abstract classes when inheriting. Regardless of its
>>endorsements, the advice itself is absolutely sound.
>
>
> They are also saying "Consider making virtual functions nonpublic, and
> public functions nonvirtual". This seems to go somewhat against
> abstract base classes.

I don't think it does. It goes along the idea of pushing policy up and
implementation down. A class with nonvirtual public functions that serve
as checked call gates into nonpublic virtual functions remains abstract.


Andrei

Viper Craft

unread,
Aug 18, 2005, 9:10:57 AM8/18/05
to

d...@pixar.com писал(а):

> Thoughts?
>

I think you can use "Visitor" pattern for your task...

Greg

unread,
Aug 18, 2005, 9:14:09 AM8/18/05
to

>
> ImRe

For the purposes of this discussion, I was interpreting "abstract" with
its usual English meaning, and therefore include any class without a
significant implementation. An abstract class defines an interface for
a concept, essentially.

The specific advice I was endorsing however did not deal with abstract
classes in either sense of the word. Rather the advice was not to
inherit from a "concrete" class - which I would describe as a class
with a signficant, and fully realized implementation. A concrete class
almost by definition cannot meaningfully be derived from since it has
fully specified the behavior of the abstract interface declared by its
base. A concrete class leaves little room for any derived class to
re-specify the behavior of the interface without breaking its ability
to be a substitute for the concrete class.

>From the description of the TriangleMesh class in the original post, it
sounded like the class was not abstract in either sense of the word,
but concrete. I think therefore that the poster was justified in any
reluctance to have clients further subclass it.

Greg

d...@pixar.com

unread,
Aug 19, 2005, 10:32:12 AM8/19/05
to
>From the description of the TriangleMesh class in the original post, it
> sounded like the class was not abstract in either sense of the word,

Absolutely correct. My primary reason for deriving from the class is
that I wish to add absolutely new state information, but this state
information won't influence the behavior of any existing functions.

So what's the design rule when you wish to add extra state information
to a concrete class?
I could of course forgo derivation completely, by for example having a
map<TriangleVertex*, ExtraStateInfo>
so that given a triangle vertex, I can get to my extra state
information, but I'm not wild about this.

David Terrell

unread,
Aug 19, 2005, 11:06:07 AM8/19/05
to
Andrei Alexandrescu (See Website For Email) <SeeWebsit...@moderncppdesign.com> says:
> Palik Imre wrote:
>> "Greg" <gre...@pacbell.net> writes:
>>>Sutter's and Alexandrescu's "C++ Coding Standards" offers similar
>>>advice: favoring abstract classes when inheriting. Regardless of its
>>>endorsements, the advice itself is absolutely sound.
>>
>>
>> They are also saying "Consider making virtual functions nonpublic, and
>> public functions nonvirtual". This seems to go somewhat against
>> abstract base classes.
>
> I don't think it does. It goes along the idea of pushing policy up and
> implementation down. A class with nonvirtual public functions that serve
> as checked call gates into nonpublic virtual functions remains abstract.

It's generally sound, but for this particular example where return
value covariance is being suggested, it might not be the right idea.

--
David Terrell
d...@meat.net
http://meat.net/ (( meatspace ))

Palik Imre

unread,
Aug 19, 2005, 11:05:22 AM8/19/05
to
"Greg" <gre...@pacbell.net> writes:

> For the purposes of this discussion, I was interpreting "abstract" with
> its usual English meaning, and therefore include any class without a
> significant implementation. An abstract class defines an interface for
> a concept, essentially.

According to the C++ Standard (10.4) a class is abstract, if it has at
least one pure virtual function.

> The specific advice I was endorsing however did not deal with abstract
> classes in either sense of the word. Rather the advice was not to
> inherit from a "concrete" class - which I would describe as a class
> with a signficant, and fully realized implementation. A concrete class
> almost by definition cannot meaningfully be derived from since it has
> fully specified the behavior of the abstract interface declared by its
> base.

I think, this very property makes a class with at least some
implementation (and without virtual public methods) really suitable to
be used as a derived class. If somebody designs a base class, he/she
also designs an interface with it, together with certain constrains
and expectations concerning the use of this interface. The
implementation of the interface can check whether the virtual
functions providing the extension points are well behaved. Making
sure, that the derived class won't try to respecify the behavior, only
extend it in ways allowed by the base class designer.

> A concrete class leaves little room for any derived class to
> re-specify the behavior of the interface without breaking its ability
> to be a substitute for the concrete class.

I consider respecifying the interface in a base class an error.
Imagine for example an operator+ that multiplies its arguments. This
also remains substitutable in the syntactical sense, but not
semantically.

ImRe

Palik Imre

unread,
Aug 19, 2005, 11:13:58 AM8/19/05
to
"Andrei Alexandrescu (See Website For Email)" <SeeWebsit...@moderncppdesign.com> writes:

> Palik Imre wrote:
> > "Greg" <gre...@pacbell.net> writes:
> >>Sutter's and Alexandrescu's "C++ Coding Standards" offers similar
> >>advice: favoring abstract classes when inheriting. Regardless of its
> >>endorsements, the advice itself is absolutely sound.
> >
> >
> > They are also saying "Consider making virtual functions nonpublic, and
> > public functions nonvirtual". This seems to go somewhat against
> > abstract base classes.
>
> I don't think it does. It goes along the idea of pushing policy up and
> implementation down. A class with nonvirtual public functions that serve
> as checked call gates into nonpublic virtual functions remains abstract.

This might be 100% true for library development, where one can hardly
have any domain expertise. But at least in those applications I have
been working on, I have been able to write a meaningful default
behaviour for the extension points quite often. Making the base class
abstract in spite of the implemented extension points, seems like a
pain in the bottom.

Actually, I remember a case, when I enthusiastically made my defaulted
extension points abstract, and then later those who had to extend the
class, talked me into removing the pure specifier, so they had to
override only those methods they really wanted to change.

ImRe

Nicola Musatti

unread,
Aug 20, 2005, 6:20:40 AM8/20/05
to

Palik Imre wrote:
> "Andrei Alexandrescu (See Website For Email)" <SeeWebsit...@moderncppdesign.com> writes:
[...]

> > I don't think it does. It goes along the idea of pushing policy up and
> > implementation down. A class with nonvirtual public functions that serve
> > as checked call gates into nonpublic virtual functions remains abstract.
>
> This might be 100% true for library development, where one can hardly
> have any domain expertise. But at least in those applications I have
> been working on, I have been able to write a meaningful default
> behaviour for the extension points quite often. Making the base class
> abstract in spite of the implemented extension points, seems like a
> pain in the bottom.
>
> Actually, I remember a case, when I enthusiastically made my defaulted
> extension points abstract, and then later those who had to extend the
> class, talked me into removing the pure specifier, so they had to
> override only those methods they really wanted to change.

An approach which might cover both issues is to make the base class
abstract, but to provide default implementations for selected member
functions by means of class templates, e.g.:

class Base {
public:
virtual ~Base() {}
virtual int f(int) = 0;
};

template <typename BaseT> class BaseImpl {
public:
int f(int i) { return i; }
};

I find that for complex hyerarchies it is not uncommon to be able to
split default implementations across several class templates that may
be reused for different concrete classes.

Cheers,
Nicola Musatti

Gerhard Menzl

unread,
Aug 22, 2005, 6:10:50 AM8/22/05
to
d...@pixar.com wrote:

> Absolutely correct. My primary reason for deriving from the class is
> that I wish to add absolutely new state information, but this state
> information won't influence the behavior of any existing functions.
>
> So what's the design rule when you wish to add extra state information
> to a concrete class?
> I could of course forgo derivation completely, by for example having a
> map<TriangleVertex*, ExtraStateInfo>
> so that given a triangle vertex, I can get to my extra state
> information, but I'm not wild about this.

A design guideline from an increasingly popular book reads:

"Public inheritance is substitutability. Inherit, not to reuse, but to
be reused." (1)

To reuse a concrete class, composition would be the preferred way: make
a TriangleVertex object a member of WeightedTriangleVertex. Sure, it
means writing a few extra forwarding functions, but your design will be
cleaner.

The rationale behind the guideline not to inherit from concrete classes
is not restricted to comparatively low-level language issues such as
virtual destructors or slicing; it's mainly about design. Inheritance is
a relationship that causes strong coupling, and class hierarchies that
result from deriving for the sake of reuse tend to become large,
unwieldy, and brittle. That's why non-leaf classes should be abstract.
You won't corrupt your program just by deriving from a concrete class
once, but your design will suffer if you do it routinely.


(1) H. Sutter, A. Alexandrescu: C++ Coding Standards (Addison-Wesley,
2005), p. 64 (2)

(2) This is not a plug just because I won an autographed copy.

--
Gerhard Menzl

#dogma int main ()

Humans may reply by replacing the thermal post part of my e-mail address
with "kapsch" and the top level domain part with "net".

Wu Yongwei

unread,
Aug 23, 2005, 2:48:25 AM8/23/05
to
Gerhard Menzl wrote:
> "Public inheritance is substitutability. Inherit, not to reuse, but to
> be reused." (1)
>
> To reuse a concrete class, composition would be the preferred way: make
> a TriangleVertex object a member of WeightedTriangleVertex. Sure, it
> means writing a few extra forwarding functions, but your design will be
> cleaner.
>
> The rationale behind the guideline not to inherit from concrete classes
> is not restricted to comparatively low-level language issues such as
> virtual destructors or slicing; it's mainly about design. Inheritance is

It was exactly Scott Meyers that talked in length about the issue of
slicing in More Effectice C++. The exact item `Make non-leaf classes
abstract' is the one least persuasive in my opinion.

> a relationship that causes strong coupling, and class hierarchies that
> result from deriving for the sake of reuse tend to become large,
> unwieldy, and brittle. That's why non-leaf classes should be abstract.
> You won't corrupt your program just by deriving from a concrete class
> once, but your design will suffer if you do it routinely.

I have not read C++ Coding Standards so my opinions could be a little
irrelevant, but I think one problem of why C++ is so hard to learn (and
to teach) is that there are many guidelines to complicate the design.
To take Scott's original example, there used to be Animal, Lizard, and
Chicken --- three classes. Scott made them into AbstractAnimal,
Animal, Lizard, and Chicken --- four classes. How many classes would
you here suggest?

Is this kind of design natural and easy to maintain? Do we really need
to flatten a natural multi-level hierarchy, or double the number of
classes by inserting `impl' classes? I do not think these ideas
attractive at all.

I would like my design to conform to the basic OO principles (Liskov
Substitution, Open-Closed, etc.) and then make it clear and easy to
understand. And sometimes I would even ignore them too because the
case is simple and some object-based design (w/o polymorphism) would be
enough.

And no one has really named the benefits to make std::exception,
std::runtime_error, etc. abstract, I suppose.

Best regards,

Yongwei

Gerhard Menzl

unread,
Aug 23, 2005, 10:37:33 AM8/23/05
to
Wu Yongwei wrote:

> It was exactly Scott Meyers that talked in length about the issue of
> slicing in More Effectice C++. The exact item `Make non-leaf classes
> abstract' is the one least persuasive in my opinion.

Scott Meyers emphasizes hands-on, practical advice. That's one thing
that makes his books so nice to read. Consequently, said item is mainly
about the direct, tangible consequences of having concrete non-leaf
classes. But it's not just about slicing. If you read it carefully, you
will also find the message: a possible consequence of class hierarchies
with concrete non-leafs is type checking at runtime. Now this might be
the most normal thing for Java programmers, but it goes against the
spirit of C++, which favours static type checking.

> I have not read C++ Coding Standards so my opinions could be a little
> irrelevant, but I think one problem of why C++ is so hard to learn
> (and to teach) is that there are many guidelines to complicate the
> design.

C++ leaves a lot of design freedom to the programmer. It doesn't tie you
to a single programming paradigm, and there are almost always different
ways of achieving the same goal. Freedom means choice, choice requires
experience, and experience is hard to win. That, and a number of
unfortunate compromises resulting from the C heritage, is what makes C++
hard to learn. But guidelines? Guidelines are meant to reduce the
bewildering set of choices and help you to achieve a cleaner, not a more
complicated design.

> To take Scott's original example, there used to be Animal, Lizard, and
> Chicken --- three classes. Scott made them into AbstractAnimal,
> Animal, Lizard, and Chicken --- four classes. How many classes would
> you here suggest?

As few as possible and as many as necessary. I find the class "Animal"
suspicious, but if the problem domain really calls for treating lizards
and chicken separately and all other animals uniformly, *and* if these
groups share enough common behaviour to justify polymorphic abstraction,
than I would go for Animal (abstract), Lizard, Chicken, and OtherAnimal.
If the behaviour of the three groups is so diverse that there is not
much to be gained from treating them uniformly, I would simply do away
with inheritance and keep them separated all the time.

> Is this kind of design natural and easy to maintain? Do we really
> need to flatten a natural multi-level hierarchy, or double the number
> of classes by inserting `impl' classes? I do not think these ideas
> attractive at all.

Nothing in software engineering is "natural". Just because there exists
a taxonomic hierarchy in biology doesn't mean that it's a good idea to
partition the source code for a zoo management application analogously.
Inheritance is primarily a tool for abstraction and decoupling, not for
taxonomy modelling.

As for the question whether the design using one abstract and three
concrete classes is easier to maintain: typically yes. When deriving
from a concrete class, you get strong coupling between the
implementations of the base class and of the derived class. A change to
the implementation of Animal (as a concrete base class) can have subtle
and unwanted consequences for both Lizard and Chicken. An abstract base
class doesn't normally inflict this problem, since it has no or little
behaviour (such as checking pre- and postconditions). If you change the
implementation of OtherAnimal (now a concrete sibling class), the
effects on Lizard and Chicken will be nil.

Pimpl classes *are* a pain. The incomplete separation of interface and
implementation, which is a result of the separate compilation/dumb
linker model inherited from C, is one of the few things I hate about
C++. It's still better than Java which doesn't have any such separation
at all and forces you to use inheritance and factories to achieve it.

> I would like my design to conform to the basic OO principles (Liskov
> Substitution, Open-Closed, etc.) and then make it clear and easy to
> understand. And sometimes I would even ignore them too because the
> case is simple and some object-based design (w/o polymorphism) would
> be enough.

By all means. Polymorphism (both dynamic and static) is not an end in
itself. I use it when it yields useful abstraction and helps to decouple
software components. Otherwise, a bunch of classes might be just enough.


--
Gerhard Menzl

#dogma int main ()

Humans may reply by replacing the thermal post part of my e-mail address
with "kapsch" and the top level domain part with "net".

[ See http://www.gotw.ca/resources/clcm.htm for info about ]

Wu Yongwei

unread,
Aug 24, 2005, 4:06:29 AM8/24/05
to
Gerhard Menzl wrote:
> Wu Yongwei wrote:
>
> > It was exactly Scott Meyers that talked in length about the issue of
> > slicing in More Effectice C++. The exact item `Make non-leaf classes
> > abstract' is the one least persuasive in my opinion.
>
> Scott Meyers emphasizes hands-on, practical advice. That's one thing
> that makes his books so nice to read. Consequently, said item is mainly
> about the direct, tangible consequences of having concrete non-leaf
> classes. But it's not just about slicing. If you read it carefully, you
> will also find the message: a possible consequence of class hierarchies
> with concrete non-leafs is type checking at runtime. Now this might be
> the most normal thing for Java programmers, but it goes against the
> spirit of C++, which favours static type checking.

I know a little Java, but do not code for real projects, so I will not
comment on the Java part, though I do not quite agree with you (see
below). The use of dynamic_cast in Scott's item is only to ensure the
assignment (or copy-construction) is correct, which is really *ugly*.
My point is that if one takes the Java way and do not copy objects but
only copy pointers (create objects in free store), the problem itself
is no more.

I do not use dynamic_cast (or typeinfo) generally.

> > I have not read C++ Coding Standards so my opinions could be a little
> > irrelevant, but I think one problem of why C++ is so hard to learn
> > (and to teach) is that there are many guidelines to complicate the
> > design.
>
> C++ leaves a lot of design freedom to the programmer. It doesn't tie you
> to a single programming paradigm, and there are almost always different
> ways of achieving the same goal. Freedom means choice, choice requires
> experience, and experience is hard to win. That, and a number of
> unfortunate compromises resulting from the C heritage, is what makes C++
> hard to learn. But guidelines? Guidelines are meant to reduce the
> bewildering set of choices and help you to achieve a cleaner, not a more
> complicated design.

Hard to argue on theories. Let's just talk about detail examples, as
above and below.

> > To take Scott's original example, there used to be Animal, Lizard, and
> > Chicken --- three classes. Scott made them into AbstractAnimal,
> > Animal, Lizard, and Chicken --- four classes. How many classes would
> > you here suggest?
>
> As few as possible and as many as necessary. I find the class "Animal"
> suspicious, but if the problem domain really calls for treating lizards
> and chicken separately and all other animals uniformly, *and* if these
> groups share enough common behaviour to justify polymorphic abstraction,
> than I would go for Animal (abstract), Lizard, Chicken, and OtherAnimal.
> If the behaviour of the three groups is so diverse that there is not
> much to be gained from treating them uniformly, I would simply do away
> with inheritance and keep them separated all the time.

Treating some separately, I think, is not a choice. It is quite usual
you need to put them in a container.

> > Is this kind of design natural and easy to maintain? Do we really
> > need to flatten a natural multi-level hierarchy, or double the number
> > of classes by inserting `impl' classes? I do not think these ideas
> > attractive at all.
>
> Nothing in software engineering is "natural". Just because there exists

Ah that is absolutely not my opinion. I design to find the most
natural expression. As an ancient Chinese poet wrote about poems: `A
good expression is heaven-made, and a good hand can find it
occasionally.' :-)

> a taxonomic hierarchy in biology doesn't mean that it's a good idea to
> partition the source code for a zoo management application analogously.
> Inheritance is primarily a tool for abstraction and decoupling, not for
> taxonomy modelling.

This I agree, like the famous example that a square is not a rectangle,
since the requirement of Liskov substitution cannot be met. But I
refrain myself only at the basic OO principles.

> As for the question whether the design using one abstract and three
> concrete classes is easier to maintain: typically yes. When deriving
> from a concrete class, you get strong coupling between the
> implementations of the base class and of the derived class. A change to

Sometimes it is wanted. Sometimes the base class is very simple so
that any behaviour change in the implementation is really an interface
change as well. I would like to name the example of std::exception
again.

> the implementation of Animal (as a concrete base class) can have subtle
> and unwanted consequences for both Lizard and Chicken. An abstract base
> class doesn't normally inflict this problem, since it has no or little
> behaviour (such as checking pre- and postconditions). If you change the
> implementation of OtherAnimal (now a concrete sibling class), the
> effects on Lizard and Chicken will be nil.

[snipped]

To take another example from my domain. I deal with packets. Let's
assume all IP packets are Ethernet packets, and we need to deal with
TCP and UDP packets a little differently. How will you design and how
many classes will you come to? I picked the simplest one and have not
found problems so far:

EthernetPacket
|
IpPacket
/ \
TcpPacket UdpPacket

I guess you will add OtherEthernetPacket and OtherIpPacket? This idea
is not bad in itself, but I do not feel enough benefits in maintenance,
understanding, or performance.

Best regards,

Yongwei

Gerhard Menzl

unread,
Aug 24, 2005, 10:51:19 AM8/24/05
to
Wu Yongwei wrote:

> I know a little Java, but do not code for real projects, so I will not
> comment on the Java part, though I do not quite agree with you (see
> below). The use of dynamic_cast in Scott's item is only to ensure the
> assignment (or copy-construction) is correct, which is really *ugly*.
> My point is that if one takes the Java way and do not copy objects but
> only copy pointers (create objects in free store), the problem itself
> is no more.

I don't think you can decide once and for all whether to copy or to
share. It really depends on the context. You share when all participants
need a uniform view of an object, and you copy when a piece of software
needs to apply changes that should not affect other pieces of software.
Lifetime issues also play a role.

>>As few as possible and as many as necessary. I find the class "Animal"
>>suspicious, but if the problem domain really calls for treating
>>lizards and chicken separately and all other animals uniformly, *and*
>>if these groups share enough common behaviour to justify polymorphic
>>abstraction, than I would go for Animal (abstract), Lizard, Chicken,
>>and OtherAnimal. If the behaviour of the three groups is so diverse
>>that there is not much to be gained from treating them uniformly, I
>>would simply do away with inheritance and keep them separated all the
>>time.
>
> Treating some separately, I think, is not a choice. It is quite usual
> you need to put them in a container.

Again, it depends. If there is a meaningful abstraction that hides all
class-specific behaviour behind a uniform interface, you can use dynamic
polymorphism and store all Animals in a single container. But if there
isn't, or so little of it, that you find the need to restore the
concrete type all the time, throwing it away in the first place is
folly. If Lizards and Chickens need to be treated differently most of
the time, you had better keep them apart.

>>Nothing in software engineering is "natural". Just because there
>>exists
>
> Ah that is absolutely not my opinion. I design to find the most
> natural expression. As an ancient Chinese poet wrote about poems: `A
> good expression is heaven-made, and a good hand can find it
> occasionally.' :-)

I do not doubt that your poet was a wise man, but he wrote poems, not
software systems. Apart from this, I did not mean to deny that sometimes
representations of real world objects in software come naturally. What I
meant to say is that you can't build software systems just by looking at
nature, turning things into objects and taxonomies into class hierarchies.

>>As for the question whether the design using one abstract and three
>>concrete classes is easier to maintain: typically yes. When deriving
>>from a concrete class, you get strong coupling between the
>>implementations of the base class and of the derived class. A change
>>to
>
> Sometimes it is wanted. Sometimes the base class is very simple so
> that any behaviour change in the implementation is really an interface
> change as well. I would like to name the example of std::exception
> again.

There is a sometimes for (almost) every rule or guideline. "Make
non-leaf classes abstract" is not to be read as "or face eternal
damnation if you do", it's more like "usually it is not a good idea,
especially if you do it all the time, so be very skeptical about it".

I don't know what the rationale for making std::exception concrete was.
Scott's advice holds mainly for OO-dominated C++ programs. Things are
often different in generic programming where inheritance can be used for
different purposes. If I remember correctly, traits classes are another
example.

> To take another example from my domain. I deal with packets. Let's
> assume all IP packets are Ethernet packets, and we need to deal with
> TCP and UDP packets a little differently. How will you design and how
> many classes will you come to? I picked the simplest one and have not
> found problems so far:
>
> EthernetPacket
> |
> IpPacket
> / \
> TcpPacket UdpPacket
>
> I guess you will add OtherEthernetPacket and OtherIpPacket? This idea
> is not bad in itself, but I do not feel enough benefits in
> maintenance, understanding, or performance.

I wouldn't add anything before having analyzed what behaviour your
packets have and how you use them.

--
Gerhard Menzl

#dogma int main ()

Humans may reply by replacing the thermal post part of my e-mail address
with "kapsch" and the top level domain part with "net".

[ See http://www.gotw.ca/resources/clcm.htm for info about ]

Wu Yongwei

unread,
Aug 25, 2005, 8:17:29 AM8/25/05
to
Gerhard Menzl wrote:
> Wu Yongwei wrote:
> > I know a little Java, but do not code for real projects, so I will not
> > comment on the Java part, though I do not quite agree with you (see
> > below). The use of dynamic_cast in Scott's item is only to ensure the
> > assignment (or copy-construction) is correct, which is really *ugly*.
> > My point is that if one takes the Java way and do not copy objects but
> > only copy pointers (create objects in free store), the problem itself
> > is no more.
>
> I don't think you can decide once and for all whether to copy or to
> share. It really depends on the context. You share when all participants
> need a uniform view of an object, and you copy when a piece of software
> needs to apply changes that should not affect other pieces of software.
> Lifetime issues also play a role.

So whether one should make non-leaf classes abstract depends on
contexts too. I would rather make copy-constructor and assignment
operator private and add a (Java-like) virtual `clone' member function
if object copying is really needed.

> > Treating some separately, I think, is not a choice. It is quite usual
> > you need to put them in a container.
>
> Again, it depends. If there is a meaningful abstraction that hides all
> class-specific behaviour behind a uniform interface, you can use dynamic
> polymorphism and store all Animals in a single container. But if there
> isn't, or so little of it, that you find the need to restore the
> concrete type all the time, throwing it away in the first place is
> folly. If Lizards and Chickens need to be treated differently most of
> the time, you had better keep them apart.

In that case, OO is really not quite relevant and the inheritance
itself is doubtful, I suppose.

> >>Nothing in software engineering is "natural". Just because there
> >>exists
> >
> > Ah that is absolutely not my opinion. I design to find the most
> > natural expression. As an ancient Chinese poet wrote about poems: `A
> > good expression is heaven-made, and a good hand can find it
> > occasionally.' :-)
>
> I do not doubt that your poet was a wise man, but he wrote poems, not
> software systems. Apart from this, I did not mean to deny that sometimes
> representations of real world objects in software come naturally. What I
> meant to say is that you can't build software systems just by looking at
> nature, turning things into objects and taxonomies into class hierarchies.

This is absolutely not what I meant. I meant the naturalness
(properties like simplicity, elegance, compactness, ease of
understanding, etc.) in the expression, but not of its similarity to
the real-world entities. Some code on which you may cry out `Eureka'.

> > Sometimes it is wanted. Sometimes the base class is very simple so
> > that any behaviour change in the implementation is really an interface
> > change as well. I would like to name the example of std::exception
> > again.
>
> There is a sometimes for (almost) every rule or guideline. "Make
> non-leaf classes abstract" is not to be read as "or face eternal
> damnation if you do", it's more like "usually it is not a good idea,
> especially if you do it all the time, so be very skeptical about it".
>
> I don't know what the rationale for making std::exception concrete was.
> Scott's advice holds mainly for OO-dominated C++ programs. Things are
> often different in generic programming where inheritance can be used for
> different purposes. If I remember correctly, traits classes are another
> example.

Even in the OO world, there are several different ways to do things. I
have shown above that once I eliminate public copy-construction and
assignment, the very guideline we are discussing now becomes not quite
beneficial.

> > EthernetPacket
> > |
> > IpPacket
> > / \
> > TcpPacket UdpPacket
> >
> > I guess you will add OtherEthernetPacket and OtherIpPacket? This idea
> > is not bad in itself, but I do not feel enough benefits in
> > maintenance, understanding, or performance.
>
> I wouldn't add anything before having analyzed what behaviour your
> packets have and how you use them.

I quite agree with you here :-). So I have not added any more classes
to the hierarchy and kept all of them concrete, since no problems or
difficulties have been encountered so far.

Best regards,

Yongwei

0 new messages