More Books
Effective C++ 55 Specific Ways to Improve Your Programs and Designs
Effective C++ Third Edition 55 Specific Ways to Improve Your Programs and Designs
Table of Contents
Copyright
Praise for Effective C++, Third Edition
Addison-Wesley Professional Computing Series
Preface
Acknowledgments
Introduction
Terminology
Chapter 1. Accustoming Yourself to C++
Item 1: View C++ as a federation of languages
Item 2: Prefer consts, enums, and inlines to #defines
Item 3: Use const whenever possible
Item 4: Make sure that objects are initialized before they're used
Chapter 2. Constructors, Destructors, and Assignment Operators
Item 5: Know what functions C++ silently writes and calls
Item 6: Explicitly disallow the use of compiler-generated functions you do not want
Item 7: Declare destructors virtual in polymorphic base classes
Item 8: Prevent exceptions from leaving destructors
Item 9: Never call virtual functions during construction or destruction
Item 10: Have assignment operators return a reference to *this
Item 11: Handle assignment to self in operator=
Item 12: Copy all parts of an object
Chapter 3. Resource Management
Item 13: Use objects to manage resources.
Item 14: Think carefully about copying behavior in resource-managing classes.
Item 15: Provide access to raw resources in resource-managing classes.
Item 16: Use the same form in corresponding uses of new and delete.
Item 17: Store newed objects in smart pointers in standalone statements.
Chapter 4. Designs and Declarations
Item 18: Make interfaces easy to use correctly and hard to use incorrectly
Item 19: Treat class design as type design
Item 20: Prefer pass-by-reference-to-const to pass-by-value
Item 21: Don't try to return a reference when you must return an object
Item 22: Declare data members private
Item 23: Prefer non-member non-friend functions to member functions
Item 24: Declare non-member functions when type conversions should apply to all parameters
Item 25: Consider support for a non-throwing swap
Chapter 5. Implementations
Item 26: Postpone variable definitions as long as possible.
Item 27: Minimize casting.
Item 28: Avoid returning "handles" to object internals.
Item29: Strive for exception-safe code.
Item 30: Understand the ins and outs of inlining.
Item31: Minimize compilation dependencies between files.
Chapter 6. Inheritance and Object-Oriented Design
Item 32: Make sure public inheritance models "is-a."
Item 33: Avoid hiding inherited names
Item 34: Differentiate between inheritance of interface and inheritance of implementation
Item 35: Consider alternatives to virtual functions
Item 36: Never redefine an inherited non-virtual function
Item 37: Never redefine a function's inherited default parameter value
Item 38: Model "has-a" or "is-implemented-in-terms-of" through composition
Item 39: Use private inheritance judiciously
Item 40: Use multiple inheritance judiciously
Chapter 7. Templates and Generic Programming
Item 41: Understand implicit interfaces and compile-time polymorphism
Item 42: Understand the two meanings of typename
Item 43: Know how to access names in templatized base classes
Item 44: Factor parameter-independent code out of templates
Item 45: Use member function templates to accept "all compatible types."
Item 46: Define non-member functions inside templates when type conversions are desired
Item 47: Use traits classes for information about types
Item 48: Be aware of template metaprogramming
Chapter 8. Customizing new and delete
Item 49: Understand the behavior of the new-handler
Item 50: Understand when it makes sense to replace new and delete
Item 51: Adhere to convention when writing new and delete
Item 52: Write placement delete if you write placement new
Chapter 9. Miscellany
Item 53: Pay attention to compiler warnings.
Item 54: Familiarize yourself with the standard library, including TR1
Item.55: Familiarize yourself with Boost.
Appendix A. Beyond Effective C++
Appendix B. Item Mappings Between Second and Third Editions
Index
index_SYMBOL
index_A
index_B
index_C
index_D
index_E
index_F
index_G
index_H
index_I
index_J
index_K
index_L
index_M
index_N
index_O
index_P
index_R
index_S
index_T
index_U
index_V
index_W
index_X
index_Z

Item 35: Consider alternatives to virtual functions

So you're working on a video game, and you're designing a hierarchy for characters in the game. Your game being of the slash-and-burn variety, it's not uncommon for characters to be injured or otherwise in a reduced state of health. You therefore decide to offer a member function, healthValue, that returns an integer indicating how healthy the character is. Because different characters may calculate their health in different ways, declaring healthValue virtual seems the obvious way to design things:


class GameCharacter {

public:

  virtual int healthValue() const;        // return character's health rating;

  ...                                     // derived classes may redefine this

};


The fact that healthValue isn't declared pure virtual suggests that there is a default algorithm for calculating health (see Item 34).

This is, indeed, the obvious way to design things, and in some sense, that's its weakness. Because this design is so obvious, you may not give adequate consideration to its alternatives. In the interest of helping you escape the ruts in the road of object-oriented design, let's consider some other ways to approach this problem.

The Template Method Pattern via the Non-Virtual Interface Idiom

We'll begin with an interesting school of thought that argues that virtual functions should almost always be private. Adherents to this school would suggest that a better design would retain healthValue as a public member function but make it non-virtual and have it call a private virtual function to do the real work, say, doHealthValue:


class GameCharacter {

public:

  int healthValue() const               // derived classes do not redefine

  {                                     // this — see Item 36



    ...                                 // do "before" stuff — see below



    int retVal = doHealthValue();       // do the real work



    ...                                 // do "after" stuff — see below



    return retVal;

  }

  ...



private:

  virtual int doHealthValue() const     // derived classes may redefine this

  {

    ...                                 // default algorithm for calculating

  }                                     // character's health

};


In this code (and for the rest of this Item), I'm showing the bodies of member functions in class definitions. As Item 30 explains, that implicitly declares them inline. I'm showing the code this way only to make it easier to see what is going on. The designs I'm describing are independent of inlining decisions, so don't think it's meaningful that the member functions are defined inside classes. It's not.

This basic design — having clients call private virtual functions indirectly through public non-virtual member functions — is known as the non-virtual interface (NVI) idiom. It's a particular manifestation of the more general design pattern called Template Method (a pattern that, unfortunately, has nothing to do with C++ templates). I call the non-virtual function (e.g., healthValue) the virtual function's wrapper.

An advantage of the NVI idiom is suggested by the "do 'before' stuff" and "do 'after' stuff" comments in the code. Those comments identify code segments guaranteed to be called before and after the virtual function that does the real work. This means that the wrapper ensures that before a virtual function is called, the proper context is set up, and after the call is over, the context is cleaned up. For example, the "before" stuff could include locking a mutex, making a log entry, verifying that class invariants and function preconditions are satisfied, etc. The "after" stuff could include unlocking a mutex, verifying function postconditions, reverifying class invariants, etc. There's not really any good way to do that if you let clients call virtual functions directly.

It may have crossed your mind that the NVI idiom involves derived classes redefining private virtual functions — redefining functions they can't call! There's no design contradiction here. Redefining a virtual function specifies how something is to be done. Calling a virtual function specifies when it will be done. These concerns are independent. The NVI idiom allows derived classes to redefine a virtual function, thus giving them control over how functionality is implemented, but the base class reserves for itself the right to say when the function will be called. It may seem odd at first, but C++'s rule that derived classes may redefine private inherited virtual functions is perfectly sensible.

Under the NVI idiom, it's not strictly necessary that the virtual functions be private. In some class hierarchies, derived class implementations of a virtual function are expected to invoke their base class counterparts (e.g., the example on page 120), and for such calls to be legal, the virtuals must be protected, not private. Sometimes a virtual function even has to be public (e.g., destructors in polymorphic base classes — see Item 7), but then the NVI idiom can't really be applied.

The Strategy Pattern via Function Pointers

The NVI idiom is an interesting alternative to public virtual functions, but from a design point of view, it's little more than window dressing. After all, we're still using virtual functions to calculate each character's health. A more dramatic design assertion would be to say that calculating a character's health is independent of the character's type — that such calculations need not be part of the character at all. For example, we could require that each character's constructor be passed a pointer to a health calculation function, and we could call that function to do the actual calculation:


class GameCharacter;                               // forward declaration



// function for the default health calculation algorithm

int defaultHealthCalc(const GameCharacter& gc);



class GameCharacter {

public:

  typedef int (*HealthCalcFunc)(const GameCharacter&);



  explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc)

  : healthFunc(hcf)

  {}



  int healthValue() const

  { return healthFunc(*this); }



  ...



private:

  HealthCalcFunc healthFunc;

};


This approach is a simple application of another common design pattern, Strategy. Compared to approaches based on virtual functions in the GameCharacter hierarchy, it offers some interesting flexibility:

  • Different instances of the same character type can have different health calculation functions. For example:

    
    class EvilBadGuy: public GameCharacter {
    
    public:
    
      explicit EvilBadGuy(HealthCalcFunc hcf = defaultHealthCalc)
    
      : GameCharacter(hcf)
    
      { ... }
    
    
    
      ...
    
    
    
    };
    
    int loseHealthQuickly(const GameCharacter&);    // health calculation
    
    int loseHealthSlowly(const GameCharacter&);     // funcs with different
    
                                                    // behavior
    
    
    
    EvilBadGuy ebg1(loseHealthQuickly);             // same-type charac-
    
    EvilBadGuy ebg2(loseHealthSlowly);              // ters with different
    
                                                    // health-related
    
                                                    // behavior
    
    

  • Health calculation functions for a particular character may be changed at runtime. For example, GameCharacter might offer a member function, setHealthCalculator, that allowed replacement of the current health calculation function.

On the other hand, the fact that the health calculation function is no longer a member function of the GameCharacter hierarchy means that it has no special access to the internal parts of the object whose health it's calculating. For example, defaultHealthCalc has no access to the non-public parts of EvilBadGuy. If a character's health can be calculated based purely on information available through the character's public interface, this is not a problem, but if accurate health calculation requires non-public information, it is. In fact, it's a potential issue anytime you replace functionality inside a class (e.g., via a member function) with equivalent functionality outside the class (e.g., via a non-member non-friend function or via a non-friend member function of another class). This issue will persist for the remainder of this Item, because all the other design alternatives we're going to consider involve the use of functions outside the GameCharacter hierarchy.

As a general rule, the only way to resolve the need for non-member functions to have access to non-public parts of a class is to weaken the class's encapsulation. For example, the class might declare the non-member functions to be friends, or it might offer public accessor functions for parts of its implementation it would otherwise prefer to keep hidden. Whether the advantages of using a function pointer instead of a virtual function (e.g., the ability to have per-object health calculation functions and the ability to change such functions at runtime) offset the possible need to decrease GameCharacter's encapsulation is something you must decide on a design-by-design basis.

The Strategy Pattern via tr1::function

Once you accustom yourself to templates and their use of implicit interfaces (see Item 41), the function-pointer-based approach looks rather rigid. Why must the health calculator be a function instead of simply something that acts like a function (e.g., a function object)? If it must be a function, why can't it be a member function? And why must it return an int instead of any type convertible to an int?

These constraints evaporate if we replace the use of a function pointer (such as healthFunc) with an object of type TR1::function. As Item 54 explains, such objects may hold any callable entity (i.e., function pointer, function object, or member function pointer) whose signature is compatible with what is expected. Here's the design we just saw, this time using tr1::function:


class GameCharacter;                                 // as before

int defaultHealthCalc(const GameCharacter& gc);      // as before



class GameCharacter {

public:

   // HealthCalcFunc is any callable entity that can be called with

   // anything compatible with a GameCharacter and that returns anything

   // compatible with an int; see below for details

   typedef std::tr1::function<int (const GameCharacter&)> HealthCalcFunc;

   explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc)

   : healthFunc(hcf)

   {}



   int healthValue() const

   { return healthFunc(*this);   }



   ...



private:

  HealthCalcFunc healthFunc;

};


As you can see, HealthCalcFunc is a typedef for a TR1::function instantiation. That means it acts like a generalized function pointer type. Look closely at what HealthCalcFunc is a typedef for:


std::tr1::function<int (const GameCharacter&)>


Here I've highlighted the "target signature" of this tr1::function instantiation. That target signature is "function taking a reference to a const GameCharacter and returning an int." An object of this tr1::function type (i.e., of type HealthCalcFunc) may hold any callable entity compatible with the target signature. To be compatible means that the entity's parameter can be implicitly converted to a const GameCharacter& and its return type can be implicitly converted to an int.

Compared to the last design we saw (where GameCharacter held a pointer to a function), this design is almost the same. The only difference is that GameCharacter now holds a tr1::function object — a generalized pointer to a function. This change is so small, I'd call it inconsequential, except that a consequence is that clients now have staggeringly more flexibility in specifying health calculation functions:


short calcHealth(const GameCharacter&);          // health calculation

                                                 // function; note

                                                 // non-int return type



struct HealthCalculator {                        // class for health

  int operator()(const GameCharacter&) const     // calculation function

  { ... }                                        // objects

};



class GameLevel {

public:

  float health(const GameCharacter&) const;      // health calculation

  ...                                            // mem function; note

};                                               // non-int return type





class EvilBadGuy: public GameCharacter {         // as before

  ...

};

class EyeCandyCharacter:   public GameCharacter {  // another character

  ...                                              // type; assume same

};                                                 // constructor as

                                                   // EvilBadGuy





EvilBadGuy ebg1(calcHealth);                       // character using a

                                                   // health calculation

                                                   // function





