Boost logo

Boost :

From: Bob Bell (belvis_at_[hidden])
Date: 2005-02-27 12:52:23


Jeff Garland <jeff <at> crystalclearsoftware.com> writes:
>
> On Fri, 25 Feb 2005 18:26:22 +0000, Jonathan Wakely wrote
> > It's generally a bad idea to inherit from concrete classes that weren't
> > designed to be inherited from, not just STL containers.
>
> Well again, to me there are other cases in the design of concrete classes
> where there is some advantage in inheriting from a base class with a
> non-virtual destructor. For example in date-time some time duration classes
> are derive from a base class with no virtual destructor. These subclasses are
> simply a constructor the adjust for precision. So for example:
>
> class time_duration {
> time_duration(int hour, int minute, int second, int millisecond)
> {
> //calc duration in terms of millisconds
> }
> //gobs of other complex stuff like operator+, operator-, etc
> private:
> int total_milliseconds;
> };
>
> Nothing wrong with this, but there's lots of places where writing constructing
> one of these time_durations is just ugly. For example, suppose I want to add
> 10 seconds to 10 milliseconds:
>
> //ugly code....
> time_duration td = time_duration(0,0,10,0) + time_duration(0,0,0,10);
>
> So a few simple conversion classes help our cause:
>
> class seconds : public time_duration
> {
> seconds(int s) : time_duration(0,0,s,0) {}
> };
>
> class milliseconds : public time_duration
> {
> milliseconds(int s) : time_duration(0,0,0,s) {}
> };
>
> Now we can write:
>
> //code with obvious meaning
> time_duration td = seconds(10) + milliseconds(10);
>
> Again, we've derived from a class where we don't want virtual functions, we do
> want to substitite the base class, and it is totally safe because of no
> allocation / trivial destructor in the sub-class.

Given that you've got these classes called "seconds" and "milliseconds", I'd
expect to be able to do things like:

seconds s = seconds(10) + milliseconds(20);
milliseconds ms = seconds(10);
++s; // increment one second
++ms; // increment one millisecond

void foo(seconds);

time_duration td(/* ... */);
foo(td);

Are these things supported? They are all, "code with obvious meaning," (I
suppose they could be supported, I don't really know. However, it seems like
it would be a lot of work to do, and would only be necessary because of the
introduction of the extra classes.)

If all you want is to support the creation of simple time units, why not have
named functions:

time_duration seconds(int secs)
{
   return time_duration(0, 0, secs, 0);
}

time_duration milliseconds(int msecs)
{
   return time_duration(0, 0, 0, msecs);
}

We can still write:

//code with obvious meaning
time_duration td = seconds(10) + milliseconds(10);

But no additional classes are needed, and none of the corner cases come
up or need to be supported.

IMHO, inheritance is often too broad a brush. You get the effect you're after,
plus other effects you may not have wanted or anticipated. It's usually a
good idea to use as fine a brush as you can get, and that means that if
there's some way to implement what you want without using inheritance,
you should probably not use inheritance.

> > If the class wasn't intended to be a base class it probably doesn't have
> > any protected members, only public and private. If that's the case
> > you don't gain anything by inheriting from it, except the
> > convenience of not havng to declare forwarding functions for all the
> > members - but that's laziness, not good design.
>
> I'd say there is something about writing less code (not laziness) that makes
> it a better design. Having to forward or reimplement the whole interface of
> an stl container is non-trivial and probably a bad design.

I'd say that the achievement of writing (a little) less code at the expense of
tighter coupling _is_ laziness. It's actually is pretty trivial to write
forwarding functions of an standard container -- so trivial it wouldn't take
long to write a code generator for it -- especially when you consider that the
whole interface is rarely required.

Which brings me to another point about inheriting from standard containers
(I'm not sure if this applies to inheriting from other concrete types). In
every case I can remember (including cases where I used to derive from
containers myself), the desired result was a class that had a more restriced
interface and/or behavior than the base (container) class. For example,
creating a Polygon class by deriving from std::vector<Point>, then trying to
limit the interface that only support the Polygon concept.

In every case, achieving this kind of limiting turned out to be easier without
using inheritance -- in fact, achieving this with public inheritance turns out
to be pretty much impossible, so at some point you have to throw up your
hands and decide to live with errors.

One lesson I take from this, which I think does apply to (publicly) deriving
from concrete classes is: when the semantics of the base class aren't pretty
much exactly what you're after, don't derive from a concrete class. Because
concrete classes offer no ability to customize or alter the semantics (for
example, by providing virtual functions that can be overridden), sooner or
later your derived class is going to be used in contexts where it has to act
exactly like a base class. For example:

void Foo(concrete_class& x);

derived_class d;

Foo(d);

Foo is going to expect its argument to behave _exactly_ like a concrete_class,
so instances of derived_class better do so.

In my experience, this kind of alignment of semantics between a base
concrete class and its derived class is rare. I don't know anything about the
date_time library, but I'd be surprised if it were true for time_duration,
seconds and milliseconds.

[snip]

> > If you want to inherit from, say, vector, do you _really_ want _all_
> > its public member functions?
>
> Yes, I frequently do.

All I can say to this is that every programmer whom I've ever encountered
who answered this question "yes" later changed his mind when we examined
the entirety of vector's public interface.

I won't go so far as to say "never" publicly derive from concrete classes or
standard containers (I always want to leave myself a little wiggle room ;-),
but I will say that in all the years I've worked with C++, I can't remember
ever seeing a good example of it.

Bob


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk