|
4. Constructors, Destructors, Inheritancerevised 15 Feb 2001 |
This page continues the class design topic. See the next page for class implementation.
[ 2000-06-16
-- to next update]
Eckel 1995 page 774 writes that any class with pointers as data
members will need all three. Martin Aupperle (private communication)
adds the important proviso, "if the class maintains resources (is the
owner of resources)".
A class that doesn't own dynamically allocated resources probably doesn't need a copy constructor, assignment operator, or destructor (but see below).
operator=()
needs to
const
as argument
const
reference.
The function signature should be
T& operator=(const T& rhs);and the function itself should follow this pattern:
T& T::operator=(const T& rhs) { if (this == &rhs) return *this; // do the assignment stuff return *this; }[ 2000-06-16 -- to next update]
Rational::Rational(int top=0, int bottom=1) { top_ = top; bottom_ = bottom; }but this:
Rational::Rational(int top=0, int bottom=1) : top_(top), bottom_(bottom) { }I'll remember not to count on having the items in an initializer list processed in any particular order. (Actually, they're initialized in the order of the data members in the class definition. But it's not safe to rely on that, since I might later rearrange the data members. As Gollum said, "Tricksy! Tricksy!")
There's a related rule for
general code.
They're also safer: if the constructor has any actual code it knows that the data members are all initialized before the opening brace, so that it can confidently call their member functions if it needs to.
Initializer lists may look odd at first, especially to old hands at C. But
I think the gain in efficiency is enough to outweigh that
inconvenience.
The standard auto_ptr
provides one way to get those
members deallocated automatically.
[ 2000-06-16
-- to next update]
This does not mean that all classes need non-default destructors or
that all destructors should be declared virtual,
just those for classes from which other classes may be derived.
However, Meyers 1996 devotes twelve pages (257-270) to an argument that
every non-leaf class should be abstract. I'm not certain I'm
ready to go that far, but he does make some compelling points.
This is one area where I need to do some more investigation.
class D : public B { ... };then an object of type D should be usable wherever an object of type B is usable (or expected). In other words, D must extend B, not extend B "with some exceptions".
Francis Glassborow said it well in
"Re: Design Mistake?" in comp.std.c++ on 15 Jun 2000:
"If a derived class cannot always substitute for the base class it is
a design fault to treat it as if it could be. This despite many books
to the contrary. OO in C++ is not OO in Smalltalk."
For example, if I have a Building and a Room class, I would not inherit Building from Room but would contain a Room object (or pointer or reference) in the Building class. This example makes Stroustrup 1997's point (page 741) almost absurdly obvious: if D can have more than one B, you want to contain B in D and not derive D from B. Cline 2000 has a less silly example at "[24.2] How are 'private inheritance' and 'composition' similar?"
To implement the "has-a" or "is-implemented-in-terms-of" relationship, authors like Sutter 1999 (page 90) and Cline 2000 prefer containing B in D over inheriting D from B, except when you need:
contact info | site map | |
this page:
http://oakroadsystems.com/codeprac/inher.htm |