Re: inheritance, list of objects, polymorphism
* Vladimir Jovic:
James Kanze wrote:
> General rule: assignment and external copy don't work well with
inheritance. (In my own code, I've gradually been introducing a
PolymorphicObject base class, with a virtual destructor and a private
copy constructor and assignment operator. With the rule that classes
designed to be used polymorphically should inherit from
PolymorphicObject.)
I do not understand why you said that "assignment and external copy
don't work well with inheritance."
Mainly it has to do with C++ variables directly being of the size of the
statically known type and directly containing an object of that type, instead of
just being pointers[1] as in Java and C# and like languages.
When sizeof(Derived) > sizeof(Base) this means that
Base o = Derived();
performs a /slice/ of the Derived object; 'o' contains only the Base stuff of
that original object.
Additionally, the copy is now a Base, so any overriding of functionality in
Derived is lost.
Even worse, consider
Derived o;
Base& b = o;
b = Base();
Perhaps Base has a person's name and Derived additional has the person's
birth-year, then the above changes the 'o' name without updating the birth-year,
yielding a Derived instance with inconsistent information.
For a PolymorphicObject base class like James mentioned you therefore generally
want to introduce two restrictions, and support one main functionality:
* Inhibit client code "slice" copying.
This is done by making the assignment operator private and the
copy constructor protected. James wrote "private" copy constructor
but that's a bit impractical. For you want to allow derived classes
to be clonable, and cloning is best expressed in terms of internal
copy construction.
* Make sure that objects can only be created dynamically.
The reasonable way is to make the destructor protected.
* Force use of smart pointer.
James relies on garbage collection so he probably doesn't do this,
but there are two aspects: ensuring that any newly created object's
raw pointer is immediately stored in a smart pointer, and ensuring
that only the smart pointer class has access to destroy an object.
One way to do the first it is to overload the class' allocation function
(operator new) so that any direct 'new' expression would be overly
complicated. For C++98 then provide a macro that supplies the
requisite magic incomprehensible expression and ensures the pointer
is immediately wrapped in a smart pointer, before client code can
get at it. For C++0x I think the improved support for argument
forwarding makes the macro unnecessary. Anyways, one way to do the
second is to make destructor protected (which you'd do anyway for
the bullet point above), and grant friendship to the smart pointer.
Cheers & hth.,
- Alf
Notes:
[1] Even though Java /programmers/ often think that Java doesn't have pointers,
the Java language specification uses that (correct) terminology. C++ programmers
are more conscious of the low level and formal stuff, because they have to be:
C++ is much more complicated... So, I'm not confusing terms here.