
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