[Exceptional C++ Style] Item 18 - Virtuality

aung.oo at sophos.com aung.oo at sophos.com
Tue Jan 4 04:26:05 EST 2005


Item 18: Virtuality

Virtual Question #1: Publicity vs. Privacy?

Guru Question
When should virtual functions be public, protected, or
private?  Justify your answer, and demonstrate
existing practice.

The short answer is rarely if ever, sometimes, and by
default, respectively.

Guideline: Prefer to make interfaces nonvirtual.

C++ standard library already follows this guideline.
The standard library has:
6 public virtual functions, and 142 nonpublic virtual
functions.

The author mentions one WinFX (the object-oriented
successor to the Win32 API and the programming model
for Longhorn) design guideline which recommends using
‘Template Method’ pattern and discusses why this
pattern is such a good idea.

Traditionally, programmers write base classes using
public virtual functions which simultaneously specify
both the interface and the customizable behaviour.
Every public virtual function is forced to serve two
audiences, the outside callers of the class and
derived classes.  A different approach should be
considered as a public virtual function inherently has
two significantly different jobs, and two competing
audiences.

Herb presents Nonvirtual Interface (NVI) pattern which
is a more restricted idiom with a form similar to that
of Gang of Four’s Template Method pattern.

//A modern base class, using Nonvirtual Interface
//(NVI) to separate interface from internals.
class Widget
{
public :
 //Stable, nonvirtual interface.
 //
 intProcess( Gadget& ); // uses DoProcess()
 bool IsDone();         // uses DoIsDone()
 //
private :
 virtual intDoProcessPhase1( Gadget& );
 virtual intDoProcessPhase2( Gadget& );
 virtual bool DoIsDone()
 //
};

The NVI approach has several benefits and no
significant drawbacks.  First, the base class is now
in complete control of its interface and policy and
can enforce interface preconditions and
postconditions, insert instrumentation all in the
nonvirtual interface function.  Second, interface and
implementation are separated.  Third, the base class
is now less fragile in the face of change.  Herb
claims no efficiency is lost for the extra function
call because if the public function is a one-line
passthrough declared inline, all compilers will
optimize it away entirely, leaving no overhead and the
only complexity is the extra time it takes to write
the one-line wrapper function, which is trivial.

Guideline: Prefer to make virtual functions private.

This lets the derived classes override the function to
customize the behaviour as needed, without further
exposing the virtual functions directly by making them
callable by derived classes.

Guideline: Only if derived classes need to invoke the
base implementation of a virtual function, make the
virtual function protected.

Unless a more complete interface/implementation
separation is needed which could be achieved by using
patterns such as GoF’s Bridge, idioms such as Pimpl or
the more general handle/body or envelope/letter, or
other approaches, NVI will often be sufficient for the
needs.

Virtual Question #2: What About Base Class
Destructors?

JG Question
What is the common advice about base class
destructors?

The author wishes this were only a frequently asked
question.  It’s more often a frequently debated
question.  The usual answer to this question is that
base class destructors should always be virtual.  That
answer is wrong and the C++ standard library itself
contains counterexamples refuting it.

The slightly less usual and somewhat more correct
answer is that base class destructors should be
virtual if you’re going to delete polymorphically
(i.e., delete via a pointer to base).

The fully correct answer is this:

Guideline: A base class destructor should be either
public and virtual, or protected and nonvirtual.

If deletion can be performed polymorphically through
the base class interface, then it must behave
virtually and must be virtual.  Indeed, the language
requires it, deleting polymorphically without a
virtual destructor will cause undefined behaviour.
The destructor is the one case where the NVI pattern
cannot be applied to a virtual function because once
execution reaches the body of a base class destructor,
any derived object parts have already been destroyed
and no longer exist.
In the standard library, class templates such as
std::unary_function and std::binary_function are
specifically intended to be instantiated as base
classes and yet do not provide virtual destructors
because they are not intended to be used for
polymorphic deletion.  Herb suggests that an empty but
protected destructor should be given to classes like
std::unary_function.


Aung Oo






--
Aung Khine Oo
Software Engineer, Sophos

DDI: 01235 540209
Web: www.sophos.com
Sophos - protecting businesses against viruses and spam




More information about the Effective-cpp mailing list