EyeCandyCharacter ecc1(HealthCalculator());        // character using a

                                                   // health calculation

                                                   // function object



GameLevel currentLevel;

...

EvilBadGuy ebg2(                                   // character using a

  std::tr1::bind(&GameLevel::health,               // health calculation

          currentLevel,                            // member function;

          _1)                                      // see below for details

);


Personally, I find what tr1::function lets you do so amazing, it makes me tingle all over. If you're not tingling, it may be because you're staring at the definition of ebg2 and wondering what's going on with the call to tr1::bind. Kindly allow me to explain.

We want to say that to calculate ebg2's health rating, the health member function in the GameLevel class should be used. Now, GameLevel::health is a function that is declared to take one parameter (a reference to a GameCharacter), but it really takes two, because it also gets an implicit GameLevel parameter — the one this points to. Health calculation functions for GameCharacters, however, take a single parameter: the GameCharacter whose health is to be calculated. If we're to use GameLevel::health for ebg2's health calculation, we have to somehow "adapt" it so that instead of taking two parameters (a GameCharacter and a GameLevel), it takes only one (a GameCharacter). In this example, we always want to use currentLevel as the GameLevel object for ebg2's health calculation, so we "bind" currentLevel as the GameLevel object to be used each time GameLevel::health is called to calculate ebg2's health. That's what the tr1::bind call does: it specifies that ebg2's health calculation function should always use currentLevel as the GameLevel object.

I'm skipping over a host of details, such as why "_1" means "use currentLevel as the GameLevel object when calling GameLevel::health for ebg2." Such details wouldn't be terribly illuminating, and they'd distract from the fundamental point I want to make: by using tr1::function instead of a function pointer, we're allowing clients to use any compatible callable entity when calculating a character's health. Is that cool or what?

The "Classic" Strategy Pattern

If you're more into design patterns than C++ coolness, a more conventional approach to Strategy would be to make the health-calculation function a virtual member function of a separate health-calculation hierarchy. The resulting hierarchy design would look like this:

If you're not up on your UML notation, this just says that GameCharacter is the root of an inheritance hierarchy where EvilBadGuy and EyeCandyCharacter are derived classes; HealthCalcFunc is the root of an inheritance hierarchy with derived classes SlowHealthLoser and FastHealthLoser; and each object of type GameCharacter contains a pointer to an object from the HealthCalcFunc hierarchy.

Here's the corresponding code skeleton:


class GameCharacter;                            // forward declaration



class HealthCalcFunc {

public:



  ...

  virtual int calc(const GameCharacter& gc) const

  { ... }

  ...



};



HealthCalcFunc defaultHealthCalc;



class GameCharacter {

public:

  explicit GameCharacter(HealthCalcFunc *phcf = &defaultHealthCalc)

  : pHealthCalc(phcf)

  {}



  int healthValue() const

  { return pHealthCalc->calc(*this);}



  ...



private:

  HealthCalcFunc *pHealthCalc;

};


This approach has the appeal of being quickly recognizable to people familiar with the "standard" Strategy pattern implementation, plus it offers the possibility that an existing health calculation algorithm can be tweaked by adding a derived class to the HealthCalcFunc hierarchy.

Summary

The fundamental advice of this Item is to consider alternatives to virtual functions when searching for a design for the problem you're trying to solve. Here's a quick recap the alternatives we examined:

  • Use the non-virtual interface idiom (NVI idiom), a form of the Template Method design pattern that wraps public non-virtual member functions around less accessible virtual functions.

  • Replace virtual functions with function pointer data members, a stripped-down manifestation of the Strategy design pattern.

  • Replace virtual functions with tr1::function data members, thus allowing use of any callable entity with a signature compatible with what you need. This, too, is a form of the Strategy design pattern.

  • Replace virtual functions in one hierarchy with virtual functions in another hierarchy. This is the conventional implementation of the Strategy design pattern.

This isn't an exhaustive list of design alternatives to virtual functions, but it should be enough to convince you that there are alternatives. Furthermore, their comparative advantages and disadvantages should make clear that you should consider them.

To avoid getting stuck in the ruts of the road of object-oriented design, give the wheel a good jerk from time to time. There are lots of other roads. It's worth taking the time to investigate them.

Things to Remember

  • Alternatives to virtual functions include the NVI idiom and various forms of the Strategy design pattern. The NVI idiom is itself an example of the Template Method design pattern.

  • A disadvantage of moving functionality from a member function to a function outside the class is that the non-member function lacks access to the class's non-public members.

  • tr1::function objects act like generalized function pointers. Such objects support all callable entities compatible with a given target signature.