Preserve semantics in a derived class

Even worse than not implementing a base class' public interface is subtly changing its meaning. Any public member function of the base class must not have its semantics changed by the derived class, and must accept the same set of arguments. Again, this is the responsibility of both the base class designer (making preservation of the semantics possible) and the derived class designer (honoring those semantics).

Suppose a class TBase has an operator== function that takes a const TBase& argument; any override of this function by a derived class TDerived must then preserve its semantics. In particular, the TDerived class override must not assume the argument is of type const Derived& and downcast it to that type, because that changes the meaning of the member function that is inherited from TBase's public interface. Downcasts are often a warning sign of such design problems.

In this case, it is better to overload operator== to accept an argument of type const Derived&, and then either reexport the inherited operator== or make the operators global for symmetry.

Be especially careful when you have two or more public base classes; make sure that the semantics of all of them are satisfied, particularly if they export the same or similar protocol. In earlier versions of the Taligent Application Environment, for example, MCollectible had a virtual function, IsEqual, that took a const MCollectible& argument and returned a Boolean. Derived classes of MCollectible overrode this function to implement the comparison used when those derived classes were inserted into collections. When one of those MCollectible-derived classes and one of its own derived classes wanted to define a comparison differently, and the derived class was inserted into a collection of base class objects, that collection didn't behave properly. Overriding was not the proper mechanism for this function.

The current Taligent approach, based on templates, works better: specify a comparator object. Default comparators are available that use overloaded operator==. If TBase and TDerived both need comparison functions, define them as follows:

      Boolean operator==(const TBase &, const TBase &);
      Boolean operator==(const TDerived &, const TDerived &);
NOTE operator== can be defined as either a friend or a member function.

Because of the overloading mechanism, the appropriate comparison function is used for the appropriate object. Another solution for a given collection is to use a pointer to a function or member function to define a comparison, rather than assuming a fixed operator. Or, write a custom comparator.

Watch for this problem: it can cause bugs that are extremely difficult to find. It is especially problematic when inheriting from two or more base classes, each of which defines a function with the same name but with different semantics.

[Contents] [Previous] [Next]
Click the icon to mail questions or corrections about this material to Taligent personnel.
Copyright©1995 Taligent,Inc. All rights reserved.

Generated with WebMaker