Discussion
Team LiB
Previous Section Next Section

Discussion

Dynamic polymorphism comes in the form of classes with virtual functions and instances manipulated indirectly (through pointers or references). Static polymorphism involves template classes and template functions.

Polymorphism means that a given value can have more than one type, and a given function can accept arguments of types other than the exact types of its parameters. "Polymorphism is a way of gaining some of the freedom of dynamic type checking without giving up the benefits of static type checking." [Webber03]

The strength of polymorphism is that the same piece of code can operate on different types, even types that were not known at the time the code was written. Such "post-hoc applicability" is the cornerstone of polymorphism because it amplifies the usefulness and reusability of code (see Item 37). (Contrast that with monomorphic code that rigidly operates only on the concrete types it was meant to work with.)

Dynamic polymorphism via public inheritance lets a value have more than one type. For example, a Derived* p can be viewed as a pointer not only to a Derived, but to an object of any type Base that's a direct or indirect base of Derived (the subsumption property). Dynamic polymorphism is also referred to as inclusion polymorphism because the set modeled by Base includes the specializations modeled by Derived.

Due to its characteristics, dynamic polymorphism in C++ is best at:

  • Uniform manipulation based on superset/subset relationships: Different classes that hold a superset/subset (base/derived) relationship can be treated uniformly. A function that works on Employee objects works also on Secretary objects.

  • Static type checking: All types are checked statically in C++.

  • Dynamic binding and separate compilation: Code that uses classes in a hierarchy can be compiled apart from the code of the entire hierarchy. This is possible because of the indirection that pointers provide (both to objects and to functions).

  • Binary interfacing: Modules can be linked either statically or dynamically, as long as the linked modules lay out the virtual tables the same way.

Static polymorphism via templates also lets a value have more than one type. Inside a template<class T> void f( T t ) {/*…*/ }, t can have any type that can be substituted inside f to render compilable code. This is called an "implicit interface," in contrast to a base class's explicit interface. It achieves the same goal of polymorphismwriting code that operates on multiple typesbut in a very different way.

Static polymorphism is best at:

  • Uniform manipulation based on syntactic and semantic interface: Types that obey a syntactic and semantic interface can be treated uniformly. Interfaces are syntactic and implicit (not signature-based and explicit), and so allow any type substitution that fits a given syntax. For example, given the statement int i = p->f( 5 ): If p is a pointer to a Base class type, this calls a specific interface function, such as perhaps a virtual int f(int). But if p is of a generic type, this call can bind to a myriad of things, including that it might invoke an overloaded operator-> that returns a type defining the function X f(double) where X is convertible to int.

  • Static type checking: All types are checked statically.

  • Static binding (prevents separate compilation): All types are bound statically.

  • Efficiency: Compile-time evaluation and static binding allow optimizations and efficiencies not available with dynamic binding.

Decide on your priorities, and use each type of polymorphism for its strengths.

Prefer to blend both kinds of polymorphism to combine their benefits while trying not to combine their drawbacks:

  • Static helps dynamic: Use static polymorphism to implement dynamically polymorphic interfaces. For example, you might have an abstract base class Command and define various implementations as template</*…*/> class ConcreteCommand : public Command. Examples include implementing the Command and Visitor design patterns (see [Alexandrescu01] and [Sutter04]).

  • Dynamic helps static: Offer a generic, comfortable, statically bound interface, but internally dispatch dynamically, so you offer a uniform object layout. Good examples are discriminated union implementations (see [Alexandrescu02b] and [Boost]) and tr1::shared_ptr's Deleter parameter (see [C++TR104]).

  • Any other blend: A bad blend that combines the weaknesses of both is worse than either alone; a good blend that combines the benefits of both is better than either alone. For example, don't put virtual functions into a class template unless you want all virtual functions to be instantiated every time (this is in sharp contrast to nonvirtual functions of templated types). The code size hit can be astronomical, and you may overconstrain your generic type by instantiating functionality that is never needed. The standard facets made this mistake. Don't make it again.

    Team LiB
    Previous Section Next Section