Discussion
Team LiB
Previous Section Next Section

Discussion

Most of us have learned through bitter experience to make class members private by default unless we really need to expose them. That's just good encapsulation. This wisdom is applied most frequently to data members (see Item 41), but it applies equally to all members, including virtual functions.

Particularly in OO hierarchies that are expensive to change, prefer full abstraction: Prefer to make public functions nonvirtual, and prefer to make virtual functions private (or protected if derived classes need to be able to call the base versions). This is the Nonvirtual Interface (NVI) pattern. (NVI is similar to other patterns, notably Template Method [Gamma95], but has a distinct motivation and purpose.)

A public virtual function inherently has two different and competing responsibilities, aimed at two different and competing audiences:

  • It specifies interface: Being public, it is directly part of the interface the class presents to the rest of the world.

  • It specifies implementation detail: Being virtual, it provides a hook for derived classes to replace the base implementation of that function (if any); it is a customization point.

Because these two responsibilities have different motives and audiences, they can be (and frequently are) in conflict, and then one function by definition cannot fulfill both responsibilities perfectly. That a public virtual function inherently has two significantly different jobs and two competing audiences is a sign that it's not separating concerns wellincluding that it is inherently violating Items 5 and 11and that we should consider a different approach.

By separating public functions from virtual functions, we achieve the following significant benefits:

  • Each interface can take its natural shape: When we separate the public interface from the customization interface, each can easily take the form it naturally wants to take instead of trying to find a compromise that forces them to look identical. Often, the two interfaces want different numbers of functions and/or different parameters; for example, outside callers may call a single public Process function that performs a logical unit of work, whereas customizers may prefer to override only certain parts of the processing, which is naturally modeled by independently overridable virtual functions (e.g., DoProcessPhase1, DoProcessPhase2) so that derived classes aren't forced to override everything. (This latter example specifically is the Template Method pattern arrived at from a different route.)

  • The base class is in control: The base class is now in complete control of its interface and policy and can enforce interface preconditions and postconditions (see Items 14 and 68), insert instrumentation, and do any similar work all in a single convenient reusable placethe nonvirtual interface function. This "prefactoring" for separation thus promotes good class design.

  • The base class is robust in the face of change: We are free to change our minds later and add pre- and postcondition checking, or separate processing into more steps, or refactor, or implement a fuller interface/implementation separation using the Pimpl idiom (see Item 43), or make other modifications to the base class's customizability, without affecting any of the code that uses or inherits from the class. Note that it is much easier to start with NVI (even if the public function is just a one-line passthrough to the virtual function) and then later add checking or instrumentation, because that can then be done without breaking any code that uses or inherits from the class. It is much harder to start with a public virtual function and later have to break it apart, which necessarily breaks either the code that uses the class or the code that inherits from the class, depending on whether we choose to keep the original function as the virtual function or as the public function, respectively.

See also Item 54.

    Team LiB
    Previous Section Next Section