next up previous
Next: Containers Up: The GNU C++ Library Previous: ArgumentsResults, and

Inheritance and Overloading

Value approaches traditionally lie in the world of overloading and parameterization, while OO approaches obtain generality via inheritance. The two do not always mix well in C++. The resulting ``edge effects'' lead to some pervasive design trade-offs.

For example, suppose you define a String class, along with a value-oriented operator + method or function that returns a new String representing the concatenation of its arguments. Now define subclass RString that adds an in-place reverse method to String. Without evasive action, this leads to a problem in user code such as:

  RString t, u;   //...
  RString s = t + u;

Depending on other class details, users may obtain either a type error or unexpected behavior, due to the fact that the expression t + u returns a new String, not an RString. There are workarounds to this, but none of them are very satisfying or general. For example, if + were a non-class operation, then overloading another special version for RStrings would work here, but not when s, t, and u were passed to:

void app(String& a, String& b, String& c) { a = b + c; }

Here, the String version would be called instead, leading to undesired behavior. Similar snags result from other strategies.

The net result of these considerations is that value-oriented classes are not readily subclassable in C++. The best two solutions are the most extreme ones: Either give up on value semantics (at least for troublesome operations) or give up on subclassability for classes with extensive reliance on constructive functions.

Most ``lightweight'' classes (e.g., strings, multiple precision numbers, bit sets) take the latter option. In consequence:

These consequences are not all bad. However, in classes like String, these issues along with the additional need to interconvert with other representations ( char, char*, RegExp), lead to an embarassing number of methods and related functions.

If I were redesigning them today, I would surely take the first option, and not provide an extensive value-based support interface. This would place the structural design of such classes closer to that of the kinds of OO applications classes people ordinarily write, enable better separation of interfaces and implementation, simplify some algorithmics, avoid nasty C++ issues such as redundant copy construction and the deletion of temporaries, and allow the use of inheritance to express commmonalities among classes. However, it is also very likely that users would complain about inconveniences stemming from the lack of simple value-based operations such as constructive string concatenation. The code would probably also be a bit slower because of virtuals and lack of inlinability. In classes like String, obtaining efficiency close to that of raw char*'s was an important design decision early on in C++ and C++ libraries. Making Strings simultaneously better and (often enough) faster than raw C character manipulation made the transition from C to C++ far easier for many programmers.

next up previous
Next: Containers Up: The GNU C++ Library Previous: ArgumentsResults, and

Doug Lea@Sun Apr 16 06:37:14 EDT 1995