443
Effective C++ by Scott Meyers

Effective C++ and More Effective C++

  • Upload
    others

  • View
    5

  • Download
    3

Embed Size (px)

Citation preview

Page 1: Effective C++ and More Effective C++

Effective C++ by Scott Meyers

Page 2: Effective C++ and More Effective C++

Back to Dedication Continue to Acknowledgments

Preface

This book is a direct outgrowth of my experiences teaching C++ to professional programmers. I've found thatmost students, after a week of intensive instruction, feel comfortable with the basic constructs of the language,but they tend to be less sanguine about their ability to put the constructs together in an effective manner. Thusbegan my attempt to formulate short, specific, easy-to-remember guidelines for effective software developmentin C++: a summary of the things experienced C++ programmers almost always do or almost always avoiddoing.

I was originally interested in rules that could be enforced by some kind of lint-like program. To that end, I ledresearch into the development of tools to examine C++ source code for violations of user-specified conditions.1Unfortunately, the research ended before a complete prototype could be developed. Fortunately, severalcommercial C++-checking products are now available. (You'll find an overview of such products in the articleon static analysis tools by me and Martin Klaus.)

Though my initial interest was in programming rules that could be automatically enforced, I soon realized thelimitations of that approach. The majority of guidelines used by good C++ programmers are too difficult toformalize or have too many important exceptions to be blindly enforced by a program. I was thus led to thenotion of something less precise than a computer program, but still more focused and to-the-point than a generalC++ textbook. The result you now hold in your hands: a book containing 50 specific suggestions on how toimprove your C++ programs and designs.

In this book, you'll find advice on what you should do, and why, and what you should not do, and why not.Fundamentally, of course, the whys are more important than the whats, but it's a lot more convenient to refer to alist of guidelines than to memorize a textbook or two.

Unlike most books on C++, my presentation here is not organized around particular language features. That is, Idon't talk about constructors in one place, about virtual functions in another, about inheritance in a third, etc.Instead, each discussion in the book is tailored to the guideline it accompanies, and my coverage of the variousaspects of a particular language feature may be dispersed throughout the book.

The advantage of this approach is that it better reflects the complexity of the software systems for which C++ isoften chosen, systems in which understanding individual language features is not enough. For example,experienced C++ developers know that understanding inline functions and understanding virtual destructors doesnot necessarily mean you understand inline virtual destructors. Such battle-scarred developers recognize thatcomprehending the interactions between the features in C++ is of the greatest possible importance in using thelanguage effectively. The organization of this book reflects that fundamental truth.

The disadvantage of this design is that you may have to look in more than one place to find everything I have tosay about a particular C++ construct. To minimize the inconvenience of this approach, I have sprinkledcross-references liberally throughout the text, and a comprehensive index is provided at the end of the book.

In preparing this second edition, my ambition to improve the book has been tempered by fear. Tens of thousandsof programmers embraced the first edition of Effective C++, and I didn't want to destroy whatevercharacteristics attracted them to it. However, in the six years since I wrote the book, C++ has changed, the C++library has changed (see Item 49), my understanding of C++ has changed, and accepted usage of C++ haschanged. That's a lot of change, and it was important to me that the technical material in Effective C++ berevised to reflect those changes. I'd done what I could by updating individual pages between printings, but booksand software are frighteningly similar ? there comes a time when localized enhancements fail to suffice, and theonly recourse is a system-wide rewrite. This book is the result of that rewrite: Effective C++, Version 2.0.

Those familiar with the first edition may be interested to know that every Item in the book has been reworked. Ibelieve the overall structure of the book remains sound, however, so little there has changed. Of the 50 originalItems, I retained 48, though I tinkered with the wording of a few Item titles (in addition to revising theaccompanying discussions). The retired Items (i.e., those replaced with completely new material) are numbers32 and 49, though much of the information that used to be in Item 32 somehow found its way into the revamped

Page 3: Effective C++ and More Effective C++

Item 1. I swapped the order of Items 41 and 42, because that made it easier to present the revised material theycontain. Finally, I reversed the direction of my inheritance arrows. They now follow the almost-universalconvention of pointing from derived classes to base classes. This is the same convention I followed in my 1996book, More Effective C++.

The set of guidelines in this book is far from exhaustive, but coming up with good rules ? ones that areapplicable to almost all applications almost all the time ? is harder than it looks. Perhaps you know ofadditional guidelines, of more ways in which to program effectively in C++. If so, I would be delighted to hearabout them.

On the other hand, you may feel that some of the Items in this book are inappropriate as general advice; that thereis a better way to accomplish a task examined in the book; or that one or more of the technical discussions isunclear, incomplete, or misleading. I encourage you to let me know about these things, too.

°Donald Knuth has a long history of offering a small reward to people who notify him of errors in his books. Thequest for a perfect book is laudable in any case, but in view of the number of bug-ridden C++ books that havebeen rushed to market, I feel especially strongly compelled to follow Knuth's example. Therefore, for each errorin this book that is reported to me ? be it technical, grammatical, typographical, or otherwise ? I will, in futureprintings, gladly add to the acknowledgments the name of the first person to bring that error to my attention.

Send your suggested guidelines, your comments, your criticisms, and ? sigh ? your bug reports to: Scott Meyers c/o Publisher, Corporate and Professional Publishing Addison Wesley Longman, Inc. 1 Jacob WayReading, MA 01867 U. S. A.

Alternatively, you may send electronic mail to [email protected].

I maintain a list of changes to this book since its first printing, including bug-fixes, clarifications, and technicalupdates. This list is available at the °Effective C++ World Wide Web site. If you would like a copy of this list,but you lack access to the World Wide Web, please send a request to one of the addresses above, and I will seethat the list is sent to you. °Scott Douglas Meyers Stafford, Oregon

July 1997

Back to Dedication Continue to Acknowledgments

1 You can find an overview of the research at the °Effective C++ World Wide Web site. Return

Page 4: Effective C++ and More Effective C++

Dedication

For Nancy, without whom nothing would be much worth doing. Continue to Preface

Page 5: Effective C++ and More Effective C++

Back to Introduction Continue to Item 1: Prefer const and inline to #define.

Shifting from C to C++

Getting used to C++ takes a little while for everyone, but for grizzled C programmers, the process can beespecially unnerving. Because C is effectively a subset of C++, all the old C tricks continue to work, but manyof them are no longer appropriate. To C++ programmers, for example, a pointer to a pointer looks a little funny.Why, we wonder, wasn't a reference to a pointer used instead?

C is a fairly simple language. All it really offers is macros, pointers, structs, arrays, and functions. No matterwhat the problem is, the solution will always boil down to macros, pointers, structs, arrays, and functions. Notso in C++. The macros, pointers, structs, arrays and functions are still there, of course, but so are private andprotected members, function overloading, default parameters, constructors and destructors, user-definedoperators, inline functions, references, friends, templates, exceptions, namespaces, and more. The design spaceis much richer in C++ than it is in C: there are just a lot more options to consider.

When faced with such a variety of choices, many C programmers hunker down and hold tight to what they'reused to. For the most part, that's no great sin, but some C habits run contrary to the spirit of C++. Those are theones that have simply got to go.

Back to Introduction Continue to Item 1: Prefer const and inline to #define.

Page 6: Effective C++ and More Effective C++

Back to Shifting from C to C++ Continue to Item 2: Prefer <iostream> to <stdio.h>.

Item 1: Prefer const and inline to #define.

This Item might better be called "prefer the compiler to the preprocessor," because #define is often treated as ifit's not part of the language per se. That's one of its problems. When you do something like this,

#define ASPECT_RATIO 1.653

the symbolic name ASPECT_RATIO may never be seen by compilers; it may be removed by the preprocessorbefore the source code ever gets to a compiler. As a result, the name ASPECT_RATIO may not get entered intothe symbol table. This can be confusing if you get an error during compilation involving the use of the constant,because the error message may refer to 1.653, not ASPECT_RATIO. If ASPECT_RATIO was defined in aheader file you didn't write, you'd then have no idea where that 1.653 came from, and you'd probably waste timetracking it down. This problem can also crop up in a symbolic debugger, because, again, the name you'reprogramming with may not be in the symbol table.

The solution to this sorry scenario is simple and succinct. Instead of using a preprocessor macro, define aconstant:

const double ASPECT_RATIO = 1.653;

This approach works like a charm. There are two special cases worth mentioning, however.

First, things can get a bit tricky when defining constant pointers. Because constant definitions are typically putin header files (where many different source files will include them), it's important that the pointer be declaredconst, usually in addition to what the pointer points to. To define a constant char*-based string in a header file,for example, you have to write const twice:

const char * const authorName = "Scott Meyers";

For a discussion of the meanings and uses of const, especially in conjunction with pointers, see Item 21.

Second, it's often convenient to define class-specific constants, and that calls for a slightly different tack. Tolimit the scope of a constant to a class, you must make it a member, and to ensure there's at most one copy of theconstant, you must make it a static member:

class GamePlayer {

private:

static const int NUM_TURNS = 5; // constant declaration

int scores[NUM_TURNS]; // use of constant

...

};

There's a minor wrinkle, however, which is that what you see above is a declaration for NUM_TURNS, not adefinition. You must still define static class members in an implementation file:

const int GamePlayer::NUM_TURNS; // mandatory definition;

// goes in class impl. file

There's no need to lose sleep worrying about this detail. If you forget the definition, your linker should remindyou.

Older compilers may not accept this syntax, because it used to be illegal to provide an initial value for a staticclass member at its point of declaration. Furthermore, in-class initialization is allowed only for integral types(e.g., ints, bools, chars, etc.), and only for constants. In cases where the above syntax can't be used, you put theinitial value at the point of definition:

Page 7: Effective C++ and More Effective C++

class EngineeringConstants { // this goes in the class

private: // header file

static const double FUDGE_FACTOR;

...

};

// this goes in the class implementation file

const double EngineeringConstants::FUDGE_FACTOR = 1.35;

This is all you need almost all the time. The only exception is when you need the value of a class constantduring compilation of the class, such as in the declaration of the array GamePlayer::scores above (wherecompilers insist on knowing the size of the array during compilation). Then the accepted way to compensate forcompilers that (incorrectly) forbid the in-class specification of initial values for integral class constants is to usewhat is affectionately known as "the enum hack." This technique takes advantage of the fact that the values of anenumerated type can be used where ints are expected, so GamePlayer could just as well have been defined likethis:

class GamePlayer {

private:

enum { NUM_TURNS = 5 }; // "the enum hack" ? makes

// NUM_TURNS a symbolic name

// for 5

int scores[NUM_TURNS]; // fine

...

};

Unless you're dealing with compilers of primarily historical interest (i.e., those written before 1995), youshouldn't have to use the enum hack. Still, it's worth knowing what it looks like, because it's not uncommon toencounter it in code dating back to those early, simpler times.

Getting back to the preprocessor, another common (mis)use of the #define directive is using it to implementmacros that look like functions but that don't incur the overhead of a function call. The canonical example iscomputing the maximum of two values:

#define max(a,b) ((a) > (b) ? (a) : (b))

This little number has so many drawbacks, just thinking about them is painful. You're better off playing in thefreeway during rush hour.

Whenever you write a macro like this, you have to remember to parenthesize all the arguments when you writethe macro body; otherwise you can run into trouble when somebody calls the macro with an expression. Buteven if you get that right, look at the weird things that can happen:

int a = 5, b = 0;

max(++a, b); // a is incremented twice

max(++a, b+10); // a is incremented once

Here, what happens to a inside max depends on what it is being compared with!

Page 8: Effective C++ and More Effective C++

Fortunately, you don't need to put up with this nonsense. You can get all the efficiency of a macro plus all thepredictable behavior and type-safety of a regular function by using an inline function (see Item 33):

inline int max(int a, int b) { return a > b ? a : b; }

Now this isn't quite the same as the macro above, because this version of max can only be called with ints, but atemplate fixes that problem quite nicely:

template<class T>

inline const T& max(const T& a, const T& b)

{ return a > b ? a : b; }

This template generates a whole family of functions, each of which takes two objects convertible to the sametype and returns a reference to (a constant version of) the greater of the two objects. Because you don't knowwhat the type T will be, you pass and return by reference for efficiency (see Item 22).

By the way, before you consider writing templates for commonly useful functions like max, check the standardlibrary (see Item 49) to see if they already exist. In the case of max, you'll be pleasantly surprised to find thatyou can rest on others' laurels: max is part of the standard C++ library.

Given the availability of consts and inlines, your need for the preprocessor is reduced, but it's not completelyeliminated. The day is far from near when you can abandon #include, and #ifdef/#ifndef continue to playimportant roles in controlling compilation. It's not yet time to retire the preprocessor, but you should definitelyplan to start giving it longer and more frequent vacations.

Back to Shifting from C to C++ Continue to Item 2: Prefer <iostream> to <stdio.h>.

Page 9: Effective C++ and More Effective C++

Back to Item 1: Prefer const and inline to #define. Continue to Item 3: Prefer new and delete to malloc and free.

Item 2: Prefer <iostream> to <stdio.h>.

Yes, they're portable. Yes, they're efficient. Yes, you already know how to use them. Yes, yes, yes. Butvenerated though they are, the fact of the matter is that scanf and printf and all their ilk could use someimprovement. In particular, they're not type-safe and they're not extensible. Because type safety and extensibilityare cornerstones of the C++ way of life, you might just as well resign yourself to them right now. Besides, theprintf/scanf family of functions separate the variables to be read or written from the formatting information thatcontrols the reads and writes, just like FORTRAN does. It's time to bid the 1950s a fond farewell.

Not surprisingly, these weaknesses of printf/scanf are the strengths of operator>> and operator<<. int i;

Rational r; // r is a rational number

...

cin >> i >> r;

cout << i << r;

If this code is to compile, there must be functions operator>> and operator<< that can work with an object oftype Rational (possibly via implicit type conversion ? see Item M5). If these functions are missing, it's an error.(The versions for ints are standard.) Furthermore, compilers take care of figuring out which versions of theoperators to call for different variables, so you needn't worry about specifying that the first object to be read orwritten is an int and the second is a Rational.

In addition, objects to be read are passed using the same syntactic form as are those to be written, so you don'thave to remember silly rules like you do for scanf, where if you don't already have a pointer, you have to be sureto take an address, but if you've already got a pointer, you have to be sure not to take an address. Let C++compilers take care of those details. They have nothing better to do, and you do have better things to do. Finally,note that built-in types like int are read and written in the same manner as user-defined types like Rational. Try that using scanf and printf!

Here's how you might write an output routine for a class representing rational numbers: class Rational {

public:

Rational(int numerator = 0, int denominator = 1);

...

private:

int n, d; // numerator and denominator

friend ostream& operator<<(ostream& s, const Rational& r);

};

ostream& operator<<(ostream& s, const Rational& r)

{

s << r.n << '/' << r.d;

return s;

}

This version of operator<< demonstrates some subtle (but important) points that are discussed elsewhere in thisbook. For example, operator<< is not a member function (Item 19 explains why), and the Rational object to beoutput is passed into operator<< as a reference-to-const rather than as an object (see Item 22). Thecorresponding input function, operator>>, would be declared and implemented in a similar manner.

Reluctant though I am to admit it, there are some situations in which it may make sense to fall back on the triedand true. First, some implementations of iostream operations are less efficient than the corresponding C stream

Page 10: Effective C++ and More Effective C++

operations, so it's possible (though unlikely ? see Item M16) that you have an application in which this makes asignificant difference. Bear in mind, though, that this says nothing about iostreams in general, only aboutparticular implementations; see Item M23. Second, the iostream library was modified in some ratherfundamental ways during the course of its standardization (see Item 49), so applications that must be maximallyportable may discover that different vendors support different approximations to the standard. Finally, becausethe classes of the iostream library have constructors and the functions in <stdio.h> do not, there are rareoccasions involving the initialization order of static objects (see Item 47) when the standard C library may bemore useful simply because you know that you can always call it with impunity.

The type safety and extensibility offered by the classes and functions in the iostream library are more useful thanyou might initially imagine, so don't throw them away just because you're used to <stdio.h>. After all, even afterthe transition, you'll still have your memories.

Incidentally, that's no typo in the Item title; I really mean <iostream> and not <iostream.h>. Technicallyspeaking, there is no such thing as <iostream.h> ? the °standardization committee eliminated it in favor of<iostream> when they truncated the names of the other non-C standard header names. The reasons for their doingthis are explained in Item 49, but what you really need to understand is that if (as is likely) your compilerssupport both <iostream> and <iostream.h>, the headers are subtly different. In particular, if you #include<iostream>, you get the elements of the iostream library ensconced within the namespace std (see Item 28), but ifyou #include <iostream.h>, you get those same elements at global scope. Getting them at global scope can leadto name conflicts, precisely the kinds of name conflicts the use of namespaces is designed to prevent. Besides,<iostream> is less to type than <iostream.h>. For many people, that's reason enough to prefer it.

Back to Item 1: Prefer const and inline to #define. Continue to Item 3: Prefer new and delete to malloc and free.

Page 11: Effective C++ and More Effective C++

Back to Item 2: Prefer <iostream> to <stdio.h>. Continue to Item 4: Prefer C++-style comments.

Item 3: Prefer new and delete to malloc and free.

The problem with malloc and free (and their variants) is simple: they don't know about constructors anddestructors.

Consider the following two ways to get space for an array of 10 string objects, one using malloc, the other usingnew:

string *stringArray1 =

static_cast<string*>(malloc(10 * sizeof(string)));

string *stringArray2 = new string[10];

Here stringArray1 points to enough memory for 10 string objects, but no objects have been constructed in thatmemory. Furthermore, without jumping through some rather obscure linguistic hoops (such as those described inItems M4 and M8), you have no way to initialize the objects in the array. In other words, stringArray1 is prettyuseless. In contrast, stringArray2 points to an array of 10 fully constructed string objects, each of which cansafely be used in any operation taking a string.

Nonetheless, let's suppose you magically managed to initialize the objects in the stringArray1 array. Later on inyour program, then, you'd expect to do this:

free(stringArray1);

delete [] stringArray2; // see Item 5 for why the

// "[]" is necessary

The call to free will release the memory pointed to by stringArray1, but no destructors will be called on thestring objects in that memory. If the string objects themselves allocated memory, as string objects are wont to do,all the memory they allocated will be lost. On the other hand, when delete is called on stringArray2, a destructoris called for each object in the array before any memory is released.

Because new and delete interact properly with constructors and destructors, they are clearly the superiorchoice.

Mixing new and delete with malloc and free is usually a bad idea. When you try to call free on a pointer you gotfrom new or call delete on a pointer you got from malloc, the results are undefined, and we all know what"undefined" means: it means it works during development, it works during testing, and it blows up in your mostimportant customers' faces.

The incompatibility of new/delete and malloc/free can lead to some interesting complications. For example, thestrdup function commonly found in <string.h> takes a char*-based string and returns a copy of it:

char * strdup(const char *ps); // return a copy of what

// ps points to

At some sites, both C and C++ use the same version of strdup, so the memory allocated inside the functioncomes from malloc. As a result, unwitting C++ programmers calling strdup might overlook the fact that they mustuse free on the pointer returned from strdup. But wait! To forestall such complications, some sites might decideto rewrite strdup for C++ and have this rewritten version call new inside the function, thereby mandating thatcallers later use delete. As you can imagine, this can lead to some pretty nightmarish portability problems ascode is shuttled back and forth between sites with different forms of strdup.

Still, C++ programmers are as interested in code reuse as C programmers, and it's a simple fact that there arelots of C libraries based on malloc and free containing code that is very much worth reusing. When takingadvantage of such a library, it's likely you'll end up with the responsibility for freeing memory malloced by the

Page 12: Effective C++ and More Effective C++

library and/or mallocing memory the library itself will free. That's fine. There's nothing wrong with callingmalloc and free inside a C++ program as long as you make sure the pointers you get from malloc always meettheir maker in free and the pointers you get from new eventually find their way to delete. The problems startwhen you get sloppy and try to mix new with free or malloc with delete. That's just asking for trouble.

Given that malloc and free are ignorant of constructors and destructors and that mixing malloc/free withnew/delete can be more volatile than a fraternity rush party, you're best off sticking to an exclusive diet of newsand deletes whenever you can.

Back to Item 2: Prefer <iostream> to <stdio.h>. Continue to Item 4: Prefer C++-style comments.

Page 13: Effective C++ and More Effective C++

Back to Item 3: Prefer new and delete to malloc and free. Continue to Memory Management

Item 4: Prefer C++-style comments.

The good old C comment syntax works in C++ too, but the newfangled C++ comment-to-end-of-line syntax hassome distinct advantages. For example, consider this situation:

if ( a > b ) {

// int temp = a; // swap a and b

// a = b;

// b = temp;

}

Here you have a code block that has been commented out for some reason or other, but in a stunning display ofsoftware engineering, the programmer who originally wrote the code actually included a comment to indicatewhat was going on. When the C++ comment form was used to comment out the block, the embedded commentwas of no concern, but there could have been a serious problem had everybody chosen to use C-stylecomments:

if ( a > b ) {

/* int temp = a; /* swap a and b */

a = b;

b = temp;

*/

}

Notice how the embedded comment inadvertently puts a premature end to the comment that is supposed tocomment out the code block.

C-style comments still have their place. For example, they're invaluable in header files that are processed byboth C and C++ compilers. Still, if you can use C++-style comments, you are often better off doing so.

It's worth pointing out that retrograde preprocessors that were written only for C don't know how to cope withC++-style comments, so things like the following sometimes don't work as expected:

#define LIGHT_SPEED 3e8 // m/sec (in a vacuum)

Given a preprocessor unfamiliar with C++, the comment at the end of the line becomes part of the macro! Ofcourse, as is discussed in Item 1, you shouldn't be using the preprocessor to define constants anyway.

Back to Item 3: Prefer new and delete to malloc and free. Continue to Memory Management

Page 14: Effective C++ and More Effective C++

Back to Item 4: Prefer C++-style comments. Continue to Item 5: Use the same form in corresponding uses of new and delete.

Memory Management

Memory management concerns in C++ fall into two general camps: getting it right and making it performefficiently. Good programmers understand that these concerns should be addressed in that order, because aprogram that is dazzlingly fast and astoundingly small is of little use if it doesn't behave the way it's supposed to.For most programmers, getting things right means calling memory allocation and deallocation routines correctly.Making things perform efficiently, on the other hand, often means writing custom versions of the allocation anddeallocation routines. Getting things right there is even more important.

On the correctness front, C++ inherits from C one of its biggest headaches, that of potential memory leaks. Evenvirtual memory, wonderful invention though it is, is finite, and not everybody has virtual memory in the firstplace.

In C, a memory leak arises whenever memory allocated through malloc is never returned through free. Thenames of the players in C++ are new and delete, but the story is much the same. However, the situation isimproved somewhat by the presence of destructors, because they provide a convenient repository for calls todelete that all objects must make when they are destroyed. At the same time, there is more to worry about,because new implicitly calls constructors and delete implicitly calls destructors. Furthermore, there is thecomplication that you can define your own versions of operator new and operator delete, both inside and outsideof classes. This gives rise to all kinds of opportunities to make mistakes. The following Items (as well as ItemM8) should help you avoid some of the most common ones.

Back to Item 4: Prefer C++-style comments. Continue to Item 5: Use the same form in corresponding uses of new and delete.

Page 15: Effective C++ and More Effective C++

Back to Memory Management Continue to Item 6: Use delete on pointer members in destructors.

Item 5: Use the same form in corresponding uses of new and delete.

What's wrong with this picture? string *stringArray = new string[100];

...

delete stringArray;

Everything here appears to be in order ? the use of new is matched with a use of delete ? but something is stillquite wrong: your program's behavior is undefined. At the very least, 99 of the 100 string objects pointed to bystringArray are unlikely to be properly destroyed, because their destructors will probably never be called.

When you use new, two things happen. First, memory is allocated (via the function operator new, about whichI'll have more to say in Items 7-10 as well as Item M8). Second, one or more constructors are called for thatmemory. When you use delete, two other things happen: one or more destructors are called for the memory, thenthe memory is deallocated (via the function operator delete ? see Items 8 and M8). The big question for delete isthis: how many objects reside in the memory being deleted? The answer to that determines how manydestructors must be called.

Actually, the question is simpler: does the pointer being deleted point to a single object or to an array ofobjects? The only way for delete to know is for you to tell it. If you don't use brackets in your use of delete,delete assumes a single object is pointed to. Otherwise, it assumes that an array is pointed to:

string *stringPtr1 = new string;

string *stringPtr2 = new string[100];

...

delete stringPtr1; // delete an object

delete [] stringPtr2; // delete an array of

// objects

What would happen if you used the "[]" form on stringPtr1? The result is undefined. What would happen if youdidn't use the "[]" form on stringPtr2? Well, that's undefined too. Furthermore, it's undefined even for built-intypes like ints, even though such types lack destructors. The rule, then, is simple: if you use [] when you callnew, you must use [] when you call delete. If you don't use [] when you call new, don't use [] when you calldelete.

This is a particularly important rule to bear in mind when you are writing a class containing a pointer datamember and also offering multiple constructors, because then you've got to be careful to use the same form ofnew in all the constructors to initialize the pointer member. If you don't, how will you know what form of deleteto use in your destructor? For a further examination of this issue, see Item 11.

This rule is also important for the typedef-inclined, because it means that a typedef's author must documentwhich form of delete should be employed when new is used to conjure up objects of the typedef type. Forexample, consider this typedef:

typedef string AddressLines[4]; // a person's address

// has 4 lines, each of

// which is a string

Page 16: Effective C++ and More Effective C++

Because AddressLines is an array, this use of new,

string *pal = new AddressLines; // note that "new

// AddressLines" returns

// a string*, just like

// "new string[4]" would

must be matched with the array form of delete:

delete pal; // undefined!

delete [] pal; // fine

To avoid such confusion, you're probably best off abstaining from typedefs for array types. That should be easy,however, because the standard C++ library (see Item 49) includes string and vector templates that reduce theneed for built-in arrays to nearly zero. Here, for example, AddressLines could be defined to be a vector ofstrings. That is, AddressLines could be of type vector<string>.

Back to Memory Management Continue to Item 6: Use delete on pointer members in destructors.

Page 17: Effective C++ and More Effective C++

Back to Item 5: Use the same form in corresponding uses of new and delete. Continue to Item 7: Be prepared for out-of-memory conditions.

Item 6: Use delete on pointer members in destructors.

Most of the time, classes performing dynamic memory allocation will use new in the constructor(s) to allocatethe memory and will later use delete in the destructor to free up the memory. This isn't too difficult to get rightwhen you first write the class, provided, of course, that you remember to employ delete on all the members thatcould have been assigned memory in any constructor.

However, the situation becomes more difficult as classes are maintained and enhanced, because theprogrammers making the modifications to the class may not be the ones who wrote the class in the first place.Under those conditions, it's easy to forget that adding a pointer member almost always requires each of thefollowing:

Initialization of the pointer in each of the constructors. If no memory is to be allocated to the pointer in aparticular constructor, the pointer should be initialized to 0 (i.e., the null pointer).

Deletion of the existing memory and assignment of new memory in the assignment operator. (See also Item17.)

Deletion of the pointer in the destructor.

If you forget to initialize a pointer in a constructor, or if you forget to handle it inside the assignment operator,the problem usually becomes apparent fairly quickly, so in practice those issues don't tend to plague you. Failingto delete the pointer in the destructor, however, often exhibits no obvious external symptoms. Instead, itmanifests itself as a subtle memory leak, a slowly growing cancer that will eventually devour your addressspace and drive your program to an early demise. Because this particular problem doesn't usually call attentionto itself, it's important that you keep it in mind whenever you add a pointer member to a class.

Note, by the way, that deleting a null pointer is always safe (it does nothing). Thus, if you write yourconstructors, your assignment operators, and your other member functions such that each pointer member of theclass is always either pointing to valid memory or is null, you can merrily delete away in the destructor withoutregard for whether you ever used new for the pointer in question.

There's no reason to get fascist about this Item. For example, you certainly don't want to use delete on a pointerthat wasn't initialized via new, and, except in the case of smart pointer objects (see Item M28), you almost neverwant to delete a pointer that was passed to you in the first place. In other words, your class destructor usuallyshouldn't be using delete unless your class members were the ones who used new in the first place.

Speaking of smart pointers, one way to avoid the need to delete pointer members is to replace those memberswith smart pointer objects like the standard C++ Library's auto_ptr. To see how this can work, take a look atItems M9 and M10.

Back to Item 5: Use the same form in corresponding uses of new and delete. Continue to Item 7: Be prepared for out-of-memory conditions.

Page 18: Effective C++ and More Effective C++

Back to Item 6: Use delete on pointer members in destructors. Continue to Item 8: Adhere to convention when writing operator new and operator delete.

Item 7: Be prepared for out-of-memory conditions.

When operator new can't allocate the memory you request, it throws an exception. (It used to return 0, and someolder compilers still do that. You can make your compilers do it again if you want to, but I'll defer thatdiscussion until the end of this Item.) Deep in your heart of hearts, you know that handling out-of-memoryexceptions is the only truly moral course of action. At the same time, you are keenly aware of the fact that doingso is a pain in the neck. As a result, chances are that you omit such handling from time to time. Like always,perhaps. Still, you must harbor a lurking sense of guilt. I mean, what if new really does yield an exception?

You may think that one reasonable way to cope with this matter is to fall back on your days in the gutter, i.e., touse the preprocessor. For example, a common C idiom is to define a type-independent macro to allocate memoryand then check to make sure the allocation succeeded. For C++, such a macro might look something like this:

#define NEW(PTR, TYPE) \

try { (PTR) = new TYPE; } \

catch (std::bad_alloc&) { assert(0); }

("Wait! What's this std::bad_alloc business?", you ask. bad_alloc is the type of exception operator new throwswhen it can't satisfy a memory allocation request, and std is the name of the namespace (see Item 28) wherebad_alloc is defined. "Okay," you continue, "what's this assert business?" Well, if you look in the standard Cinclude file <assert.h> (or its namespace-savvy C++ equivalent, <cassert> ? see Item 49), you'll find that assertis a macro. The macro checks to see if the expression it's passed is non-zero, and, if it's not, it issues an errormessage and calls abort. Okay, it does that only when the standard macro NDEBUG isn't defined, i.e., in debugmode. In production mode, i.e., when NDEBUG is defined, assert expands to nothing ? to a void statement. Youthus check assertions only when debugging.)

This NEW macro suffers from the common error of using an assert to test a condition that might occur inproduction code (after all, you can run out of memory at any time), but it also has a drawback specific to C++: itfails to take into account the myriad ways in which new can be used. There are three common syntactic forms forgetting new objects of type T, and you need to deal with the possibility of exceptions for each of these forms:

new T;

new T(constructor arguments);

new T[size];

This oversimplifies the problem, however, because clients can define their own (overloaded) versions ofoperator new, so programs may contain an arbitrary number of different syntactic forms for using new.

How, then, to cope? If you're willing to settle for a very simple error-handling strategy, you can set things up sothat if a request for memory cannot be satisfied, an error-handling function you specify is called. This strategyrelies on the convention that when operator new cannot satisfy a request, it calls a client-specifiableerror-handling function ? often called a new-handler ? before it throws an exception. (In truth, what operatornew really does is slightly more complicated. Details are provided in Item 8.)

To specify the out-of-memory-handling function, clients call set_new_handler, which is specified in the header<new> more or less like this:

typedef void (*new_handler)();

new_handler set_new_handler(new_handler p) throw();

As you can see, new_handler is a typedef for a pointer to a function that takes and returns nothing, andset_new_handler is a function that takes and returns a new_handler.

set_new_handler's parameter is a pointer to the function operator new should call if it can't allocate therequested memory. The return value of set_new_handler is a pointer to the function in effect for that purpose

Page 19: Effective C++ and More Effective C++

before set_new_handler was called.

You use set_new_handler like this: // function to call if operator new can't allocate enough memory

void noMoreMemory()

{

cerr << "Unable to satisfy request for memory\n";

abort();

}

int main()

{

set_new_handler(noMoreMemory);

int *pBigDataArray = new int[100000000];

...

}

If, as seems likely, operator new is unable to allocate space for 100,000,000 integers, noMoreMemory will becalled, and the program will abort after issuing an error message. This is a marginally better way to terminatethe program than a simple core dump. (By the way, consider what happens if memory must be dynamicallyallocated during the course of writing the error message to cerr...)

When operator new cannot satisfy a request for memory, it calls the new-handler function not once, but repeatedly until it can find enough memory. The code giving rise to these repeated calls is shown in Item 8, butthis high-level description is enough to conclude that a well-designed new-handler function must do one of thefollowing:

Make more memory available. This may allow operator new's next attempt to allocate the memory tosucceed. One way to implement this strategy is to allocate a large block of memory at program start-up,then release it the first time the new-handler is invoked. Such a release is often accompanied by some kindof warning to the user that memory is low and that future requests may fail unless more memory issomehow made available.

Install a different new-handler. If the current new-handler can't make any more memory available,perhaps it knows of a different new-handler that is more resourceful. If so, the current new-handler caninstall the other new-handler in its place (by calling set_new_handler). The next time operator new callsthe new-handler function, it will get the one most recently installed. (A variation on this theme is for anew-handler to modify its own behavior, so the next time it's invoked, it does something different. Oneway to achieve this is to have the new-handler modify static or global data that affects the new-handler'sbehavior.)

Deinstall the new-handler, i.e., pass the null pointer to set_new_handler. With no new-handler installed,operator new will throw an exception of type std::bad_alloc when its attempt to allocate memory isunsuccessful.

Throw an exception of type std::bad_alloc or some type derived from std::bad_alloc. Such exceptionswill not be caught by operator new, so they will propagate to the site originating the request for memory.(Throwing an exception of a different type will violate operator new's exception specification. The defaultaction when that happens is to call abort, so if your new-handler is going to throw an exception, youdefinitely want to make sure it's from the std::bad_alloc hierarchy. For more information on exceptionspecifications, see Item M14.)

Not return, typically by calling abort or exit, both of which are found in the standard C library (and thusin the standard C++ library ? see Item 49).

These choices give you considerable flexibility in implementing new-handler functions.

Sometimes you'd like to handle memory allocation failures in different ways, depending on the class of theobject being allocated:

class X {

public:

Page 20: Effective C++ and More Effective C++

static void outOfMemory();

...

};

class Y {

public:

static void outOfMemory();

...

};

X* p1 = new X; // if allocation is unsuccessful,

// call X::outOfMemory

Y* p2 = new Y; // if allocation is unsuccessful,

// call Y::outOfMemory

C++ has no support for class-specific new-handlers, but it doesn't need to. You can implement this behavioryourself. You just have each class provide its own versions of set_new_handler and operator new. The class'sset_new_handler allows clients to specify the new-handler for the class (just like the standard set_new_handlerallows clients to specify the global new-handler). The class's operator new ensures that the class-specificnew-handler is used in place of the global new-handler when memory for class objects is allocated.

Consider a class X for which you want to handle memory allocation failures. You'll have to keep track of thefunction to call when operator new can't allocate enough memory for an object of type X, so you'll declare astatic member of type new_handler to point to the new-handler function for the class. Your class X will looksomething like this:

class X {

public:

static new_handler set_new_handler(new_handler p);

static void * operator new(size_t size);

private:

static new_handler currentHandler;

};

Static class members must be defined outside the class definition. Because you'll want to use the defaultinitialization of static objects to 0, you'll define X::currentHandler without initializing it:

new_handler X::currentHandler; // sets currentHandler

// to 0 (i.e., null) by

// default

The set_new_handler function in class X will save whatever pointer is passed to it. It will return whateverpointer had been saved prior to the call. This is exactly what the standard version of set_new_handler does:

new_handler X::set_new_handler(new_handler p)

{

new_handler oldHandler = currentHandler;

currentHandler = p;

return oldHandler;

}

Finally, X's operator new will do the following: 1. Call the standard set_new_handler with X's error-handling function. This will install X's new-handler as

Page 21: Effective C++ and More Effective C++

the global new- handler. In the code below, notice how you explicitly reference the std scope (where thestandard set_new_handler resides) by using the "::" notation.

2. Call the global operator new to actually allocate the requested memory. If the initial attempt at allocationfails, the global operator new will invoke X's new-handler, because that function was just installed as theglobal new-handler. If the global operator new is ultimately unable to find a way to allocate the requestedmemory, it will throw a std::bad_alloc exception, which X's operator new will catch. X's operator newwill then restore the global new-handler that was originally in place, and it will return by propagating theexception.

3. Assuming the global operator new was able to successfully allocate enough memory for an object of typeX, X's operator new will again call the standard set_new_handler to restore the global error-handlingfunction to what it was originally. It will then return a pointer to the allocated memory.

Here's how you say all that in C++:

void * X::operator new(size_t size)

{

new_handler globalHandler = // install X's

std::set_new_handler(currentHandler); // handler

void *memory;

try { // attempt

memory = ::operator new(size); // allocation

}

catch (std::bad_alloc&) { // restore

std::set_new_handler(globalHandler); // handler;

throw; // propagate

} // exception

std::set_new_handler(globalHandler); // restore

// handler

return memory;

}

If the duplicated calls to std::set_new_handler caught your eye, turn to Item M9 for information on how toeliminate them.

Clients of class X use its new-handling capabilities like this:

void noMoreMemory(); // decl. of function to

// call if memory allocation

// for X objects fails

X::set_new_handler(noMoreMemory);

// set noMoreMemory as X's

// new-handling function

X *px1 = new X; // if memory allocation

// fails, call noMoreMemory

string *ps = new string; // if memory allocation

// fails, call the global

// new-handling function

// (if there is one)

X::set_new_handler(0); // set the X-specific

Page 22: Effective C++ and More Effective C++

// new-handling function

// to nothing (i.e., null)

X *px2 = new X; // if memory allocation

// fails, throw an exception

// immediately. (There is

// no new-handling function

// for class X.)

You may note that the code for implementing this scheme is the same regardless of the class, so a reasonableinclination would be to reuse it in other places. As Item 41 explains, both inheritance and templates can be usedto create reusable code. However, in this case, it's a combination of the two that gives you what you need.

All you have to do is create a "mixin-style" base class, i.e., a base class that's designed to allow derived classesto inherit a single specific capability ? in this case, the ability to set a class-specific new-handler. Then you turnthe base class into a template. The base class part of the design lets derived classes inherit the set_new_handlerand operator new functions they all need, while the template part of the design ensures that each inheriting classgets a different currentHandler data member. The result may sound a little complicated, but you'll find that thecode looks reassuringly familiar. In fact, about the only real difference is that it's now reusable by any class thatwants it:

template<class T>// "mixin-style" base class

class NewHandlerSupport {// for class-specific

public:// set_new_handler support

static new_handler set_new_handler(new_handler p);

static void * operator new(size_t size);

private:

static new_handler currentHandler;

};

template<class T>

new_handler NewHandlerSupport<T>::set_new_handler(new_handler p)

{

new_handler oldHandler = currentHandler;

currentHandler = p;

return oldHandler;

}

template<class T>

void * NewHandlerSupport<T>::operator new(size_t size)

{

new_handler globalHandler =

std::set_new_handler(currentHandler);

void *memory;

try {

memory = ::operator new(size);

}

catch (std::bad_alloc&) {

std::set_new_handler(globalHandler);

throw;

}

std::set_new_handler(globalHandler);

return memory;

}

Page 23: Effective C++ and More Effective C++

// this sets each currentHandler to 0

template<class T>

new_handler NewHandlerSupport<T>::currentHandler;

With this class template, adding set_new_handler support to class X is easy: X just inherits fromnewHandlerSupport<X>:

// note inheritance from mixin base class template. (See

// my article on counting objects for information on why

// private inheritance might be preferable here.)

class X: public NewHandlerSupport<X> {

... // as before, but no declarations for

}; // set_new_handler or operator new

Clients of X remain oblivious to all the behind-the-scenes action; their old code continues to work. This isgood, because one thing you can usually rely on your clients being is oblivious.

Using set_new_handler is a convenient, easy way to cope with the possibility of out-of-memory conditions.Certainly it's a lot more attractive than wrapping every use of new inside a try block. Furthermore, templateslike NewHandlerSupport make it simple to add a class-specific new-handler to any class that wants one.Mixin-style inheritance, however, invariably leads to the topic of multiple inheritance, and before starting downthat slippery slope, you'll definitely want to read Item 43.

Until 1993, C++ required that operator new return 0 when it was unable to satisfy a memory request. The currentbehavior is for operator new to throw a std::bad_alloc exception, but a lot of C++ was written before compilersbegan supporting the revised specification. The °C++ standardization committee didn't want to abandon theestablished test-for-0 code base, so they provided alternative forms of operator new (and operator new[] ? see Item 8) that continue to offer the traditional failure-yields-0 behavior. These forms are called "nothrow" formsbecause, well, they never do a throw, and they employ nothrow objects (defined in the standard header <new>)at the point where new is used:

class Widget { ... };

Widget *pw1 = new Widget; // throws std::bad_alloc if

// allocation fails

if (pw1 == 0) ... // this test must fail

Widget *pw2 =

new (nothrow) Widget; // returns 0 if allocation

// fails

if (pw2 == 0) ... // this test may succeed

Regardless of whether you use "normal" (i.e., exception-throwing) new or "nothrow" new, it's important thatyou be prepared to handle memory allocation failures. The easiest way to do that is to take advantage ofset_new_handler, because it works with both forms.

Back to Item 6: Use delete on pointer members in destructors. Continue to Item 8: Adhere to convention when writing operator new and operator delete.

Page 24: Effective C++ and More Effective C++

Back to Item 7: Be prepared for out-of-memory conditions. Continue to Item 9: Avoid hiding the "normal" form of new.

Item 8: Adhere to convention when writing operator new and operator delete.

When you take it upon yourself to write operator new (Item 10 explains why you might want to), it's importantthat your function(s) offer behavior that is consistent with the default operator new. In practical terms, this meanshaving the right return value, calling an error-handling function when insufficient memory is available (see Item7), and being prepared to cope with requests for no memory. You also need to avoid inadvertently hiding the"normal" form of new, but that's a topic for Item 9.

The return value part is easy. If you can supply the requested memory, you just return a pointer to it. If you can't,you follow the rule described in Item 7 and throw an exception of type std::bad_alloc.

It's not quite that simple, however, because operator new actually tries to allocate memory more than once,calling the error-handling function after each failure, the assumption being that the error-handling function mightbe able to do something to free up some memory. Only when the pointer to the error-handling function is nulldoes operator new throw an exception.

In addition, the °C++ standard requires that operator new return a legitimate pointer even when 0 bytes arerequested. (Believe it or not, requiring this odd-sounding behavior actually simplifies things elsewhere in thelanguage.)

That being the case, pseudocode for a non-member operator new looks like this:

void * operator new(size_t size) // your operator new might

{ // take additional params

if (size == 0) { // handle 0-byte requests

size = 1; // by treating them as

} // 1-byte requests

while (1) {

attempt to allocate size bytes;

if (the allocation was successful)

return (a pointer to the memory);

// allocation was unsuccessful; find out what the

// current error-handling function is (see Item 7)

new_handler globalHandler = set_new_handler(0);

set_new_handler(globalHandler);

if (globalHandler) (*globalHandler)();

else throw std::bad_alloc();

}

}

The trick of treating requests for zero bytes as if they were really requests for one byte looks slimy, but it'ssimple, it's legal, it works, and how often do you expect to be asked for zero bytes, anyway?

You may also look askance at the place in the pseudocode where the error-handling function pointer is set tonull, then promptly reset to what it was originally. Unfortunately, there is no way to get at the error-handlingfunction pointer directly, so you have to call set_new_handler to find out what it is. Crude, yes, but alsoeffective.

Item 7 remarks that operator new contains an infinite loop, and the code above shows that loop explicitly ?while (1) is about as infinite as it gets. The only way out of the loop is for memory to be successfully allocatedor for the new-handling function to do one of the things described in Item 7: make more memory available,

Page 25: Effective C++ and More Effective C++

install a different new-handler, deinstall the new-handler, throw an exception of or derived from std::bad_alloc,or fail to return. It should now be clear why the new-handler must do one of those things. If it doesn't, the loopinside operator new will never terminate.

One of the things many people don't realize about operator new is that it's inherited by subclasses. That can leadto some interesting complications. In the pseudocode for operator new above, notice that the function tries toallocate size bytes (unless size is 0). That makes perfect sense, because that's the argument that was passed tothe function. However, most class-specific versions of operator new (including the one you'll find in Item 10)are designed for a specific class, not for a class or any of its subclasses. That is, given an operator new for aclass X, the behavior of that function is almost always carefully tuned for objects of size sizeof(X) ? nothinglarger and nothing smaller. Because of inheritance, however, it is possible that the operator new in a base classwill be called to allocate memory for an object of a derived class:

class Base {

public:

static void * operator new(size_t size);

...

};

class Derived: public Base // Derived doesn't declare

{ ... }; // operator new

Derived *p = new Derived; // calls Base::operator new!

If Base's class-specific operator new wasn't designed to cope with this ? and chances are slim that it was ? thebest way for it to handle the situation is to slough off calls requesting the "wrong" amount of memory to thestandard operator new, like this:

void * Base::operator new(size_t size)

{

if (size != sizeof(Base)) // if size is "wrong,"

return ::operator new(size); // have standard operator

// new handle the request

... // otherwise handle

// the request here

}

"Hold on!" I hear you cry, "You forgot to check for the pathological-but-nevertheless-possible case where sizeis zero!" Actually, I didn't, and please stop using hyphens when you cry out. The test is still there, it's just beenincorporated into the test of size against sizeof(Base). The °C++ standard works in mysterious ways, and one ofthose ways is to decree that all freestanding classes have nonzero size. By definition, sizeof(Base) can never bezero (even if it has no members), so if size is zero, the request will be forwarded to ::operator new, and it willbecome that function's responsibility to treat the request in a reasonable fashion. (Interestingly, sizeof(Base) maybe zero if Base is not a freestanding class. For details, consult my article on counting objects.)

If you'd like to control memory allocation for arrays on a per-class basis, you need to implement operator new'sarray-specific cousin, operator new[]. (This function is usually called "array new," because it's hard to figureout how to pronounce "operator new[]".) If you decide to write operator new[], remember that all you're doingis allocating raw memory ? you can't do anything to the as-yet-nonexistent objects in the array. In fact, you can'teven figure out how many objects will be in the array, because you don't know how big each object is. After all,a base class's operator new[] might, through inheritance, be called to allocate memory for an array of derivedclass objects, and derived class objects are usually bigger than base class objects. Hence, you can't assumeinside Base::operator new[] that the size of each object going into the array is sizeof(Base), and that means youcan't assume that the number of objects in the array is (bytes requested)/sizeof(Base). For more information onoperator new[], see Item M8.

So much for the conventions you need to follow when writing operator new (and operator new[]). For operator

Page 26: Effective C++ and More Effective C++

delete (and its array counterpart, operator delete[]), things are simpler. About all you need to remember is thatC++ guarantees it's always safe to delete the null pointer, so you need to honor that guarantee. Here'spseudocode for a non-member operator delete:

void operator delete(void *rawMemory)

{

if (rawMemory == 0) return; // do nothing if the null

// pointer is being deleted

deallocate the memory pointed to by rawMemory;

return;

}

The member version of this function is simple, too, except you've got to be sure to check the size of what's beingdeleted. Assuming your class-specific operator new forwards requests of the "wrong" size to ::operator new,you've got to forward "wrongly sized" deletion requests to ::operator delete:

class Base { // same as before, but now

public: // op. delete is declared

static void * operator new(size_t size);

static void operator delete(void *rawMemory, size_t size);

...

};

void Base::operator delete(void *rawMemory, size_t size)

{

if (rawMemory == 0) return; // check for null pointer

if (size != sizeof(Base)) { // if size is "wrong,"

::operator delete(rawMemory); // have standard operator

return; // delete handle the request

}

deallocate the memory pointed to by rawMemory;

return;

}

The conventions, then, for operator new and operator delete (and their array counterparts) are not particularlyonerous, but it is important that you obey them. If your allocation routines support new-handler functions andcorrectly deal with zero-sized requests, you're all but finished, and if your deallocation routines cope with nullpointers, there's little more to do. Add support for inheritance in member versions of the functions, and presto! ?you're done.

Back to Item 7: Be prepared for out-of-memory conditions. Continue to Item 9: Avoid hiding the "normal" form of new.

Page 27: Effective C++ and More Effective C++

Back to Item 8: Adhere to convention when writing operator new and operator delete. Continue to Item 10: Write operator delete if you write operator new.

Item 9: Avoid hiding the "normal" form of new.

A declaration of a name in an inner scope hides the same name in outer scopes, so for a function f at both globaland class scope, the member function will hide the global function:

void f(); // global function

class X {

public:

void f(); // member function

};

X x;

f(); // calls global f

x.f(); // calls X::f

This is unsurprising and normally causes no confusion, because global and member functions are usuallyinvoked using different syntactic forms. However, if you add to this class an operator new taking additionalparameters, the result is likely to be an eye-opener:

class X {

public:

void f();

// operator new allowing specification of a

// new-handling function

static void * operator new(size_t size, new_handler p);

};

void specialErrorHandler(); // definition is elsewhere

X *px1 =

new (specialErrorHandler) X; // calls X::operator new

X *px2 = new X; // error!

By declaring a function called "operator new" inside the class, you inadvertently block access to the "normal"form of new. Why this is so is discussed in Item 50. Here we're more interested in figuring out how to avoid theproblem.

One solution is to write a class-specific operator new that supports the "normal" invocation form. If it does thesame thing as the global version, that can be efficiently and elegantly encapsulated as an inline function:

class X {

public:

void f();

static void * operator new(size_t size, new_handler p);

static void * operator new(size_t size)

{ return ::operator new(size); }

};

X *px1 =

Page 28: Effective C++ and More Effective C++

new (specialErrorHandler) X; // calls X::operator

// new(size_t, new_handler)

X* px2 = new X; // calls X::operator

// new(size_t)

An alternative is to provide a default parameter value (see Item 24) for each additional parameter you add tooperator new:

class X {

public:

void f();

static

void * operator new(size_t size, // note default

new_handler p = 0); // value for p

};

X *px1 = new (specialErrorHandler) X; // fine

X* px2 = new X; // also fine

Either way, if you later decide to customize the behavior of the "normal" form of new, all you need to do isrewrite the function; callers will get the customized behavior automatically when they relink.

Back to Item 8: Adhere to convention when writing operator new and operator delete. Continue to Item 10: Write operator delete if you write operator new.

Page 29: Effective C++ and More Effective C++

Back to Item 9: Avoid hiding the "normal" form of new. Continue to Constructors, Destructors, and Assignment Operators

Item 10: Write operator delete if you write operator new.

Let's step back for a moment and return to fundamentals. Why would anybody want to write their own version ofoperator new or operator delete in the first place?

More often than not, the answer is efficiency. The default versions of operator new and operator delete areperfectly adequate for general-purpose use, but their flexibility inevitably leaves room for improvements in theirperformance in a more circumscribed context. This is especially true for applications that dynamically allocate alarge number of small objects.

As an example, consider a class for representing airplanes, where the Airplane class contains only a pointer tothe actual representation for airplane objects (a technique discussed in Item 34):

class AirplaneRep { ... }; // representation for an

// Airplane object

class Airplane {

public:

...

private:

AirplaneRep *rep; // pointer to representation

};

An Airplane object is not very big; it contains but a single pointer. (As explained in Items 14 and M24, it mayimplicitly contain a second pointer if the Airplane class declares virtual functions.) When you allocate anAirplane object by calling operator new, however, you probably get back more memory than is needed to storethis pointer (or pair of pointers). The reason for this seemingly wayward behavior has to do with the need foroperator new and operator delete to communicate with one another.

Because the default version of operator new is a general-purpose allocator, it must be prepared to allocateblocks of any size. Similarly, the default version of operator delete must be prepared to deallocate blocks ofwhatever size operator new allocated. For operator delete to know how much memory to deallocate, it musthave some way of knowing how much memory operator new allocated in the first place. A common way foroperator new to tell operator delete how much memory it allocated is by prepending to the memory it returnssome additional data that specifies the size of the allocated block. That is, when you say this,

Airplane *pa = new Airplane;

you don't necessarily get back a block of memory that looks like this:

Page 30: Effective C++ and More Effective C++

Instead, you often get back a block of memory that looks more like this:

For small objects like those of class Airplane, this additional bookkeeping data can more than double the amountof memory needed for each dynamically allocated object (especially if the class contains no virtual functions).

If you're developing software for an environment in which memory is precious, you may not be able to affordthis kind of spendthrift allocation. By writing your own operator new for the Airplane class, you can takeadvantage of the fact that all Airplane objects are the same size, so there isn't any need for bookkeepinginformation to be kept with each allocated block.

One way to implement your class-specific operator new is to ask the default operator new for big blocks of rawmemory, each block of sufficient size to hold a large number of Airplane objects. The memory chunks forAirplane objects themselves will be taken from these big blocks. Currently unused chunks will be organized intoa linked list ? the free list ? of chunks that are available for future Airplane use. This may make it sound likeyou'll have to pay for the overhead of a next field in every object (to support the list), but you won't: the spacefor the rep field (which is necessary only for memory chunks in use as Airplane objects) will also serve as theplace to store the next pointer (because that pointer is needed only for chunks of memory not in use as Airplaneobjects). You'll arrange for this job-sharing in the usual fashion: you'll use a union.

To turn this design into reality, you have to modify the definition of Airplane to support custom memorymanagement. You do it as follows:

class Airplane { // modified class ? now supports

Page 31: Effective C++ and More Effective C++

public: // custom memory management

static void * operator new(size_t size);

...

private:

union {

AirplaneRep *rep; // for objects in use

Airplane *next; // for objects on free list

};

// this class-specific constant (see Item 1) specifies how

// many Airplane objects fit into a big memory block;

// it's initialized below

static const int BLOCK_SIZE;

static Airplane *headOfFreeList;

};

Here you've added the declarations for operator new, the union that allows the rep and next fields to occupy thesame memory, a class-specific constant for specifying how big each allocated block should be, and a staticpointer to keep track of the head of the free list. It's important to use a static member for this last task, becausethere's one free list for the entire class, not one free list for each Airplane object.

The next thing to do is to write the new operator new: void * Airplane::operator new(size_t size)

{

// send requests of the "wrong" size to ::operator new();

// for details, see Item 8

if (size != sizeof(Airplane))

return ::operator new(size);

Airplane *p = // p is now a pointer to the

headOfFreeList; // head of the free list

// if p is valid, just move the list head to the

// next element in the free list

if (p)

headOfFreeList = p->next;

else {

// The free list is empty. Allocate a block of memory

// big enough to hold BLOCK_SIZE Airplane objects

Airplane *newBlock =

static_cast<Airplane*>(::operator new(BLOCK_SIZE *

sizeof(Airplane)));

// form a new free list by linking the memory chunks

// together; skip the zeroth element, because you'll

// return that to the caller of operator new

for (int i = 1; i < BLOCK_SIZE-1; ++i)

newBlock[i].next = &newBlock[i+1];

// terminate the linked list with a null pointer

newBlock[BLOCK_SIZE-1].next = 0;

// set p to front of list, headOfFreeList to

// chunk immediately following

p = newBlock;

Page 32: Effective C++ and More Effective C++

headOfFreeList = &newBlock[1];

}

return p;

}

If you've read Item 8, you know that when operator new can't satisfy a request for memory, it's supposed toperform a series of ritualistic steps involving new-handler functions and exceptions. There is no sign of suchsteps above. That's because this operator new gets all the memory it manages from ::operator new. That meansthis operator new can fail only if ::operator new does. But if ::operator new fails, it must engage in thenew-handling ritual (possibly culminating in the throwing of an exception), so there is no need for Airplane'soperator new to do it, too. In other words, the new-handler behavior is there, you just don't see it, because it'shidden inside ::operator new.

Given this operator new, the only thing left to do is provide the obligatory definitions of Airplane's static datamembers:

Airplane *Airplane::headOfFreeList; // these definitions

// go in an implemen-

const int Airplane::BLOCK_SIZE = 512; // tation file, not

// a header file

There's no need to explicitly set headOfFreeList to the null pointer, because static members are initialized to 0by default. The value for BLOCK_SIZE, of course, determines the size of each memory block we get from::operator new.

This version of operator new will work just fine. Not only will it use a lot less memory for Airplane objectsthan the default operator new, it's also likely to be faster, possibly as much as two orders of magnitude faster.That shouldn't be surprising. After all, the general version of operator new has to cope with memory requests ofdifferent sizes, has to worry about internal and external fragmentation, etc., whereas your version of operatornew just manipulates a couple of pointers in a linked list. It's easy to be fast when you don't have to be flexible.

At long last we are in a position to discuss operator delete. Remember operator delete? This Item is aboutoperator delete. As currently written, your Airplane class declares operator new, but it does not declareoperator delete. Now consider what happens when a client writes the following, which is nothing if noteminently reasonable:

Airplane *pa = new Airplane; // calls

// Airplane::operator new

...

delete pa; // calls ::operator delete

If you listen closely when you read this code, you can hear the sound of an airplane crashing and burning, withmuch weeping and wailing by the programmers who knew it. The problem is that operator new (the one definedin Airplane) returns a pointer to memory without any header information, but operator delete (the default,global one) assumes that the memory it's passed does contain header information! Surely this is a recipe fordisaster.

This example illustrates the general rule: operator new and operator delete must be written in concert so thatthey share the same assumptions. If you're going to roll your own memory allocation routine, be sure to roll onefor deallocation, too. (For another reason why you should follow this advice, turn to the sidebar on placementnew and placement delete in my article on counting objects in C++.)

Here's how you solve the problem with the Airplane class:

class Airplane { // same as before, except there's

Page 33: Effective C++ and More Effective C++

public: // now a decl. for operator delete

...

static void operator delete(void *deadObject,

size_t size);

};

// operator delete is passed a memory chunk, which,

// if it's the right size, is just added to the

// front of the list of free chunks

void Airplane::operator delete(void *deadObject,

size_t size)

{

if (deadObject == 0) return; // see Item 8

if (size != sizeof(Airplane)) { // see Item 8

::operator delete(deadObject);

return;

}

Airplane *carcass =

static_cast<Airplane*>(deadObject);

carcass->next = headOfFreeList;

headOfFreeList = carcass;

}

Because you were careful in operator new to ensure that calls of the "wrong" size were forwarded to the globaloperator new (see Item 8), you must demonstrate equal care in ensuring that such "improperly sized" objects arehandled by the global version of operator delete. If you did not, you'd run into precisely the problem you havebeen laboring so arduously to avoid ? a semantic mismatch between new and delete.

Interestingly, the size_t value C++ passes to operator delete may be incorrect if the object being deleted wasderived from a base class lacking a virtual destructor. This is reason enough for making sure your base classeshave virtual destructors, but Item 14 describes a second, arguably better reason. For now, simply note that if youomit virtual destructors in base classes, operator delete functions may not work correctly.

All of which is well and good, but I can tell by the furrow in your brow that what you're really concerned aboutis the memory leak. With all the software development experience you bring to the table, there's no way you'dfail to notice that Airplane's operator new calls ::operator new to get big blocks of memory, but Airplane'soperator delete fails to release those blocks.4 Memory leak! Memory leak! I can almost hear the alarm bellsgoing off in your head.

Listen to me carefully: there is no memory leak.

A memory leak arises when memory is allocated, then all pointers to that memory are lost. Absent garbagecollection or some other extralinguistic mechanism, such memory cannot be reclaimed. But this design has nomemory leak, because it's never the case that all pointers to memory are lost. Each big block of memory is firstbroken down into Airplane-sized chunks, and these chunks are then placed on the free list. When clients callAirplane::operator new, chunks are removed from the free list, and clients receive pointers to them. Whenclients call operator delete, the chunks are put back on the free list. With this design, all memory chunks areeither in use as Airplane objects (in which case it's the clients' responsibility to avoid leaking their memory) orare on the free list (in which case there's a pointer to the memory). There is no memory leak.

Nevertheless, the blocks of memory returned by ::operator new are never released by Airplane::operator delete,and there has to be some name for that. There is. You've created a memory pool. Call it semantic gymnastics ifyou must, but there is an important difference between a memory leak and a memory pool. A memory leak maygrow indefinitely, even if clients are well-behaved, but a memory pool never grows larger than the maximum

Page 34: Effective C++ and More Effective C++

amount of memory requested by its clients.

It would not be difficult to modify Airplane's memory management routines so that the blocks of memoryreturned by ::operator new were automatically released when they were no longer in use, but there are tworeasons why you might not want to do it.

The first concerns your likely motivation for tackling custom memory management. There are many reasons whyyou might do it, but the most common one is that you've determined (see Item M16) that the default operator newand operator delete use too much memory or are too slow (or both). That being the case, every additional byteand every additional statement you devote to tracking and releasing those big memory blocks comes straight offthe bottom line: your software runs slower and uses more memory than it would if you adopted the pool strategy.For libraries and applications in which performance is at a premium and you can expect pool sizes to bereasonably bounded, the pool approach may well be best.

The second reason has to do with pathological behavior. Suppose Airplane's memory management routines aremodified so Airplane's operator delete releases any big block of memory that has no active objects in it. Nowconsider this program:

int main()

{

Airplane *pa = new Airplane; // first allocation: get big

// block, make free list, etc.

delete pa; // block is now empty;

// release it

pa = new Airplane; // uh oh, get block again,

// make free list, etc.

delete pa; // okay, block is empty

// again; release it

... // you get the idea...

return 0;

}

This nasty little program will run slower and use more memory than with even the default operator new andoperator delete, much less the pool-based versions of those functions!

Of course, there are ways to deal with this pathology, but the more you code for uncommon special cases, thecloser you get to reimplementing the default memory management functions, and then what have you gained? Amemory pool is not the answer to all memory management questions, but it's a reasonable answer to many ofthem.

In fact, it's a reasonable answer often enough that you may be bothered by the need to reimplement it for differentclasses. "Surely," you think to yourself, "there should be a way to package the notion of a fixed-sized memoryallocator so it's easily reused." There is, though this Item has droned on long enough that I'll leave the details inthe form of the dreaded exercise for the reader.

Instead, I'll simply show a minimal interface (see Item 18) to a Pool class, where each object of type Pool is anallocator for objects of the size specified in the Pool's constructor:

class Pool {

public:

Pool(size_t n); // Create an allocator for

// objects of size n

Page 35: Effective C++ and More Effective C++

void * alloc(size_t n) ; // Allocate enough memory

// for one object; follow

// operator new conventions

// from Item 8

void free( void *p, size_t n); // Return to the pool the

// memory pointed to by p;

// follow operator delete

// conventions from Item 8

~Pool(); // Deallocate all memory in

// the pool

};

This class allows Pool objects to be created, to perform allocation and deallocation operations, and to bedestroyed. When a Pool object is destroyed, it releases all the memory it allocated. This means there is now away to avoid the memory leak-like behavior that Airplane's functions exhibited. However, this also means that ifa Pool's destructor is called too soon (before all the objects using its memory have been destroyed), someobjects will find their memory yanked out from under them before they're done using it. To say that the resultingbehavior is undefined is being generous.

Given this Pool class, even a Java programmer can add custom memory management capabilities to Airplanewithout breaking a sweat:

class Airplane {

public:

... // usual Airplane functions

static void * operator new(size_t size);

static void operator delete(void *p, size_t size);

private:

AirplaneRep *rep; // pointer to representation

static Pool memPool; // memory pool for Airplanes

};

inline void * Airplane::operator new(size_t size)

{ return memPool.alloc(size); }

inline void Airplane::operator delete(void *p,

size_t size)

{ memPool.free(p, size); }

// create a new pool for Airplane objects; this goes in

// the class implementation file

Pool Airplane::memPool(sizeof(Airplane));

This is a much cleaner design than the one we saw earlier, because the Airplane class is no longer clutteredwith non-airplane details. Gone are the union, the head of the free list, the constant defining how big each rawmemory block should be, etc. That's all hidden inside Pool, which is really where it should be. Let Pool's authorworry about memory management minutiae. Your job is to make the Airplane class work properly.

Now, it's interesting to see how custom memory management routines can improve program performance, andit's worthwhile to see how such routines can be encapsulated inside a class like Pool, but let us not lose sight ofthe main point. That point is that operator new and operator delete need to work together, so if you writeoperator new, be sure to write operator delete, as well.

Page 36: Effective C++ and More Effective C++

4 I write this with certainty, because I failed to address this issue in the first edition of this book, and manyreaders upbraided me for the omission. There's nothing quite like a few thousand proofreaders to demonstrateone's fallibility, sigh. Return

Back to Item 9: Avoid hiding the "normal" form of new. Continue to Constructors, Destructors, and Assignment Operators

Page 37: Effective C++ and More Effective C++

Back to Item 10: Write operator delete if you write operator new. Continue to Item 11: Declare a copy constructor and an assignment operator for classes with dynamically allocated memory.

Constructors, Destructors, and Assignment Operators Almost every class you write will have one or more constructors, a destructor, and an assignment operator.Little wonder. These are your bread-and-butter functions, the ones that control the fundamental operations ofbringing a new object into existence and making sure it's initialized; getting rid of an object and making sure it'sbeen properly cleaned up; and giving an object a new value. Making mistakes in these functions will lead tofar-reaching and distinctly unpleasant repercussions throughout your classes, so it's vital that you get them right.In this section, I offer guidance on putting together the functions that comprise the backbone of well-formedclasses.

Back to Item 10: Write operator delete if you write operator new. Continue to Item 11: Declare a copy constructor and an assignment operator for classes with dynamically allocated memory.

Page 38: Effective C++ and More Effective C++

Back to Constructors, Destructors, and Assignment Operators Continue to Item 12: Prefer initialization to assignment in constructors.

Item 11: Declare a copy constructor and an assignment operator for classes with dynamically allocatedmemory.

Consider a class for representing String objects: // a poorly designed String class

class String {

public:

String(const char *value);

~String();

... // no copy ctor or operator=

private:

char *data;

};

String::String(const char *value)

{

if (value) {

data = new char[strlen(value) + 1];

strcpy(data, value);

}

else {

data = new char[1];

*data = '\0';

}

}

inline String::~String() { delete [] data; }

Note that there is no assignment operator or copy constructor declared in this class. As you'll see, this has someunfortunate consequences.

If you make these object definitions, String a("Hello");

String b("World");

the situation is as shown below:

Page 39: Effective C++ and More Effective C++

Inside object a is a pointer to memory containing the character string "Hello". Separate from that is an object bcontaining a pointer to the character string "World". If you now perform an assignment,

b = a;

there is no client-defined operator= to call, so C++ generates and calls the default assignment operator instead(see Item 45). This default assignment operator performs memberwise assignment from the members of a to themembers of b, which for pointers (a.data and b.data) is just a bitwise copy. The result of this assignment isshown below.

Page 40: Effective C++ and More Effective C++

There are at least two problems with this state of affairs. First, the memory that b used to point to was neverdeleted; it is lost forever. This is a classic example of how a memory leak can arise. Second, both a and b nowcontain pointers to the same character string. When one of them goes out of scope, its destructor will delete thememory still pointed to by the other. For example:

String a("Hello"); // define and construct a

{ // open new scope

String b("World"); // define and construct b

...

b = a; // execute default op=,

// lose b's memory

} // close scope, call b's

// destructor

String c = a; // c.data is undefined!

// a.data is already deleted

The last statement in this example is a call to the copy constructor, which also isn't defined in the class, hencewill be generated by C++ in the same manner as the assignment operator (again, see Item 45) and with the samebehavior: bitwise copy of the underlying pointers. That leads to the same kind of problem, but without the worryof a memory leak, because the object being initialized can't yet point to any allocated memory. In the case of thecode above, for example, there is no memory leak when c.data is initialized with the value of a.data, becausec.data doesn't yet point anywhere. However, after c is initialized with a, both c.data and a.data point to the sameplace, so that place will be deleted twice: once when c is destroyed, once again when a is destroyed.

Page 41: Effective C++ and More Effective C++

The case of the copy constructor differs a little from that of the assignment operator, however, because of theway it can bite you: pass-by-value. Of course, Item 22 demonstrates that you should only rarely pass objects byvalue, but consider this anyway:

void doNothing(String localString) {}

String s = "The Truth Is Out There";

doNothing(s);

Everything looks innocuous enough, but because localString is passed by value, it must be initialized from s viathe (default) copy constructor. Hence, localString has a copy of the pointer that is inside s. When doNothingfinishes executing, localString goes out of scope, and its destructor is called. The end result is by now familiar:s contains a pointer to memory that localString has already deleted.

By the way, the result of using delete on a pointer that has already been deleted is undefined, so even if s isnever used again, there could well be a problem when it goes out of scope.

The solution to these kinds of pointer aliasing problems is to write your own versions of the copy constructorand the assignment operator if you have any pointers in your class. Inside those functions, you can either copy thepointed-to data structures so that every object has its own copy, or you can implement some kind ofreference-counting scheme (see Item M29) to keep track of how many objects are currently pointing to aparticular data structure. The reference-counting approach is more complicated, and it calls for extra workinside the constructors and destructors, too, but in some (though by no means all) applications, it can result insignificant memory savings and substantial increases in speed.

For some classes, it's more trouble than it's worth to implement copy constructors and assignment operators,especially when you have reason to believe that your clients won't make copies or perform assignments. Theexamples above demonstrate that omitting the corresponding member functions reflects poor design, but what doyou do if writing them isn't practical, either? Simple: you follow this Item's advice. You declare the functions(private, as it turns out), but you don't define (i.e., implement) them at all. That prevents clients from callingthem, and it prevents compilers from generating them, too. For details on this nifty trick, see Item 27.

One more thing about the String class I used in this Item. In the constructor body, I was careful to use [] with newboth times I called it, even though in one of the places I wanted only a single object. As described in Item 5, it'sessential to employ the same form in corresponding applications of new and delete, so I was careful to beconsistent in my uses of new. This is something you do not want to forget. Always make sure that you use [] withdelete if and only if you used [] with the corresponding use of new.

Back to Constructors, Destructors, and Assignment Operators Continue to Item 12: Prefer initialization to assignment in constructors.

Page 42: Effective C++ and More Effective C++

Back to Item 11: Declare a copy constructor and an assignment operator for classes with dynamically allocated memory. Continue to Item 13: List members in an initialization list in the order in which they are declared.

Item 12: Prefer initialization to assignment in constructors.

Consider a template for generating classes that allow a name to be associated with a pointer to an object ofsome type T:

template<class T>

class NamedPtr {

public:

NamedPtr(const string& initName, T *initPtr);

...

private:

string name;

T *ptr;

};

(In light of the aliasing that can arise during the assignment and copy construction of objects with pointermembers (see Item 11), you might wish to consider whether NamedPtr should implement these functions. Hint: itshould (see Item 27).)

When you write the NamedPtr constructor, you have to transfer the values of the parameters to the correspondingdata members. There are two ways to do this. The first is to use the member initialization list:

template<class T>

NamedPtr<T>::NamedPtr(const string& initName, T *initPtr )

: name(initName), ptr(initPtr)

{}

The second is to make assignments in the constructor body: template<class T>

NamedPtr<T>::NamedPtr(const string& initName, T *initPtr)

{

name = initName;

ptr = initPtr;

}

There are important differences between these two approaches.

From a purely pragmatic point of view, there are times when the initialization list must be used. In particular,const and reference members may only be initialized, never assigned. So, if you decided that a NamedPtr<T>object could never change its name or its pointer, you might follow the advice of Item 21 and declare themembers const:

template<class T>

class NamedPtr {

public:

NamedPtr(const string& initName, T *initPtr);

...

private:

const string name;

T * const ptr;

};

This class definition requires that you use a member initialization list, because const members may only beinitialized, never assigned. You'd obtain very different behavior if you decided that a NamedPtr<T> object should contain a reference to anexisting name. Even so, you'd still have to initialize the reference on your constructors' member initialization

Page 43: Effective C++ and More Effective C++

lists. Of course, you could also combine the two, yielding NamedPtr<T> objects with read-only access to namesthat might be modified outside the class:

template<class T>

class NamedPtr {

public:

NamedPtr(const string& initName, T *initPtr);

...

private:

const string& name; // must be initialized via

// initializer list

T * const ptr; // must be initialized via

// initializer list

};

The original class template, however, contains no const or reference members. Even so, using a memberinitialization list is still preferable to performing assignments inside the constructor. This time the reason isefficiency. When a member initialization list is used, only a single string member function is called. Whenassignment inside the constructor is used, two are called. To understand why, consider what happens when youdeclare a NamedPtr<T> object.

Construction of objects proceeds in two phases: 1. Initialization of data members. (See also Item 13.) 2. Execution of the body of the constructor that was called.

(For objects with base classes, base class member initialization and constructor body execution occurs prior tothat for derived classes.)

For the NamedPtr classes, this means that a constructor for the string object name will always be called beforeyou ever get inside the body of a NamedPtr constructor. The only question, then, is this: which string constructorwill be called?

That depends on the member initialization list in the NamedPtr classes. If you fail to specify an initializationargument for name, the default string constructor will be called. When you later perform an assignment to nameinside the NamedPtr constructors, you will call operator= on name. That will total two calls to string memberfunctions: one for the default constructor and one more for the assignment.

On the other hand, if you use a member initialization list to specify that name should be initialized withinitName, name will be initialized through the copy constructor at a cost of only a single function call.

Even in the case of the lowly string type, the cost of an unnecessary function call may be significant, and asclasses become larger and more complex, so do their constructors, and so does the cost of constructing objects.If you establish the habit of using a member initialization list whenever you can, not only do you satisfy arequirement for const and reference members, you also minimize the chances of initializing data members in aninefficient manner.

In other words, initialization via a member initialization list is always legal, is never less efficient thanassignment inside the body of the constructor, and is often more efficient. Furthermore, it simplifies maintenanceof the class (see Item M32), because if a data member's type is later modified to something that requires use of amember initialization list, nothing has to change.

There is one time, however, when it may make sense to use assignment instead of initialization for the datamembers in a class. That is when you have a large number of data members of built-in types, and you want themall initialized the same way in each constructor. For example, here's a class that might qualify for this kind oftreatment:

class ManyDataMbrs {

public:

Page 44: Effective C++ and More Effective C++

// default constructor

ManyDataMbrs();

// copy constructor

ManyDataMbrs(const ManyDataMbrs& x);

private:

int a, b, c, d, e, f, g, h;

double i, j, k, l, m;

};

Suppose you want to initialize all the ints to 1 and all the doubles to 0, even if the copy constructor is used.Using member initialization lists, you'd have to write this:

ManyDataMbrs::ManyDataMbrs()

: a(1), b(1), c(1), d(1), e(1), f(1), g(1), h(1), i(0),

j(0), k(0), l(0), m(0)

{ ... }

ManyDataMbrs::ManyDataMbrs(const ManyDataMbrs& x)

: a(1), b(1), c(1), d(1), e(1), f(1), g(1), h(1), i(0),

j(0), k(0), l(0), m(0)

{ ... }

This is more than just unpleasant drudge work. It is error-prone in the short term and difficult to maintain in thelong term.

However, you can take advantage of the fact that there is no operational difference between initialization andassignment for (non-const, non-reference) objects of built-in types, so you can safely replace the memberwiseinitialization lists with a function call to a common initialization routine:

class ManyDataMbrs {

public:

// default constructor

ManyDataMbrs();

// copy constructor

ManyDataMbrs(const ManyDataMbrs& x);

private:

int a, b, c, d, e, f, g, h;

double i, j, k, l, m;

void init(); // used to initialize data

// members

};

void ManyDataMbrs::init()

{

a = b = c = d = e = f = g = h = 1;

i = j = k = l = m = 0;

}

ManyDataMbrs::ManyDataMbrs()

{

init();

...

}

ManyDataMbrs::ManyDataMbrs(const ManyDataMbrs& x)

{

Page 45: Effective C++ and More Effective C++

init();

...

}

Because the initialization routine is an implementation detail of the class, you are, of course, careful to make itprivate, right?

Note that static class members should never be initialized in a class's constructor. Static members are initializedonly once per program run, so it makes no sense to try to "initialize" them each time an object of the class's typeis created. At the very least, doing so would be inefficient: why pay to "initialize" an object multiple times?Besides, initialization of static class members is different enough from initialization of their nonstaticcounterparts that an entire Item ? Item 47 ? is devoted to the topic.

Back to Item 11: Declare a copy constructor and an assignment operator for classes with dynamically allocated memory. Continue to Item 13: List members in an initialization list in the order in which they are declared.

Page 46: Effective C++ and More Effective C++

Back to Item 12: Prefer initialization to assignment in constructors. Continue to Item 14: Make sure base classes have virtual destructors.

Item 13: List members in an initialization list in the order in which they are declared.

Unrepentant Pascal and Ada programmers often yearn for the ability to define arrays with arbitrary bounds, i.e.,from 10 to 20 instead of from 0 to 10. Long-time C programmers will insist that everybody who's anybody willalways start counting from 0, but it's easy enough to placate the begin/end crowd. All you have to do is defineyour own Array class template:

template<class T>

class Array {

public:

Array(int lowBound, int highBound);

...

private:

vector<T> data; // the array data is stored

// in a vector object; see

// Item 49 for info about

// the vector template

size_t size; // # of elements in array

int lBound, hBound; // lower bound, higher bound

};

template<class T>

Array<T>::Array(int lowBound, int highBound)

: size(highBound - lowBound + 1),

lBound(lowBound), hBound(highBound),

data(size)

{}

An industrial-strength constructor would perform sanity checking on its parameters to ensure that highBoundwas at least as great as lowBound, but there is a much nastier error here: even with perfectly good values for thearray's bounds, you have absolutely no idea how many elements data holds.

"How can that be?" I hear you cry. "I carefully initialized size before passing it to the vector constructor!"Unfortunately, you didn't ? you just tried to. The rules of the game are that class members are initialized in theorder of their declaration in the class; the order in which they are listed in a member initialization list makesnot a whit of difference. In the classes generated by your Array template, data will always be initialized first,followed by size, lBound, and hBound. Always.

Perverse though this may seem, there is a reason for it. Consider this scenario: class Wacko {

public:

Wacko(const char *s): s1(s), s2(0) {}

Wacko(const Wacko& rhs): s2(rhs.s1), s1(0) {}

private:

string s1, s2;

};

Wacko w1 = "Hello world!";

Wacko w2 = w1;

If members were initialized in the order of their appearance in an initialization list, the data members of w1 andw2 would be constructed in different orders. Recall that the destructors for the members of an object are always

Page 47: Effective C++ and More Effective C++

called in the inverse order of their constructors. Thus, if the above were allowed, compilers would have to keeptrack of the order in which the members were initialized for each object, just to ensure that the destructorswould be called in the right order. That would be an expensive proposition. To avoid that overhead, the order ofconstruction and destruction is the same for all objects of a given type, and the order of members in aninitialization list is ignored.

Actually, if you really want to get picky about it, only nonstatic data members are initialized according to therule. Static data members act like global and namespace objects, so they are initialized only once; see Item 47for details. Furthermore, base class data members are initialized before derived class data members, so if you'reusing inheritance, you should list base class initializers at the very beginning of your member initialization lists.(If you're using multiple inheritance, your base classes will be initialized in the order in which you inherit fromthem; the order in which they're listed in your member initialization lists will again be ignored. However, ifyou're using multiple inheritance, you've probably got more important things to worry about. If you don't, Item 43would be happy to make suggestions regarding aspects of multiple inheritance that are worrisome.)

The bottom line is this: if you hope to understand what is really going on when your objects are initialized, besure to list the members in an initialization list in the order in which those members are declared in the class.

Back to Item 12: Prefer initialization to assignment in constructors. Continue to Item 14: Make sure base classes have virtual destructors.

Page 48: Effective C++ and More Effective C++

Back to Item 13: List members in an initialization list in the order in which they are declared. Continue to Item 15: Have operator= return a reference to *this.

Item 14: Make sure base classes have virtual destructors.

Sometimes it's convenient for a class to keep track of how many objects of its type exist. The straightforwardway to do this is to create a static class member for counting the objects. The member is initialized to 0, isincremented in the class constructors, and is decremented in the class destructor. (Item M26 shows how topackage this approach so it's easy to add to any class, and my article on counting objects describes additionalrefinements to the technique.)

You might envision a military application, in which a class representing enemy targets might look something likethis:

class EnemyTarget {

public:

EnemyTarget() { ++numTargets; }

EnemyTarget(const EnemyTarget&) { ++numTargets; }

~EnemyTarget() { --numTargets; }

static size_t numberOfTargets()

{ return numTargets; }

virtual bool destroy(); // returns success of

// attempt to destroy

// EnemyTarget object

private:

static size_t numTargets; // object counter

};

// class statics must be defined outside the class;

// initialization is to 0 by default

size_t EnemyTarget::numTargets;

This class is unlikely to win you a government defense contract, but it will suffice for our purposes here, whichare substantially less demanding than are those of the Department of Defense. Or so we may hope.

Let us suppose that a particular kind of enemy target is an enemy tank, which you model, naturally enough (see Item 35, but also see Item M33), as a publicly derived class of EnemyTarget. Because you are interested in thetotal number of enemy tanks as well as the total number of enemy targets, you'll pull the same trick with thederived class that you did with the base class:

class EnemyTank: public EnemyTarget {

public:

EnemyTank() { ++numTanks; }

EnemyTank(const EnemyTank& rhs)

: EnemyTarget(rhs)

{ ++numTanks; }

~EnemyTank() { --numTanks; }

static size_t numberOfTanks()

{ return numTanks; }

virtual bool destroy();

private:

static size_t numTanks; // object counter for tanks

};

Page 49: Effective C++ and More Effective C++

Having now added this code to two different classes, you may be in a better position to appreciate Item M26'sgeneral solution to the problem.

Finally, let's assume that somewhere in your application, you dynamically create an EnemyTank object usingnew, which you later get rid of via delete:

EnemyTarget *targetPtr = new EnemyTank;

...

delete targetPtr;

Everything you've done so far seems completely kosher. Both classes undo in the destructor what they did in theconstructor, and there's certainly nothing wrong with your application, in which you were careful to use deleteafter you were done with the object you conjured up with new. Nevertheless, there is something very troublinghere. Your program's behavior is undefined ? you have no way of knowing what will happen.

The °C++ language standard is unusually clear on this topic. When you try to delete a derived class objectthrough a base class pointer and the base class has a nonvirtual destructor (as EnemyTarget does), the results areundefined. That means compilers may generate code to do whatever they like: reformat your disk, sendsuggestive mail to your boss, fax source code to your competitors, whatever. (What often happens at runtime isthat the derived class's destructor is never called. In this example, that would mean your count of EnemyTankswould not be adjusted when targetPtr was deleted. Your count of enemy tanks would thus be wrong, a ratherdisturbing prospect to combatants dependent on accurate battlefield information.)

To avoid this problem, you have only to make the EnemyTarget destructor virtual. Declaring the destructorvirtual ensures well-defined behavior that does precisely what you want: both EnemyTank's and EnemyTarget'sdestructors will be called before the memory holding the object is deallocated.

Now, the EnemyTarget class contains a virtual function, which is generally the case with base classes. After all,the purpose of virtual functions is to allow customization of behavior in derived classes (see Item 36), so almostall base classes contain virtual functions.

If a class does not contain any virtual functions, that is often an indication that it is not meant to be used as abase class. When a class is not intended to be used as a base class, making the destructor virtual is usually a badidea. Consider this example, based on a discussion in the ARM (see Item 50):

// class for representing 2D points

class Point {

public:

Point(short int xCoord, short int yCoord);

~Point();

private:

short int x, y;

};

If a short int occupies 16 bits, a Point object can fit into a 32-bit register. Furthermore, a Point object can bepassed as a 32-bit quantity to functions written in other languages such as C or FORTRAN. If Point's destructoris made virtual, however, the situation changes.

The implementation of virtual functions requires that objects carry around with them some additionalinformation that can be used at runtime to determine which virtual functions should be invoked on the object. Inmost compilers, this extra information takes the form of a pointer called a vptr ("virtual table pointer"). The vptrpoints to an array of function pointers called a vtbl ("virtual table"); each class with virtual functions has anassociated vtbl. When a virtual function is invoked on an object, the actual function called is determined byfollowing the object's vptr to a vtbl and then looking up the appropriate function pointer in the vtbl.

The details of how virtual functions are implemented are unimportant (though, if you're curious, you can readabout them in Item M24). What is important is that if the Point class contains a virtual function, objects of that

Page 50: Effective C++ and More Effective C++

type will implicitly double in size, from two 16-bit shorts to two 16-bit shorts plus a 32-bit vptr! No longer willPoint objects fit in a 32-bit register. Furthermore, Point objects in C++ no longer look like the same structuredeclared in another language such as C, because their foreign language counterparts will lack the vptr. As aresult, it is no longer possible to pass Points to and from functions written in other languages unless youexplicitly compensate for the vptr, which is itself an implementation detail and hence unportable.

The bottom line is that gratuitously declaring all destructors virtual is just as wrong as never declaring themvirtual. In fact, many people summarize the situation this way: declare a virtual destructor in a class if and onlyif that class contains at least one virtual function.

This is a good rule, one that works most of the time, but unfortunately, it is possible to get bitten by thenonvirtual destructor problem even in the absence of virtual functions. For example, Item 13 considers a classtemplate for implementing arrays with client-defined bounds. Suppose you decide (in spite of the advice in ItemM33) to write a template for derived classes representing named arrays, i.e., classes where every array has aname:

template<class T> // base class template

class Array { // (from Item 13)

public:

Array(int lowBound, int highBound);

~Array();

private:

vector<T> data;

size_t size;

int lBound, hBound;

};

template<class T>

class NamedArray: public Array<T> {

public:

NamedArray(int lowBound, int highBound, const string& name);

...

private:

string arrayName;

};

If anywhere in an application you somehow convert a pointer-to-NamedArray into a pointer-to-Array and youthen use delete on the Array pointer, you are instantly transported to the realm of undefined behavior:

NamedArray<int> *pna =

new NamedArray<int>(10, 20, "Impending Doom");

Array<int> *pa;

...

pa = pna; // NamedArray<int>* -> Array<int>*

...

delete pa; // undefined! (Insert theme to

//°Twilight Zone here); in practice,

// pa->arrayName will often be leaked,

// because the NamedArray part of

// *pa will never be destroyed

This situation can arise more frequently than you might imagine, because it's not uncommon to want to take anexisting class that does something, Array in this case, and derive from it a class that does all the same things,

Page 51: Effective C++ and More Effective C++

plus more. NamedArray doesn't redefine any of the behavior of Array ? it inherits all its functions withoutchange ? it just adds some additional capabilities. Yet the nonvirtual destructor problem persists. (As do others.See Item M33.)

Finally, it's worth mentioning that it can be convenient to declare pure virtual destructors in some classes.Recall that pure virtual functions result in abstract classes ? classes that can't be instantiated (i.e., you can'tcreate objects of that type). Sometimes, however, you have a class that you'd like to be abstract, but you don'thappen to have any functions that are pure virtual. What to do? Well, because an abstract class is intended to beused as a base class, and because a base class should have a virtual destructor, and because a pure virtualfunction yields an abstract class, the solution is simple: declare a pure virtual destructor in the class you want tobe abstract.

Here's an example:

class AWOV { // AWOV = "Abstract w/o

// Virtuals"

public:

virtual ~AWOV() = 0; // declare pure virtual

// destructor

};

This class has a pure virtual function, so it's abstract, and it has a virtual destructor, so you can rest assured thatyou won't have to worry about the destructor problem. There is one twist, however: you must provide a definition for the pure virtual destructor:

AWOV::~AWOV() {} // definition of pure

// virtual destructor

You need this definition, because the way virtual destructors work is that the most derived class's destructor iscalled first, then the destructor of each base class is called. That means that compilers will generate a call to~AWOV even though the class is abstract, so you have to be sure to provide a body for the function. If you don't,the linker will complain about a missing symbol, and you'll have to go back and add one.

You can do anything you like in that function, but, as in the example above, it's not uncommon to have nothing todo. If that is the case, you'll probably be tempted to avoid paying the overhead cost of a call to an empty functionby declaring your destructor inline. That's a perfectly sensible strategy, but there's a twist you should knowabout.

Because your destructor is virtual, its address must be entered into the class's vtbl (see Item M24). But inlinefunctions aren't supposed to exist as freestanding functions (that's what inline means, right?), so special measuresmust be taken to get addresses for them. Item 33 tells the full story, but the bottom line is this: if you declare avirtual destructor inline, you're likely to avoid function call overhead when it's invoked, but your compiler willstill have to generate an out-of-line copy of the function somewhere, too.

Back to Item 13: List members in an initialization list in the order in which they are declared. Continue to Item 15: Have operator= return a reference to *this.

Page 52: Effective C++ and More Effective C++

Back to Item 14: Make sure base classes have virtual destructors. Continue to Item 16: Assign to all data members in operator=.

Item 15: Have operator= return a reference to *this.

°Bjarne Stroustrup, the designer of C++, went to a lot of trouble to ensure that user-defined types would mimicthe built-in types as closely as possible. That's why you can overload operators, write type conversion functions(see Item M5), take control of assignment and copy construction, etc. After so much effort on his part, the leastyou can do is keep the ball rolling.

Which brings us to assignment. With the built-in types, you can chain assignments together, like so: int w, x, y, z;

w = x = y = z = 0;

As a result, you should be able to chain together assignments for user-defined types, too:

string w, x, y, z; // string is "user-defined"

// by the standard C++

// library (see Item 49)

w = x = y = z = "Hello";

As fate would have it, the assignment operator is right-associative, so the assignment chain is parsed like this: w = (x = (y = (z = "Hello")));

It's worthwhile to write this in its completely equivalent functional form. Unless you're a closet LISPprogrammer, this example should make you grateful for the ability to define infix operators:

w.operator=(x.operator=(y.operator=(z.operator=("Hello"))));

This form is illustrative because it emphasizes that the argument to w.operator=, x.operator=, and y.operator= isthe return value of a previous call to operator=. As a result, the return type of operator= must be acceptable asan input to the function itself. For the default version of operator= in a class C, the signature of the function is asfollows (see Item 45):

C& C::operator=(const C&);

You'll almost always want to follow this convention of having operator= both take and return a reference to aclass object, although at times you may overload operator= so that it takes different argument types. Forexample, the standard string type provides two different versions of the assignment operator:

string& // assign a string

operator=(const string& rhs); // to a string

string& // assign a char*

operator=(const char *rhs); // to a string

Notice, however, that even in the presence of overloading, the return type is a reference to an object of theclass.

A common error amongst new C++ programmers is to have operator= return void, a decision that seemsreasonable until you realize it prevents chains of assignment. So don't do it.

Another common error is to have operator= return a reference to a const object, like this: class Widget {

Page 53: Effective C++ and More Effective C++

public:

... // note

const Widget& operator=(const Widget& rhs); // const

... // return

}; // type

The usual motivation is to prevent clients from doing silly things like this: Widget w1, w2, w3;

...

(w1 = w2) = w3; // assign w2 to w1, then w3 to

// the result! (Giving Widget's

// operator= a const return value

// prevents this from compiling.)

Silly this may be, but not so silly that it's prohibited for the built-in types: int i1, i2, i3;

...

(i1 = i2) = i3; // legal! assigns i2 to

// i1, then i3 to i1!

I know of no practical use for this kind of thing, but if it's good enough for the ints, it's good enough for me andmy classes. It should be good enough for you and yours, too. Why introduce gratuitous incompatibilities with theconventions followed by the built-in types?

Within an assignment operator bearing the default signature, there are two obvious candidates for the object tobe returned: the object on the left hand side of the assignment (the one pointed to by this) and the object on theright-hand side (the one named in the parameter list). Which is correct?

Here are the possibilities for a String class (a class for which you'd definitely want to write an assignmentoperator, as explained in Item 11):

String& String::operator=(const String& rhs)

{

...

return *this; // return reference

// to left-hand object

}

String& String::operator=(const String& rhs)

{

...

return rhs; // return reference to

// right-hand object

}

This might strike you as a case of six of one versus a half a dozen of the other, but there are importantdifferences.

Page 54: Effective C++ and More Effective C++

First, the version returning rhs won't compile. That's because rhs is a reference-to-const-String, but operator=returns a reference-to-String. Compilers will give you no end of grief for trying to return areference-to-non-const when the object itself is const. That seems easy enough to get around, however ? justredeclare operator= like this:

String& String::operator=(String& rhs) { ... }

Alas, now the client code won't compile! Look again at the last part of the original chain of assignments: x = "Hello"; // same as x.op=("Hello");

Because the right-hand argument of the assignment is not of the correct type ? it's a char array, not a String ?compilers would have to create a temporary String object (via the String constructor ? see Item M19) to makethe call succeed. That is, they'd have to generate code roughly equivalent to this:

const String temp("Hello"); // create temporary

x = temp; // pass temporary to op=

Compilers are willing to create such a temporary (unless the needed constructor is explicit ? see Item 19), butnote that the temporary object is const. This is important, because it prevents you from accidentally passing atemporary into a function that modifies its parameter. If that were allowed, programmers would be surprised tofind that only the compiler-generated temporary was modified, not the argument they actually provided at thecall site. (We know this for a fact, because early versions of C++ allowed these kinds of temporaries to begenerated, passed, and modified, and the result was a lot of surprised programmers.)

Now we can see why the client code above won't compile if String's operator= is declared to take areference-to-non-const String: it's never legal to pass a const object to a function that fails to declare thecorresponding parameter const. That's just simple const-correctness.

You thus find yourself in the happy circumstance of having no choice whatsoever: you'll always want to defineyour assignment operators in such a way that they return a reference to their left-hand argument, *this. If you doanything else, you prevent chains of assignments, you prevent implicit type conversions at call sites, or both.

Back to Item 14: Make sure base classes have virtual destructors. Continue to Item 16: Assign to all data members in operator=.

Page 55: Effective C++ and More Effective C++

Back to Item 15: Have operator= return a reference to *this. Continue to Item 17: Check for assignment to self in operator=.

Item 16: Assign to all data members in operator=.

Item 45 explains that C++ will write an assignment operator for you if you don't declare one yourself, and Item11 describes why you often won't much care for the one it writes for you, so perhaps you're wondering if youcan somehow have the best of both worlds, whereby you let C++ generate a default assignment operator and youselectively override those parts you don't like. No such luck. If you want to take control of any part of theassignment process, you must do the entire thing yourself.

In practice, this means that you need to assign to every data member of your object when you write yourassignment operator(s):

template<class T> // template for classes associating

class NamedPtr { // names with pointers (from Item 12)

public:

NamedPtr(const string& initName, T *initPtr);

NamedPtr& operator=(const NamedPtr& rhs);

private:

string name;

T *ptr;

};

template<class T>

NamedPtr<T>& NamedPtr<T>::operator=(const NamedPtr<T>& rhs)

{

if (this == &rhs)

return *this; // see Item 17

// assign to all data members

name = rhs.name; // assign to name

*ptr = *rhs.ptr; // for ptr, assign what's

// pointed to, not the

// pointer itself

return *this; // see Item 15

}

This is easy enough to remember when the class is originally written, but it's equally important that theassignment operator(s) be updated if new data members are added to the class. For example, if you decide toupgrade the NamedPtr template to carry a timestamp marking when the name was last changed, you'll have toadd a new data member, and this will require updating the constructor(s) as well as the assignment operator(s).In the hustle and bustle of upgrading a class and adding new member functions, etc., it's easy to let this kind ofthing slip your mind.

The real fun begins when inheritance joins the party, because a derived class's assignment operator(s) must alsohandle assignment of its base class members! Consider this:

class Base {

public:

Base(int initialValue = 0): x(initialValue) {}

private:

int x;

};

class Derived: public Base {

public:

Page 56: Effective C++ and More Effective C++

Derived(int initialValue)

: Base(initialValue), y(initialValue) {}

Derived& operator=(const Derived& rhs);

private:

int y;

};

The logical way to write Derived's assignment operator is like this: // erroneous assignment operator

Derived& Derived::operator=(const Derived& rhs)

{

if (this == &rhs) return *this; // see Item 17

y = rhs.y; // assign to Derived's

// lone data member

return *this; // see Item 15

}

Unfortunately, this is incorrect, because the data member x in the Base part of a Derived object is unaffected bythis assignment operator. For example, consider this code fragment:

void assignmentTester()

{

Derived d1(0); // d1.x = 0, d1.y = 0

Derived d2(1); // d2.x = 1, d2.y = 1

d1 = d2; // d1.x = 0, d1.y = 1!

}

Notice how the Base part of d1 is unchanged by the assignment.

The straightforward way to fix this problem would be to make an assignment to x in Derived::operator=.Unfortunately, that's not legal, because x is a private member of Base. Instead, you have to make an explicitassignment to the Base part of Derived from inside Derived's assignment operator.

This is how you do it: // correct assignment operator

Derived& Derived::operator=(const Derived& rhs)

{

if (this == &rhs) return *this;

Base::operator=(rhs); // call this->Base::operator=

y = rhs.y;

return *this;

}

Here you just make an explicit call to Base::operator=. That call, like all calls to member functions from withinother member functions, will use *this as its implicit left-hand object. The result will be that Base::operator=will do whatever work it does on the Base part of *this ? precisely the effect you want.

Alas, some compilers (incorrectly) reject this kind of call to a base class's assignment operator if thatassignment operator was generated by the compiler (see Item 45). To pacify these renegade translators, you needto implement Derived::operator= this way:

Derived& Derived::operator=(const Derived& rhs)

Page 57: Effective C++ and More Effective C++

{

if (this == &rhs) return *this;

static_cast<Base&>(*this) = rhs; // call operator= on

// Base part of *this

y = rhs.y;

return *this;

}

This monstrosity casts *this to be a reference to a Base, then makes an assignment to the result of the cast. Thatmakes an assignment to only the Base part of the Derived object. Careful now! It is important that the cast be toa reference to a Base object, not to a Base object itself. If you cast *this to be a Base object, you'll end upcalling the copy constructor for Base, and the new object you construct (see Item M19) will be the target of theassignment; *this will remain unchanged. Hardly what you want.

Regardless of which of these approaches you employ, once you've assigned the Base part of the Derived object,you then continue with Derived's assignment operator, making assignments to all the data members of Derived.

A similar inheritance-related problem often arises when implementing derived class copy constructors. Take alook at the following, which is the copy constructor analogue of the code we just examined:

class Base {

public:

Base(int initialValue = 0): x(initialValue) {}

Base(const Base& rhs): x(rhs.x) {}

private:

int x;

};

class Derived: public Base {

public:

Derived(int initialValue)

: Base(initialValue), y(initialValue) {}

Derived(const Derived& rhs) // erroneous copy

: y(rhs.y) {} // constructor

private:

int y;

};

Class Derived demonstrates one of the nastiest bugs in all C++-dom: it fails to copy the base class part when aDerived object is copy constructed. Of course, the Base part of such a Derived object is constructed, but it'sconstructed using Base's default constructor. Its member x is initialized to 0 (the default constructor's defaultparameter value), regardless of the value of x in the object being copied!

To avoid this problem, Derived's copy constructor must make sure that Base's copy constructor is invokedinstead of Base's default constructor. That's easily done. Just be sure to specify an initializer value for Base inthe member initialization list of Derived's copy constructor:

class Derived: public Base {

public:

Derived(const Derived& rhs): Base(rhs), y(rhs.y) {}

...

};

Page 58: Effective C++ and More Effective C++

Now when a client creates a Derived by copying an existing object of that type, its Base part will be copied,too.

Back to Item 15: Have operator= return a reference to *this. Continue to Item 17: Check for assignment to self in operator=.

Page 59: Effective C++ and More Effective C++

Back to Item 16: Assign to all data members in operator=. Continue to Classes and Functions: Design and Declaration

Item 17: Check for assignment to self in operator=.

An assignment to self occurs when you do something like this: class X { ... };

X a;

a = a; // a is assigned to itself

This looks like a silly thing to do, but it's perfectly legal, so don't doubt for a moment that programmers do it.More importantly, assignment to self can appear in this more benign-looking form:

a = b;

If b is another name for a (for example, a reference that has been initialized to a), then this is also an assignmentto self, though it doesn't outwardly look like it. This is an example of aliasing: having two or more names for thesame underlying object. As you'll see at the end of this Item, aliasing can crop up in any number of nefariousdisguises, so you need to take it into account any time you write a function.

Two good reasons exist for taking special care to cope with possible aliasing in assignment operator(s). Thelesser of them is efficiency. If you can detect an assignment to self at the top of your assignment operator(s), youcan return right away, possibly saving a lot of work that you'd otherwise have to go through to implementassignment. For example, Item 16 points out that a proper assignment operator in a derived class must call anassignment operator for each of its base classes, and those classes might themselves be derived classes, soskipping the body of an assignment operator in a derived class might save a large number of other functioncalls.

A more important reason for checking for assignment to self is to ensure correctness. Remember that anassignment operator must typically free the resources allocated to an object (i.e., get rid of its old value) beforeit can allocate the new resources corresponding to its new value. When assigning to self, this freeing ofresources can be disastrous, because the old resources might be needed during the process of allocating the newones.

Consider assignment of String objects, where the assignment operator fails to check for assignment to self: class String {

public:

String(const char *value); // see Item 11 for

// function definition

~String(); // see Item 11 for

// function definition

...

String& operator=(const String& rhs);

private:

char *data;

};

// an assignment operator that omits a check

// for assignment to self

String& String::operator=(const String& rhs)

{

delete [] data; // delete old memory

// allocate new memory and copy rhs's value into it

data = new char[strlen(rhs.data) + 1];

Page 60: Effective C++ and More Effective C++

strcpy(data, rhs.data);

return *this; // see Item 15

}

Consider now what happens in this case: String a = "Hello";

a = a; // same as a.operator=(a)

Inside the assignment operator, *this and rhs seem to be different objects, but in this case they happen to bedifferent names for the same object. You can envision it like this:

The first thing the assignment operator does is use delete on data, and the result is the following state of affairs:

Page 61: Effective C++ and More Effective C++

Now when the assignment operator tries to do a strlen on rhs.data, the results are undefined. This is becauserhs.data was deleted when data was deleted, which happened because data, this->data, and rhs.data are all thesame pointer! From this point on, things can only get worse.

By now you know that the solution to the dilemma is to check for an assignment to self and to return immediatelyif such an assignment is detected. Unfortunately, it's easier to talk about such a check than it is to write it,because you are immediately forced to figure out what it means for two objects to be "the same."

The topic you confront is technically known as that of object identity, and it's a well-known topic inobject-oriented circles. This book is no place for a discourse on object identity, but it is worthwhile to mentionthe two basic approaches to the problem.

One approach is to say that two objects are the same (have the same identity) if they have the same value. Forexample, two String objects would be the same if they represented the same sequence of characters:

String a = "Hello";

String b = "World";

String c = "Hello";

Here a and c have the same value, so they are considered identical; b is different from both of them. If youwanted to use this definition of identity in your String class, your assignment operator might look like this:

String& String::operator=(const String& rhs)

{

if (strcmp(data, rhs.data) == 0) return *this;

...

}

Value equality is usually determined by operator==, so the general form for an assignment operator for a classC that uses value equality for object identity is this:

C& C::operator=(const C& rhs)

Page 62: Effective C++ and More Effective C++

{

// check for assignment to self

if (*this == rhs) // assumes op== exists

return *this;

...

}

Note that this function is comparing objects (via operator==), not pointers. Using value equality to determineidentity, it doesn't matter whether two objects occupy the same memory; all that matters is the values theyrepresent.

The other possibility is to equate an object's identity with its address in memory. Using this definition of objectequality, two objects are the same if and only if they have the same address. This definition is more common inC++ programs, probably because it's easy to implement and the computation is fast, neither of which is alwaystrue when object identity is based on values. Using address equality, a general assignment operator looks likethis:

C& C::operator=(const C& rhs)

{

// check for assignment to self

if (this == &rhs) return *this;

...

}

This suffices for a great many programs.

If you need a more sophisticated mechanism for determining whether two objects are the same, you'll have toimplement it yourself. The most common approach is based on a member function that returns some kind ofobject identifier:

class C {

public:

ObjectID identity() const; // see also Item 36

...

};

Given object pointers a and b, then, the objects they point to are identical if and only if a->identity() ==b->identity(). Of course, you are responsible for writing operator== for ObjectIDs.

The problems of aliasing and object identity are hardly confined to operator=. That's just a function in which youare particularly likely to run into them. In the presence of references and pointers, any two names for objects ofcompatible types may in fact refer to the same object. Here are some other situations in which aliasing can showits Medusa-like visage:

class Base {

void mf1(Base& rb); // rb and *this could be

// the same

...

};

void f1(Base& rb1,Base& rb2); // rb1 and rb2 could be

// the same

class Derived: public Base {

Page 63: Effective C++ and More Effective C++

void mf2(Base& rb); // rb and *this could be

// the same

...

};

int f2(Derived& rd, Base& rb); // rd and rb could be

// the same

These examples happen to use references, but pointers would serve just as well.

As you can see, aliasing can crop up in a variety of guises, so you can't just forget about it and hope you'll neverrun into it. Well, maybe you can, but most of us can't. At the expense of mixing my metaphors, this is a clear casein which an ounce of prevention is worth its weight in gold. Anytime you write a function in which aliasingcould conceivably be present, you must take that possibility into account when you write the code.

Back to Item 16: Assign to all data members in operator=. Continue to Classes and Functions: Design and Declaration

Page 64: Effective C++ and More Effective C++

Back to Item 17: Check for assignment to self in operator=. Continue to Item 18: Strive for class interfaces that are complete and minimal.

Classes and Functions: Design and Declaration

Declaring a new class in a program creates a new type: class design is type design. You probably don't havemuch experience with type design, because most languages don't offer you the opportunity to get any practice. InC++, it is of fundamental importance, not just because you can do it if you want to, but because you are doing itevery time you declare a class, whether you mean to or not.

Designing good classes is challenging because designing good types is challenging. Good types have a naturalsyntax, an intuitive semantics, and one or more efficient implementations. In C++, a poorly thought out classdefinition can make it impossible to achieve any of these goals. Even the performance characteristics of a class'smember functions are determined as much by the declarations of those member functions as they are by theirdefinitions.

How, then, do you go about designing effective classes? First, you must understand the issues you face. Virtuallyevery class requires that you confront the following questions, the answers to which often lead to constraints onyour design:

How should objects be created and destroyed? How this is done strongly influences the design of yourconstructors and destructor, as well as your versions of operator new, operator new[], operator delete,and operator delete[], if you write them. (Item M8 describes the differences among these terms.)

How does object initialization differ from object assignment? The answer to this question determines thebehavior of and the differences between your constructors and your assignment operators.

What does it mean to pass objects of the new type by value? Remember, the copy constructor defineswhat it means to pass an object by value.

What are the constraints on legal values for the new type? These constraints determine the kind of errorchecking you'll have to do inside your member functions, especially your constructors and assignmentoperators. It may also affect the exceptions your functions throw and, if you use them, your functions'exception specifications (see Item M14).

Does the new type fit into an inheritance graph? If you inherit from existing classes, you are constrainedby the design of those classes, particularly by whether the functions you inherit are virtual or nonvirtual. Ifyou wish to allow other classes to inherit from your class, that will affect whether the functions youdeclare are virtual.

What kind of type conversions are allowed? If you wish to allow objects of type A to be implicitlyconverted into objects of type B, you will want to write either a type conversion function in class A or anon-explicit constructor in class B that can be called with a single argument. If you wish to allow explicitconversions only, you'll want to write functions to perform the conversions, but you'll want to avoidmaking them type conversion operators or non-explicit single-argument constructors. (Item M5 discussesthe advantages and disadvantages of user-defined conversion functions.)

What operators and functions make sense for the new type? The answer to this question determineswhich functions you'll declare in your class interface.

What standard operators and functions should be explicitly disallowed? Those are the ones you'll needto declare private.

Who should have access to the members of the new type? This question helps you determine whichmembers are public, which are protected, and which are private. It also helps you determine whichclasses and/or functions should be friends, as well as whether it makes sense to nest one class insideanother.

How general is the new type? Perhaps you're not really defining a new type. Perhaps you're defining awhole family of types. If so, you don't want to define a new class, you want to define a new class template.

These are difficult questions to answer, so defining effective classes in C++ is far from simple. Done properly,however, user-defined classes in C++ yield types that are all but indistinguishable from built-in types, and thatmakes all the effort worthwhile.

A discussion of the details of each of the above issues would comprise a book in its own right, so the guidelinesthat follow are anything but comprehensive. However, they highlight some of the most important design

Page 65: Effective C++ and More Effective C++

considerations, warn about some of the most frequent errors, and provide solutions to some of the most commonproblems encountered by class designers. Much of the advice is as applicable to non-member functions as it isto member functions, so in this section I consider the design and declaration of global and namespace-residentfunctions, too.

Back to Item 17: Check for assignment to self in operator=. Continue to Item 18: Strive for class interfaces that are complete and minimal.

Page 66: Effective C++ and More Effective C++

Back to Design and Declaration Continue to Item 19: Differentiate among member functions, non-member functions, and friend functions.

Item 18: Strive for class interfaces that are complete and minimal.

The client interface for a class is the interface that is accessible to the programmers who use the class.Typically, only functions exist in this interface, because having data members in the client interface has a numberof drawbacks (see Item 20).

Trying to figure out what functions should be in a class interface can drive you crazy. You're pulled in twocompletely different directions. On the one hand, you'd like to build a class that is easy to understand,straightforward to use, and easy to implement. That usually implies a fairly small number of member functions,each of which performs a distinct task. On other hand, you'd like your class to be powerful and convenient touse, which often means adding functions to provide support for commonly performed tasks. How do you decidewhich functions go into the class and which ones don't?

Try this: aim for a class interface that is complete and minimal.

A complete interface is one that allows clients to do anything they might reasonably want to do. That is, for anyreasonable task that clients might want to accomplish, there is a reasonable way to accomplish it, although itmay not be as convenient as clients might like. A minimal interface, on the other hand, is one with as fewfunctions in it as possible, one in which no two member functions have overlapping functionality. If you offer acomplete, minimal interface, clients can do whatever they want to do, but the class interface is no morecomplicated than absolutely necessary.

The desirability of a complete interface seems obvious enough, but why a minimal interface? Why not just giveclients everything they ask for, adding functionality until everyone is happy?

Aside from the moral issue ? is it really right to mollycoddle your clients? ? there are definite technicaldisadvantages to a class interface that is crowded with functions. First, the more functions in an interface, theharder it is for potential clients to understand. The harder it is for them to understand, the more reluctant theywill be to learn how to use it. A class with 10 functions looks tractable to most people, but a class with 100functions is enough to make many programmers run and hide. By expanding the functionality of your class tomake it as attractive as possible, you may actually end up discouraging people from learning how to use it.

A large interface can also lead to confusion. Suppose you create a class that supports cognition for an artificialintelligence application. One of your member functions is called think, but you later discover that some peoplewant the function to be called ponder, and others prefer the name ruminate. In an effort to be accommodating,you offer all three functions, even though they do the same thing. Consider then the plight of a potential client ofyour class who is trying to figure things out. The client is faced with three different functions, all of which aresupposed to do the same thing. Can that really be true? Isn't there some subtle difference between the three,possibly in efficiency or generality or reliability? If not, why are there three different functions? Rather thanappreciating your flexibility, such a potential client is likely to wonder what on earth you were thinking (orpondering, or ruminating over).

A second disadvantage to a large class interface is that of maintenance (see Item M32). It's simply more difficultto maintain and enhance a class with many functions than it is a class with few. It is more difficult to avoidduplicated code (with the attendant duplicated bugs), and it is more difficult to maintain consistency across theinterface. It's also more difficult to document.

Finally, long class definitions result in long header files. Because header files typically have to be read everytime a program is compiled (see Item 34), class definitions that are longer than necessary can incur a substantialpenalty in total compile-time over the life of a project.

The long and short of it is that the gratuitous addition of functions to an interface is not without costs, so youneed to think carefully about whether the convenience of a new function (a new function can only be added forconvenience if the interface is already complete) justifies the additional costs in complexity, comprehensibility,maintainability, and compilation speed.

Page 67: Effective C++ and More Effective C++

Yet there's no sense in being unduly miserly. It is often justifiable to offer more than a minimal set of functions. Ifa commonly performed task can be implemented much more efficiently as a member function, that may welljustify its addition to the interface. (Then again, it may not. See Item M16.) If the addition of a member functionmakes the class substantially easier to use, that may be enough to warrant its inclusion in the class. And if addinga member function is likely to prevent client errors, that, too, is a powerful argument for its being part of theinterface.

Consider a concrete example: a template for classes that implement arrays with client-defined upper and lowerbounds and that offer optional bounds-checking. The beginning of such an array template is shown below:

template<class T>

class Array {

public:

enum BoundsCheckingStatus {NO_CHECK_BOUNDS = 0,

CHECK_BOUNDS = 1};

Array(int lowBound, int highBound,

BoundsCheckingStatus check = NO_CHECK_BOUNDS);

Array(const Array& rhs);

~Array();

Array& operator=(const Array& rhs);

private:

int lBound, hBound; // low bound, high bound

vector<T> data; // contents of array; see

// Item 49 for vector info

BoundsCheckingStatus checkingBounds;

};

The member functions declared so far are the ones that require basically no thinking (or pondering orruminating). You have a constructor to allow clients to specify each array's bounds, a copy constructor, anassignment operator, and a destructor. In this case, you've declared the destructor nonvirtual, which implies thatthis class is not to be used as a base class (see Item 14).

The declaration of the assignment operator is actually less clear-cut than it might at first appear. After all,built-in arrays in C++ don't allow assignment, so you might want to disallow it for your Array objects, too (see Item 27). On the other hand, the array-like vector template (in the standard library ? see Item 49) permitsassignments between vector objects. In this example, you'll follow vector's lead, and that decision, as you'll seebelow, will affect other portions of the classes's interface.

Old-time C hacks would cringe to see this interface. Where is the support for declaring an array of a particularsize? It would be easy enough to add another constructor,

Array(int size,

BoundsCheckingStatus check = NO_CHECK_BOUNDS);

but this is not part of a minimal interface, because the constructor taking an upper and lower bound can be usedto accomplish the same thing. Nonetheless, it might be a wise political move to humor the old geezers, possiblyunder the rubric of consistency with the base language.

What other functions do you need? Certainly it is part of a complete interface to index into an array: // return element for read/write

T& operator[](int index);

// return element for read-only

const T& operator[](int index) const;

Page 68: Effective C++ and More Effective C++

By declaring the same function twice, once const and once non-const, you provide support for both const andnon-const Array objects. The difference in return types is significant, as is explained in Item 21.

As it now stands, the Array template supports construction, destruction, pass-by-value, assignment, andindexing, which may strike you as a complete interface. But look closer. Suppose a client wants to loop throughan array of integers, printing out each of its elements, like so:

Array<int> a(10, 20); // bounds on a are 10 to 20

...

for (int i = lower bound of a; i <= upper bound of a; ++i)

cout << "a[" << i << "] = " << a[i] << '\n';

How is the client to get the bounds of a? The answer depends on what happens during assignment of Arrayobjects, i.e., on what happens inside Array::operator=. In particular, if assignment can change the bounds of anArray object, you must provide member functions to return the current bounds, because the client has no way ofknowing a priori what the bounds are at any given point in the program. In the example above, if a was the targetof an assignment between the time it was defined and the time it was used in the loop, the client would have noway to determine the current bounds of a.

On the other hand, if the bounds of an Array object cannot be changed during assignment, then the bounds arefixed at the point of definition, and it would be possible (though cumbersome) for a client to keep track of thesebounds. In that case, though it would be convenient to offer functions to return the current bounds, such functionswould not be part of a truly minimal interface.

Proceeding on the assumption that assignment can modify the bounds of an object, the bounds functions could bedeclared thus:

int lowBound() const;

int highBound() const;

Because these functions don't modify the object on which they are invoked, and because you prefer to use constwhenever you can (see Item 21), these are both declared const member functions. Given these functions, the loopabove would be written as follows:

for (int i = a.lowBound(); i <= a.highBound(); ++i)

cout << "a[" << i << "] = " << a[i] << '\n';

Needless to say, for such a loop to work for an array of objects of type T, an operator<< function must bedefined for objects of type T. (That's not quite true. What must exist is an operator<< for T or for some othertype to which T may be implicitly converted (see Item M5). But you get the idea.)

Some designers would argue that the Array class should also offer a function to return the number of elements inan Array object. The number of elements is simply highBound()-lowBound()+1, so such a function is not reallynecessary, but in view of the frequency of off-by-one errors, it might not be a bad idea to add such a function.

Other functions that might prove worthwhile for this class include those for input and output, as well as thevarious relational operators (e.g., <, >, ==, etc.). None of those functions is part of a minimal interface,however, because they can all be implemented in terms of loops containing calls to operator[].

Speaking of functions like operator<<, operator>>, and the relational operators, Item 19 discusses why they arefrequently implemented as non-member friend functions instead of as member functions. That being the case,don't forget that friend functions are, for all practical purposes, part of a class's interface. That means that friendfunctions count toward a class interface's completeness and minimalness.

Back to Design and Declaration Continue to Item 19: Differentiate among member functions, non-member functions, and friend functions.

Page 69: Effective C++ and More Effective C++

Back to Item 18: Strive for class interfaces that are complete and minimal. Continue to Item 20: Avoid data members in the public interface.

Item 19: Differentiate among member functions, non-member functions, and friend functions.

The biggest difference between member functions and non-member functions is that member functions can bevirtual and non-member functions can't. As a result, if you have a function that has to be dynamically bound (see Item 38), you've got to use a virtual function, and that virtual function must be a member of some class. It's assimple as that. If your function doesn't need to be virtual, however, the water begins to muddy a bit.

Consider a class for representing rational numbers: class Rational {

public:

Rational(int numerator = 0, int denominator = 1);

int numerator() const;

int denominator() const;

private:

...

};

As it stands now, this is a pretty useless class. (Using the terms of Item 18, the interface is certainly minimal,but it's far from complete.) You know you'd like to support arithmetic operations like addition, subtraction,multiplication, etc., but you're unsure whether you should implement them via a member function, a non-memberfunction, or possibly a non-member function that's a friend.

When in doubt, be object-oriented. You know that, say, multiplication of rational numbers is related to theRational class, so try bundling the operation with the class by making it a member function:

class Rational {

public:

...

const Rational operator*(const Rational& rhs) const;

};

(If you're unsure why this function is declared the way it is ? returning a const by-value result, but taking areference-to-const as its argument ? consult Items 21-23.)

Now you can multiply rational numbers with the greatest of ease: Rational oneEighth(1, 8);

Rational oneHalf(1, 2);

Rational result = oneHalf * oneEighth; // fine

result = result * oneEighth; // fine

But you're not satisfied. You'd also like to support mixed-mode operations, where Rationals can be multipliedwith, for example, ints. When you try to do this, however, you find that it works only half the time:

result = oneHalf * 2; // fine

result = 2 * oneHalf; // error!

This is a bad omen. Multiplication is supposed to be commutative, remember?

The source of the problem becomes apparent when you rewrite the last two examples in their equivalentfunctional form:

result = oneHalf.operator*(2); // fine

Page 70: Effective C++ and More Effective C++

result = 2.operator*(oneHalf); // error!

The object oneHalf is an instance of a class that contains an operator*, so your compilers call that function.However, the integer 2 has no associated class, hence no operator* member function. Your compilers will alsolook for a non-member operator* (i.e., one that's in a visible namespace or is global) that can be called likethis,

result = operator*(2, oneHalf); // error!

but there is no non-member operator* taking an int and a Rational, so the search fails.

Look again at the call that succeeds. You'll see that its second parameter is the integer 2, yet Rational::operator*takes a Rational object as its argument. What's going on here? Why does 2 work in one position and not in theother?

What's going on is implicit type conversion. Your compilers know you're passing an int and the function requiresa Rational, but they also know that they can conjure up a suitable Rational by calling the Rational constructorwith the int you provided, so that's what they do (see Item M19). In other words, they treat the call as if it hadbeen written more or less like this:

const Rational temp(2); // create a temporary

// Rational object from 2

result = oneHalf * temp; // same as

// oneHalf.operator*(temp);

Of course, they do this only when non-explicit constructors are involved, because explicit constructors can't beused for implicit conversions; that's what explicit means. If Rational were defined like this,

class Rational {

public:

explicit Rational(int numerator = 0, // this ctor is

int denominator = 1); // now explicit

...

const Rational operator*(const Rational& rhs) const;

...

};

neither of these statements would compile: result = oneHalf * 2; // error!

result = 2 * oneHalf; // error!

That would hardly qualify as support for mixed-mode arithmetic, but at least the behavior of the two statementswould be consistent.

The Rational class we've been examining, however, is designed to allow implicit conversions from built-intypes to Rationals ? that's why Rational's constructor isn't declared explicit. That being the case, compilers willperform the implicit conversion necessary to allow result's first assignment to compile. In fact, yourhandy-dandy compilers will perform this kind of implicit type conversion, if it's needed, on every parameter ofevery function call. But they will do it only for parameters listed in the parameter list, never for the object onwhich a member function is invoked, i.e., the object corresponding to *this inside a member function. That's whythis call works,

result = oneHalf.operator*(2); // converts int -> Rational

Page 71: Effective C++ and More Effective C++

and this one does not: result = 2.operator*(oneHalf); // doesn't convert

// int -> Rational

The first case involves a parameter listed in the function declaration, but the second one does not.

Nonetheless, you'd still like to support mixed-mode arithmetic, and the way to do it is by now perhaps clear:make operator* a non-member function, thus allowing compilers to perform implicit type conversions on allarguments:

class Rational {

... // contains no operator*

};

// declare this globally or within a namespace; see

// Item M20 for why it's written as it is

const Rational operator*(const Rational& lhs,

const Rational& rhs)

{

return Rational(lhs.numerator() * rhs.numerator(),

lhs.denominator() * rhs.denominator());

}

Rational oneFourth(1, 4);

Rational result;

result = oneFourth * 2; // fine

result = 2 * oneFourth; // hooray, it works!

This is certainly a happy ending to the tale, but there is a nagging worry. Should operator* be made a friend ofthe Rational class?

In this case, the answer is no, because operator* can be implemented entirely in terms of the class's publicinterface. The code above shows one way to do it. Whenever you can avoid friend functions, you should,because, much as in real life, friends are often more trouble than they're worth.

However, it's not uncommon for functions that are not members, yet are still conceptually part of a classinterface, to need access to the non-public members of the class.

As an example, let's fall back on a workhorse of this book, the String class. If you try to overload operator>>and operator<< for reading and writing String objects, you'll quickly discover that they shouldn't be memberfunctions. If they were, you'd have to put the String object on the left when you called the functions:

// a class that incorrectly declares operator>> and

// operator<< as member functions

class String {

public:

String(const char *value);

...

istream& operator>>(istream& input);

ostream& operator<<(ostream& output);

private:

char *data;

};

String s;

Page 72: Effective C++ and More Effective C++

s >> cin; // legal, but contrary

// to convention

s << cout; // ditto

That would confuse everyone. As a result, these functions shouldn't be member functions. Notice that this is adifferent case from the one we discussed above. Here the goal is a natural calling syntax; earlier we wereconcerned about implicit type conversions.

If you were designing these functions, you'd come up with something like this: istream& operator>>(istream& input, String& string)

{

delete [] string.data;

read from input into some memory, and make string.data

point to it

return input;

}

ostream& operator<<(ostream& output,

const String& string)

{

return output << string.data;

}

Notice that both functions need access to the data field of the String class, a field that's private. However, youalready know that you have to make these functions non-members. You're boxed into a corner and have nochoice: non-member functions with a need for access to non-public members of a class must be made friends ofthat class.

The lessons of this Item are summarized below, in which it is assumed that f is the function you're trying todeclare properly and C is the class to which it is conceptually related:

Virtual functions must be members. If f needs to be virtual, make it a member function of C. operator>> and operator<< are never members. If f is operator>> or operator<<, make f a non-member

function. If, in addition, f needs access to non-public members of C, make f a friend of C. Only non-member functions get type conversions on their left-most argument. If f needs type

conversions on its left-most argument, make f a non-member function. If, in addition, f needs access tonon-public members of C, make f a friend of C.

Everything else should be a member function. If none of the other cases apply, make f a memberfunction of C.

Back to Item 18: Strive for class interfaces that are complete and minimal. Continue to Item 20: Avoid data members in the public interface.

Page 73: Effective C++ and More Effective C++

Back to Item 19: Differentiate among member functions, non-member functions, and friend functions. Continue to Item 21: Use const whenever possible.

Item 20: Avoid data members in the public interface.

First, let's look at this issue from the point of view of consistency. If everything in the public interface is afunction, clients of your class won't have to scratch their heads trying to remember whether to use parentheseswhen they want to access a member of your class. They'll just do it, because everything is a function. Over thecourse of a lifetime, that can save a lot of head scratching.

You don't buy the consistency argument? How about the fact that using functions gives you much more precisecontrol over the accessibility of data members? If you make a data member public, everybody has read/writeaccess to it, but if you use functions to get and set its value, you can implement no access, read-only access, andread-write access. Heck, you can even implement write-only access if you want to:

class AccessLevels {

public:

int getReadOnly() const{ return readOnly; }

void setReadWrite(int value) { readWrite = value; }

int getReadWrite() const { return readWrite; }

void setWriteOnly(int value) { writeOnly = value; }

private:

int noAccess; // no access to this int

int readOnly; // read-only access to

// this int

int readWrite; // read-write access to

// this int

int writeOnly; // write-only access to

// this int

};

Still not convinced? Then it's time to bring out the big gun: functional abstraction. If you implement access to adata member through a function, you can later replace the data member with a computation, and nobody usingyour class will be any the wiser.

For example, suppose you are writing an application in which some automated equipment is monitoring thespeed of passing cars. As each car passes, its speed is computed, and the value is added to a collection of all thespeed data collected so far:

class SpeedDataCollection {

public:

void addValue(int speed); // add a new data value

double averageSoFar() const; // return average speed

};

Now consider the implementation of the member function averageSoFar (see also Item M18). One way toimplement it is to have a data member in the class that is a running average of all the speed data so far collected.Whenever averageSoFar is called, it just returns the value of that data member. A different approach is to haveaverageSoFar compute its value anew each time it's called, something it could do by examining each data valuein the collection. (For a more general discussion of these two approaches, see Items M17 and M18.)

The first approach ? keeping a running average ? makes each SpeedDataCollection object bigger, because you

Page 74: Effective C++ and More Effective C++

have to allocate space for the data member holding the running average. However, averageSoFar can beimplemented very efficiently; it's just an inline function (see Item 33) that returns the value of the data member.Conversely, computing the average whenever it's requested will make averageSoFar run slower, but eachSpeedDataCollection object will be smaller.

Who's to say which is best? On a machine where memory is tight, and in an application where averages areneeded only infrequently, computing the average each time is a better solution. In an application where averagesare needed frequently, speed is of the essence, and memory is not an issue, keeping a running average ispreferable. The important point is that by accessing the average through a member function, you can use eitherimplementation, a valuable source of flexibility that you wouldn't have if you made a decision to include therunning average data member in the public interface.

The upshot of all this is that you're just asking for trouble by putting data members in the public interface, so playit safe by hiding all your data members behind a wall of functional abstraction. If you do it now, we'll throw inconsistency and fine-grained access control at no extra cost!

Back to Item 19: Differentiate among member functions, non-member functions, and friend functions. Continue to Item 21: Use const whenever possible.

Page 75: Effective C++ and More Effective C++

Back to Item 20: Avoid data members in the public interface. Continue to Item 22: Prefer pass-by-reference to pass-by-value.

Item 21: Use const whenever possible.

The wonderful thing about const is that it allows you to specify a certain semantic constraint ? a particularobject should not be modified ? and compilers will enforce that constraint. It allows you to communicate to bothcompilers and other programmers that a value should remain invariant. Whenever that is true, you should be sureto say so explicitly, because that way you enlist your compilers' aid in making sure the constraint isn't violated.

The const keyword is remarkably versatile. Outside of classes, you can use it for global or namespace constants(see Items 1 and 47) and for static objects (local to a file or a block). Inside classes, you can use it for bothstatic and nonstatic data members (see also Item 12).

For pointers, you can specify whether the pointer itself is const, the data it points to is const, both, or neither:

char *p = "Hello"; // non-const pointer,

// non-const data5

const char *p = "Hello"; // non-const pointer,

// const data

char * const p = "Hello"; // const pointer,

// non-const data

const char * const p = "Hello"; // const pointer,

// const data

This syntax isn't quite as capricious as it looks. Basically, you mentally draw a vertical line through the asteriskof a pointer declaration, and if the word const appears to the left of the line, what's pointed to is constant; if theword const appears to the right of the line, the pointer itself is constant; if const appears on both sides of theline, both are constant.

When what's pointed to is constant, some programmers list const before the type name. Others list it after thetype name but before the asterisk. As a result, the following functions take the same parameter type:

class Widget { ... };

void f1(const Widget *pw); // f1 takes a pointer to a

// constant Widget object

void f2(Widget const *pw); // so does f2

Because both forms exist in real code, you should accustom yourself to both of them.

Some of the most powerful uses of const stem from its application to function declarations. Within a functiondeclaration, const can refer to the function's return value, to individual parameters, and, for member functions, tothe function as a whole.

Having a function return a constant value often makes it possible to reduce the incidence of client errors withoutgiving up safety or efficiency. In fact, as Item 29 demonstrates, using const with a return value can make itpossible to improve the safety and efficiency of a function that would otherwise be problematic.

For example, consider the declaration of the operator* function for rational numbers that is introduced in Item 19:

const Rational operator*(const Rational& lhs,

Page 76: Effective C++ and More Effective C++

const Rational& rhs);

Many programmers squint when they first see this. Why should the result of operator* be a const object?Because if it weren't, clients would be able to commit atrocities like this:

Rational a, b, c;

...

(a * b) = c; // assign to the product

// of a*b!

I don't know why any programmer would want to make an assignment to the product of two numbers, but I doknow this: it would be flat-out illegal if a, b, and c were of a built-in type. One of the hallmarks of gooduser-defined types is that they avoid gratuitous behavioral incompatibilities with the built-ins, and allowingassignments to the product of two numbers seems pretty gratuitous to me. Declaring operator*'s return valueconst prevents it, and that's why It's The Right Thing To Do.

There's nothing particularly new about const parameters ? they act just like local const objects. (See Item M19,however, for a discussion of how const parameters can lead to the creation of temporary objects.) Memberfunctions that are const, however, are a different story.

The purpose of const member functions, of course, is to specify which member functions may be invoked onconst objects. Many people overlook the fact that member functions differing only in their constness can beoverloaded, however, and this is an important feature of C++. Consider the String class once again:

class String {

public:

...

// operator[] for non-const objects

char& operator[](int position)

{ return data[position]; }

// operator[] for const objects

const char& operator[](int position) const

{ return data[position]; }

private:

char *data;

};

String s1 = "Hello";

cout << s1[0]; // calls non-const

// String::operator[]

const String s2 = "World";

cout << s2[0]; // calls const

// String::operator[]

By overloading operator[] and giving the different versions different return values, you are able to have constand non-const Strings handled differently:

String s = "Hello"; // non-const String object

cout << s[0]; // fine ? reading a

// non-const String

s[0] = 'x'; // fine ? writing a

Page 77: Effective C++ and More Effective C++

// non-const String

const String cs = "World"; // const String object

cout << cs[0]; // fine ? reading a

// const String

cs[0] = 'x'; // error! ? writing a

// const String

By the way, note that the error here has only to do with the return value of the operator[] that is called; the callsto operator[] themselves are all fine. The error arises out of an attempt to make an assignment to a const char&,because that's the return value from the const version of operator[].

Also note that the return type of the non-const operator[] must be a reference to a char ? a char itself will not do.If operator[] did return a simple char, statements like this wouldn't compile:

s[0] = 'x';

That's because it's never legal to modify the return value of a function that returns a built-in type. Even if it werelegal, the fact that C++ returns objects by value (see Item 22) would mean that a copy of s.data[0] would bemodified, not s.data[0] itself, and that's not the behavior you want, anyway.

Let's take a brief time-out for philosophy. What exactly does it mean for a member function to be const? Thereare two prevailing notions: bitwise constness and conceptual constness.

The bitwise const camp believes that a member function is const if and only if it doesn't modify any of theobject's data members (excluding those that are static), i.e., if it doesn't modify any of the bits inside the object.The nice thing about bitwise constness is that it's easy to detect violations: compilers just look for assignmentsto data members. In fact, bitwise constness is C++'s definition of constness, and a const member function isn'tallowed to modify any of the data members of the object on which it is invoked.

Unfortunately, many member functions that don't act very const pass the bitwise test. In particular, a memberfunction that modifies what a pointer points to frequently doesn't act const. But if only the pointer is in theobject, the function is bitwise const, and compilers won't complain. That can lead to counterintuitive behavior:

class String {

public:

// the constructor makes data point to a copy

// of what value points to

String(const char *value);

...

operator char *() const { return data;}

private:

char *data;

};

const String s = "Hello"; // declare constant object

char *nasty = s; // calls op char*() const

*nasty = 'M'; // modifies s.data[0]

Page 78: Effective C++ and More Effective C++

cout << s; // writes "Mello"

Surely there is something wrong when you create a constant object with a particular value and you invoke onlyconst member functions on it, yet you are still able to change its value! (For a more detailed discussion of thisexample, see Item 29.)

This leads to the notion of conceptual constness. Adherents to this philosophy argue that a const member functionmight modify some of the bits in the object on which it's invoked, but only in ways that are undetectable byclients. For example, your String class might want to cache the length of the object whenever it's requested (see Item M18):

class String {

public:

// the constructor makes data point to a copy

// of what value points to

String(const char *value): lengthIsValid(false) { ... }

...

size_t length() const;

private:

char *data;

size_t dataLength; // last calculated length

// of string

bool lengthIsValid; // whether length is

// currently valid

};

size_t String::length() const

{

if (!lengthIsValid) {

dataLength = strlen(data); // error!

lengthIsValid = true; // error!

}

return dataLength;

}

This implementation of length is certainly not bitwise const ? both dataLength and lengthIsValid may bemodified ? yet it seems as though it should be valid for const String objects. Compilers, you will find,respectfully disagree; they insist on bitwise constness. What to do?

The solution is simple: take advantage of the const-related wiggle room the °C++ standardization committeethoughtfully provided for just these types of situations. That wiggle room takes the form of the keyword mutable.When applied to nonstatic data members, mutable frees those members from the constraints of bitwiseconstness:

class String {

public:

... // same as above

private:

char *data;

mutable size_t dataLength; // these data members are

// now mutable; they may be

Page 79: Effective C++ and More Effective C++

mutable bool lengthIsValid; // modified anywhere, even

// inside const member

}; // functions

size_t String::length() const

{

if (!lengthIsValid) {

dataLength = strlen(data); // now fine

lengthIsValid = true; // also fine

}

return dataLength;

}

mutable is a wonderful solution to the bitwise-constness-is-not-quite-what-I-had-in-mind problem, but it wasadded to C++ relatively late in the standardization process, so your compilers may not support it yet. If that's thecase, you must descend into the dark recesses of C++, where life is cheap and constness may be cast away.

Inside a member function of class C, the this pointer behaves as if it had been declared as follows:

C * const this; // for non-const member

// functions

const C * const this; // for const member

// functions

That being the case, all you have to do to make the problematic version of String::length (i.e., the one you couldfix with mutable if your compilers supported it) valid for both const and non-const objects is to change the typeof this from const C * const to C * const. You can't do that directly, but you can fake it by initializing a localpointer to point to the same object as this does. Then you can access the members you want to modify through thelocal pointer:

size_t String::length() const

{

// make a local version of this that's

// not a pointer-to-const

String * const localThis =

const_cast<String * const>(this);

if (!lengthIsValid) {

localThis->dataLength = strlen(data);

localThis->lengthIsValid = true;

}

return dataLength;

}

Pretty this ain't, but sometimes a programmer's just gotta do what a programmer's gotta do.

Unless, of course, it's not guaranteed to work, and sometimes the old cast-away-constness trick isn't. Inparticular, if the object this points to is truly const, i.e., was declared const at its point of definition, the resultsof casting away its constness are undefined. If you want to cast away constness in one of your member functions,you'd best be sure that the object you're doing the casting on wasn't originally defined to be const.

There is one other time when casting away constness may be both useful and safe. That's when you have a constobject you want to pass to a function taking a non-const parameter, and you know the parameter won't bemodified inside the function. The second condition is important, because it is always safe to cast away theconstness of an object that will only be read ? not written ? even if that object was originally defined to beconst.

Page 80: Effective C++ and More Effective C++

For example, some libraries have been known to incorrectly declare the strlen function as follows: size_t strlen(char *s);

Certainly strlen isn't going to modify what s points to ? at least not the strlen I grew up with. Because of thisdeclaration, however, it would be invalid to call it on pointers of type const char *. To get around the problem,you can safely cast away the constness of such pointers when you pass them to strlen:

const char *klingonGreeting = "nuqneH"; // "nuqneH" is

// "Hello" in

// Klingon

size_t length =

strlen(const_cast<char*>(klingonGreeting));

Don't get cavalier about this, though. It is guaranteed to work only if the function being called, strlen in thiscase, doesn't try to modify what its parameter points to.

Back to Item 20: Avoid data members in the public interface. Continue to Item 22: Prefer pass-by-reference to pass-by-value.

5 According to the °C++ standard, the type of "Hello" is const char [], a type that's almost always treated asconst char*. We'd therefore expect it to be a violation of const correctness to initialize a char* variable with astring literal like "Hello". The practice is so common in C, however, that the standard grants a specialdispensation for initializations like this. Nevertheless, you should try to avoid them, because they're deprecated. Return

Page 81: Effective C++ and More Effective C++

Back to Item 21: Use const whenever possible. Continue to Item 23: Don't try to return a reference when you must return an object.

Item 22: Prefer pass-by-reference to pass-by-value.

In C, everything is passed by value, and C++ honors this heritage by adopting the pass-by-value convention asits default. Unless you specify otherwise, function parameters are initialized with copies of the actual arguments,and function callers get back a copy of the value returned by the function.

As I pointed out in the Introduction to this book, the meaning of passing an object by value is defined by the copyconstructor of that object's class. This can make pass-by-value an extremely expensive operation. For example,consider the following (rather contrived) class hierarchy:

class Person {

public:

Person(); // parameters omitted for

// simplicity

~Person();

...

private:

string name, address;

};

class Student: public Person {

public:

Student(); // parameters omitted for

// simplicity

~Student();

...

private:

string schoolName, schoolAddress;

};

Now consider a simple function returnStudent that takes a Student argument (by value) and immediately returnsit (also by value), plus a call to that function:

Student returnStudent(Student s) { return s; }

Student plato; // Plato studied under

// Socrates

returnStudent(plato); // call returnStudent

What happens during the course of this innocuous-looking function call?

The simple explanation is this: the Student copy constructor is called to initialize s with plato. Then the Studentcopy constructor is called again to initialize the object returned by the function with s. Next, the destructor iscalled for s. Finally, the destructor is called for the object returned by returnStudent. So the cost of thisdo-nothing function is two calls to the Student copy constructor and two calls to the Student destructor.

But wait, there's more! A Student object has two string objects within it, so every time you construct a Studentobject you must also construct two string objects. A Student object also inherits from a Person object, so everytime you construct a Student object you must also construct a Person object. A Person object has two additionalstring objects inside it, so each Person construction also entails two more string constructions. The end result isthat passing a Student object by value leads to one call to the Student copy constructor, one call to the Personcopy constructor, and four calls to the string copy constructor. When the copy of the Student object is destroyed,

Page 82: Effective C++ and More Effective C++

each constructor call is matched by a destructor call, so the overall cost of passing a Student by value is sixconstructors and six destructors. Because the function returnStudent uses pass-by-value twice (once for theparameter, once for the return value), the complete cost of a call to that function is twelve constructors andtwelve destructors!

In fairness to the C++ compiler-writers of the world, this is a worst-case scenario. Compilers are allowed toeliminate some of these calls to copy constructors. (The °C++ standard ? see Item 50 ? describes the preciseconditions under which they are allowed to perform this kind of magic, and Item M20 gives examples). Somecompilers take advantage of this license to optimize. Until such optimizations become ubiquitous, however,you've got to be wary of the cost of passing objects by value.

To avoid this potentially exorbitant cost, you need to pass things not by value, but by reference: const Student& returnStudent(const Student& s)

{ return s; }

This is much more efficient: no constructors or destructors are called, because no new objects are beingcreated.

Passing parameters by reference has another advantage: it avoids what is sometimes called the "slicingproblem." When a derived class object is passed as a base class object, all the specialized features that make itbehave like a derived class object are "sliced" off, and you're left with a simple base class object. This isalmost never what you want. For example, suppose you're working on a set of classes for implementing agraphical window system:

class Window {

public:

string name() const; // return name of window

virtual void display() const; // draw window and contents

};

class WindowWithScrollBars: public Window {

public:

virtual void display() const;

};

All Window objects have a name, which you can get at through the name function, and all windows can bedisplayed, which you can bring about by invoking the display function. The fact that display is virtual tells youthat the way in which simple base class Window objects are displayed is apt to differ from the way in which thefancy, high-priced WindowWithScrollBars objects are displayed (see Items 36, 37, and M33).

Now suppose you'd like to write a function to print out a window's name and then display the window. Here'sthe wrong way to write such a function:

// a function that suffers from the slicing problem

void printNameAndDisplay(Window w)

{

cout << w.name();

w.display();

}

Consider what happens when you call this function with a WindowWithScrollBars object: WindowWithScrollBars wwsb;

printNameAndDisplay(wwsb);

The parameter w will be constructed ? it's passed by value, remember? ? as a Window object, and all thespecialized information that made wwsb act like a WindowWithScrollBars object will be sliced off. InsideprintNameAndDisplay, w will always act like an object of class Window (because it is an object of classWindow), regardless of the type of object that is passed to the function. In particular, the call to display inside

Page 83: Effective C++ and More Effective C++

printNameAndDisplay will always call Window::display, never WindowWithScrollBars::display.

The way around the slicing problem is to pass w by reference: // a function that doesn't suffer from the slicing problem

void printNameAndDisplay(const Window& w)

{

cout << w.name();

w.display();

}

Now w will act like whatever kind of window is actually passed in. To emphasize that w isn't modified by thisfunction even though it's passed by reference, you've followed the advice of Item 21 and carefully declared it tobe const; how good of you.

Passing by reference is a wonderful thing, but it leads to certain complications of its own, the most notorious ofwhich is aliasing, a topic that is discussed in Item 17. In addition, it's important to recognize that you sometimescan't pass things by reference; see Item 23. Finally, the brutal fact of the matter is that references are almostalways implemented as pointers, so passing something by reference usually means really passing a pointer. As aresult, if you have a small object ? an int, for example ? it may actually be more efficient to pass it by value thanto pass it by reference.

Back to Item 21: Use const whenever possible. Continue to Item 23: Don't try to return a reference when you must return an object.

Page 84: Effective C++ and More Effective C++

Back to Item 22: Prefer pass-by-reference to pass-by-value. Continue to Item 24: Choose carefully between function overloading and parameter defaulting.

Item 23: Don't try to return a reference when you must return an object.

It is said that Albert Einstein once offered this advice: make things as simple as possible, but no simpler. TheC++ analogue might well be to make things as efficient as possible, but no more efficient.

Once programmers grasp the efficiency implications of pass-by-value for objects (see Item 22), they becomecrusaders, determined to root out the evil of pass-by-value wherever it may hide. Unrelenting in their pursuit ofpass-by-reference purity, they invariably make a fatal mistake: they start to pass references to objects that don'texist. This is not a good thing.

Consider a class for representing rational numbers, including a friend function (see Item 19) for multiplying tworationals together:

class Rational {

public:

Rational(int numerator = 0, int denominator = 1);

...

private:

int n, d; // numerator and denominator

friend

const Rational // see Item 21 for why

operator*(const Rational& lhs, // the return value is

const Rational& rhs) // const

};

inline const Rational operator*(const Rational& lhs,

const Rational& rhs)

{

return Rational(lhs.n * rhs.n, lhs.d * rhs.d);

}

Clearly, this version of operator* is returning its result object by value, and you'd be shirking your professionalduties if you failed to worry about the cost of that object's construction and destruction. Another thing that's clearis that you're cheap and you don't want to pay for such a temporary object (see Item M19) if you don't have to.So the question is this: do you have to pay?

Well, you don't have to if you can return a reference instead. But remember that a reference is just a name, aname for some existing object. Whenever you see the declaration for a reference, you should immediately askyourself what it is another name for, because it must be another name for something (see Item M1). In the case ofoperator*, if the function is to return a reference, it must return a reference to some other Rational object thatalready exists and that contains the product of the two objects that are to be multiplied together.

There is certainly no reason to expect that such an object exists prior to the call to operator*. That is, if youhave

Rational a(1, 2); // a = 1/2

Rational b(3, 5); // b = 3/5

Rational c = a * b; // c should be 3/10

it seems unreasonable to expect that there already exists a rational number with the value three-tenths. No, ifoperator* is to return a reference to such a number, it must create that number object itself.

A function can create a new object in only two ways: on the stack or on the heap. Creation on the stack isaccomplished by defining a local variable. Using that strategy, you might try to write your operator* as follows:

// the first wrong way to write this function

inline const Rational& operator*(const Rational& lhs,

Page 85: Effective C++ and More Effective C++

const Rational& rhs)

{

Rational result(lhs.n * rhs.n, lhs.d * rhs.d);

return result;

}

You can reject this approach out of hand, because your goal was to avoid a constructor call, and result willhave to be constructed just like any other object. In addition, this function has a more serious problem in that itreturns a reference to a local object, an error that is discussed in depth in Item 31.

That leaves you with the possibility of constructing an object on the heap and then returning a reference to it.Heap-based objects come into being through the use of new. This is how you might write operator* in that case:

// the second wrong way to write this function

inline const Rational& operator*(const Rational& lhs,

const Rational& rhs)

{

Rational *result =

new Rational(lhs.n * rhs.n, lhs.d * rhs.d);

return *result;

}

Well, you still have to pay for a constructor call, because the memory allocated by new is initialized by callingan appropriate constructor (see Items 5 and M8), but now you have a different problem: who will apply deleteto the object that was conjured up by your use of new?

In fact, this is a guaranteed memory leak. Even if callers of operator* could be persuaded to take the address ofthe function's result and use delete on it (astronomically unlikely ? Item 31 shows what the code would have tolook like), complicated expressions would yield unnamed temporaries that programmers would never be able toget at. For example, in

Rational w, x, y, z;

w = x * y * z;

both calls to operator* yield unnamed temporaries that the programmer never sees, hence can never delete.(Again, see Item 31.)

But perhaps you think you're smarter than the average bear ? or the average programmer. Perhaps you notice thatboth the on-the-stack and the on-the-heap approaches suffer from having to call a constructor for each resultreturned from operator*. Perhaps you recall that our initial goal was to avoid such constructor invocations.Perhaps you think you know of a way to avoid all but one constructor call. Perhaps the following implementationoccurs to you, an implementation based on operator* returning a reference to a static Rational object, onedefined inside the function:

// the third wrong way to write this function

inline const Rational& operator*(const Rational& lhs,

const Rational& rhs)

{

static Rational result; // static object to which a

// reference will be returned

somehow multiply lhs and rhs and put the

resulting value inside result;

return result;

}

This looks promising, though when you try to compose real C++ for the italicized pseudocode above, you'll findthat it's all but impossible to give result the correct value without invoking a Rational constructor, and avoidingsuch a call is the whole reason for this game. Let us posit that you manage to find a way, however, because no

Page 86: Effective C++ and More Effective C++

amount of cleverness can ultimately save this star-crossed design.

To see why, consider this perfectly reasonable client code:

bool operator==(const Rational& lhs, // an operator==

const Rational& rhs); // for Rationals

Rational a, b, c, d;

...

if ((a * b) == (c * d)) {

do whatever's appropriate when the products are equal;

} else {

do whatever's appropriate when they're not;

}

Now ponder this: the expression ((a*b) == (c*d)) will always evaluate to true, regardless of the values of a, b,c, and d!

It's easiest to understand this vexing behavior by rewriting the test for equality in its equivalent functional form: if (operator==(operator*(a, b), operator*(c, d)))

Notice that when operator== is called, there will already be two active calls to operator*, each of which willreturn a reference to the static Rational object inside operator*. Thus, operator== will be asked to compare thevalue of the static Rational object inside operator* with the value of the static Rational object inside operator*.It would be surprising indeed if they did not compare equal. Always.

With luck, this is enough to convince you that returning a reference from a function like operator* is a waste oftime, but I'm not so naive as to believe that luck is always sufficient. Some of you ? and you know who you are ?are at this very moment thinking, "Well, if one static isn't enough, maybe a static array will do the trick..."

Stop. Please. Haven't we suffered enough already?

I can't bring myself to dignify this design with example code, but I can sketch why even entertaining the notionshould cause you to blush in shame. First, you must choose n, the size of the array. If n is too small, you may runout of places to store function return values, in which case you'll have gained nothing over the single-staticdesign we just discredited. But if n is too big, you'll decrease the performance of your program, because everyobject in the array will be constructed the first time the function is called. That will cost you n constructors andn destructors, even if the function in question is called only once. If "optimization" is the process of improvingsoftware performance, this kind of thing should be called "pessimization." Finally, think about how you'd put thevalues you need into the array's objects and what it would cost you to do it. The most direct way to move a valuebetween objects is via assignment, but what is the cost of an assignment? In general, it's about the same as a callto a destructor (to destroy the old value) plus a call to a constructor (to copy over the new value). But your goalis to avoid the costs of construction and destruction! Face it: this approach just isn't going to pan out.

No, the right way to write a function that must return a new object is to have that function return a new object.For Rational's operator*, that means either the following code (which we first saw back on page 102) orsomething essentially equivalent:

inline const Rational operator*(const Rational& lhs,

const Rational& rhs)

{

return Rational(lhs.n * rhs.n, lhs.d * rhs.d);

}

Page 87: Effective C++ and More Effective C++

Sure, you may incur the cost of constructing and destructing operator*'s return value, but in the long run, that's asmall price to pay for correct behavior. Besides, the bill that so terrifies you may never arrive. Like allprogramming languages, C++ allows compiler implementers to apply certain optimizations to improve theperformance of the generated code, and it turns out that in some cases, operator*'s return value can be safelyeliminated (see Item M20). When compilers take advantage of that fact (and current compilers often do), yourprogram continues to behave the way it's supposed to, it just does it faster than you expected.

It all boils down to this: when deciding between returning a reference and returning an object, your job is tomake the choice that does the right thing. Let your compiler vendors wrestle with figuring out how to make thatchoice as inexpensive as possible.

Back to Item 22: Prefer pass-by-reference to pass-by-value. Continue to Item 24: Choose carefully between function overloading and parameter defaulting.

Page 88: Effective C++ and More Effective C++

Back to Item 23: Don't try to return a reference when you must return an object. Continue to Item 25: Avoid overloading on a pointer and a numerical type.

Item 24: Choose carefully between function overloading and parameter defaulting.

The confusion over function overloading and parameter defaulting stems from the fact that they both allow asingle function name to be called in more than one way:

void f(); // f is overloaded

void f(int x);

f(); // calls f()

f(10); // calls f(int)

void g(int x = 0); // g has a default

// parameter value

g(); // calls g(0)

g(10); // calls g(10)

So which should be used when?

The answer depends on two other questions. First, is there a value you can use for a default? Second, how manyalgorithms do you want to use? In general, if you can choose a reasonable default value and you want to employonly a single algorithm, you'll use default parameters (see also Item 38). Otherwise you'll use functionoverloading.

Here's a function to compute the maximum of up to five ints. This function uses ? take a deep breath and steelyourself ? std::numeric_limits<int>::min() as a default parameter value. I'll have more to say about that in amoment, but first, here's the code:

int max(int a,

int b = std::numeric_limits<int>::min(),

int c = std::numeric_limits<int>::min(),

int d = std::numeric_limits<int>::min(),

int e = std::numeric_limits<int>::min())

{

int temp = a > b ? a : b;

temp = temp > c ? temp : c;

temp = temp > d ? temp : d;

return temp > e ? temp : e;

}

Now, calm yourself. std::numeric_limits<int>::min() is just the fancy new-fangled way the standard C++ librarysays what C says via the INT_MIN macro in <limits.h>: it's the minimum possible value for an int in whatevercompiler happens to be processing your C++ source code. True, it's a deviation from the terseness for which Cis renowned, but there's a method behind all those colons and other syntactic strychnine.

Suppose you'd like to write a function template taking any built-in numeric type as its parameter, and you'd likethe functions generated from the template to print the minimum value representable by their instantiation type.Your template would look something like this:

template<class T>

void printMinimumValue()

{

cout << the minimum value representable by T;

}

Page 89: Effective C++ and More Effective C++

This is a difficult function to write if all you have to work with is <limits.h> and <float.h>. You don't knowwhat T is, so you don't know whether to print out INT_MIN or DBL_MIN or what.

To sidestep these difficulties, the standard C++ library (see Item 49) defines in the header <limits> a classtemplate, numeric_limits, which itself defines several static member functions. Each function returns informationabout the type instantiating the template. That is, the functions in numeric_limits<int> return information abouttype int, the functions in numeric_limits<double> return information about type double, etc. Among the functionsin numeric_limits is min. min returns the minimum representable value for the instantiating type, sonumeric_limits<int>::min() returns the minimum representable integer value.

Given numeric_limits (which, like nearly everything in the standard library, is in namespace std ? see Item 28;numeric_limits itself is in the header <limits>), writing printMinimumValue is as easy as can be:

template<class T>

void printMinimumValue()

{

cout << std::numeric_limits<T>::min();

}

This numeric_limits-based approach to specifying type-dependent constants may look expensive, but it's not.That's because the long-windedness of the source code fails to be reflected in the resultant object code. In fact,calls to functions in numeric_limits generate no instructions at all. To see how that can be, consider thefollowing, which is an obvious way to implement numeric_limits<int>::min:

#include <limits.h>

namespace std {

inline int numeric_limits<int>::min() throw ()

{ return INT_MIN; }

}

Because this function is declared inline, calls to it should be replaced by its body (see Item 33). That's justINT_MIN, which is itself a simple #define for some implementation-defined constant. So even though the maxfunction at the beginning of this Item looks like it's making a function call for each default parameter value, it'sjust using a clever way of referring to a type-dependent constant, in this case the value of INT_MIN. Suchefficient cleverness abounds in C++'s standard library. You really should read Item 49.

Getting back to the max function, the crucial observation is that max uses the same (rather inefficient) algorithmto compute its result, regardless of the number of arguments provided by the caller. Nowhere in the function doyou attempt to figure out which parameters are "real" and which are defaults. Instead, you have chosen a defaultvalue that cannot possibly affect the validity of the computation for the algorithm you're using. That's what makesthe use of default parameter values a viable solution.

For many functions, there is no reasonable default value. For example, suppose you want to write a function tocompute the average of up to five ints. You can't use default parameter values here, because the result of thefunction is dependent on the number of parameters passed in: if 3 values are passed in, you'll divide their sum by3; if 5 values are passed in, you'll divide their sum by 5. Furthermore, there is no "magic number" you can use asa default to indicate that a parameter wasn't actually provided by the client, because all possible ints are validvalues for the parameters. In this case, you have no choice: you must use overloaded functions:

double avg(int a);

double avg(int a, int b);

double avg(int a, int b, int c);

double avg(int a, int b, int c, int d);

double avg(int a, int b, int c, int d, int e);

The other case in which you need to use overloaded functions occurs when you want to accomplish a particulartask, but the algorithm that you use depends on the inputs that are given. This is commonly the case with

Page 90: Effective C++ and More Effective C++

constructors: a default constructor will construct an object from scratch, whereas a copy constructor willconstruct one from an existing object:

// A class for representing natural numbers

class Natural {

public:

Natural(int initValue);

Natural(const Natural& rhs);

private:

unsigned int value;

void init(int initValue);

void error(const string& msg);

};

inline

void Natural::init(int initValue) { value = initValue; }

Natural::Natural(int initValue)

{

if (initValue > 0) init(initValue);

else error("Illegal initial value");

}

inline Natural::Natural(const Natural& x)

{ init(x.value); }

The constructor taking an int has to perform error checking, but the copy constructor doesn't, so two differentfunctions are needed. That means overloading. However, note that both functions must assign an initial value forthe new object. This could lead to code duplication in the two constructors, so you maneuver around thatproblem by writing a private member function init that contains the code common to the two constructors. Thistactic ? using overloaded functions that call a common underlying function for some of their work ? is worthremembering, because it's frequently useful (see e.g., Item 12).

Back to Item 23: Don't try to return a reference when you must return an object. Continue to Item 25: Avoid overloading on a pointer and a numerical type.

Page 91: Effective C++ and More Effective C++

Back to Item 24: Choose carefully between function overloading and parameter defaulting. Continue to Item 26: Guard against potential ambiguity.

Item 25: Avoid overloading on a pointer and a numerical type.

Trivia question for the day: what is zero?

More specifically, what will happen here? void f(int x);

void f(string *ps);

f(0); // calls f(int) or f(string*)?

The answer is that 0 is an int ? a literal integer constant, to be precise ? so f(int) will always be called. Thereinlies the problem, because that's not what people always want. This is a situation unique in the world of C++: aplace where people think a call should be ambiguous, but compilers do not.

It would be nice if you could somehow tiptoe around this problem by use of a symbolic name, say, NULL fornull pointers, but that turns out to be a lot tougher than you might imagine.

Your first inclination might be to declare a constant called NULL, but constants have types, and what type shouldNULL have? It needs to be compatible with all pointer types, but the only type satisfying that requirement isvoid*, and you can't pass void* pointers to typed pointers without an explicit cast. Not only is that ugly, at firstglance it's not a whole lot better than the original situation:

void * const NULL = 0; // potential NULL definition

f(0); // still calls f(int)

f(static_cast<string*>(NULL)); // calls f(string*)

f(static_cast<string*>(0)); // calls f(string*)

On second thought, however, the use of NULL as a void* constant is a shade better than what you started with,because you avoid ambiguity if you use only NULL to indicate null pointers:

f(0); // calls f(int)

f(NULL); // error! ? type mis-match

f(static_cast<string*>(NULL)); // okay, calls f(string*)

At least now you've traded a runtime error (the call to the "wrong" f for 0) for a compile-time error (the attemptto pass a void* into a string* parameter). This improves matters somewhat (see Item 46), but the cast is stillunsatisfying.

If you shamefacedly crawl back to the preprocessor, you find that it doesn't really offer a way out, either,because the obvious choices seem to be

#define NULL 0

and #define NULL ((void*) 0)

and the first possibility is just the literal 0, which is fundamentally an integer constant (your original problem,as you'll recall), while the second possibility gets you back into the trouble with passing void* pointers to typedpointers.

If you've boned up on the rules governing type conversions, you may know that C++ views a conversion from along int to an int as neither better nor worse than a conversion from the long int 0 to the null pointer. You cantake advantage of that to introduce the ambiguity into the int/pointer question you probably believe should be

Page 92: Effective C++ and More Effective C++

there in the first place: #define NULL 0L // NULL is now a long int

void f(int x);

void f(string *p);

f(NULL); // error! ? ambiguous

However, this fails to help if you overload on a long int and a pointer: #define NULL 0L

void f(long int x); // this f now takes a long

void f(string *p);

f(NULL); // fine, calls f(long int)

In practice, this is probably safer than defining NULL to be an int, but it's more a way of moving the problemaround than of eliminating it.

The problem can be exterminated, but it requires the use of a late-breaking addition to the language: memberfunction templates (often simply called member templates). Member function templates are exactly what theysound like: templates within classes that generate member functions for those classes. In the case of NULL, youwant an object that acts like the expression static_cast<T*>(0) for every type T. That suggests that NULL shouldbe an object of a class containing an implicit conversion operator for every possible pointer type. That's a lot ofconversion operators, but a member template lets you force C++ into generating them for you:

// a first cut at a class yielding NULL pointer objects

class NullClass {

public:

template<class T> // generates

operator T*() const { return 0; } // operator T* for

}; // all types T; each

// function returns

// the null pointer

const NullClass NULL; // NULL is an object of

// type NullClass

void f(int x); // same as we originally had

void f(string *p); // ditto

f(NULL); // fine, converts NULL to

// string*, then calls f(string*)

This is a good initial draft, but it can be refined in several ways. First, we don't really need more than oneNullClass object, so there's no reason to give the class a name; we can just use an anonymous class and makeNULL of that type. Second, as long as we're making it possible to convert NULL to any type of pointer, weshould handle pointers to members, too. That calls for a second member template, one to convert 0 to type TC::* ("pointer to member of type T in class C") for all classes C and all types T. (If that makes no sense to you,or if you've never heard of ? much less used ? pointers to members, relax. Pointers to members are uncommonbeasts, rarely seen in the wild, and you'll probably never have to deal with them. The terminally curious maywish to consult Item 30, which discusses pointers to members in a bit more detail.) Finally, we should preventclients from taking the address of NULL, because NULL isn't supposed to act like a pointer, it's supposed to actlike a pointer value, and pointer values (e.g., 0x453AB002) don't have addresses.

Page 93: Effective C++ and More Effective C++

The jazzed-up NULL definition looks like this:

const // this is a const object...

class {

public:

template<class T> // convertible to any type

operator T*() const // of null non-member

{ return 0; } // pointer...

template<class C, class T> // or any type of null

operator T C::*() const // member pointer...

{ return 0; }

private:

void operator&() const; // whose address can't be

// taken (see Item 27)...

} NULL; // and whose name is NULL

This is truly a sight to behold, though you may wish to make a minor concession to practicality by giving theclass a name after all. If you don't, compiler messages referring to NULL's type are likely to be prettyunintelligible.

For another example of how member templates can be useful, take a look at Item M28.

An important point about all these attempts to come up with a workable NULL is that they help only if you're the caller. If you're the author of the functions being called, having a foolproof NULL won't help you at all, becauseyou can't compel your callers to use it. For example, even if you offer your clients the space-age NULL we justdeveloped, you still can't keep them from doing this,

f(0); // still calls f(int),

// because 0 is still an int

and that's just as problematic now as it was at the beginning of this Item.

As a designer of overloaded functions, then, the bottom line is that you're best off avoiding overloading on anumerical and a pointer type if you can possibly avoid it.

Back to Item 24: Choose carefully between function overloading and parameter defaulting. Continue to Item 26: Guard against potential ambiguity.

Page 94: Effective C++ and More Effective C++

Back to Item 25: Avoid overloading on a pointer and a numerical type. Continue to Item 27: Explicitly disallow use of implicitly generated member functions you don't want.

Item 26: Guard against potential ambiguity.

Everybody has to have a philosophy. Some people believe in laissez faire economics, others believe inreincarnation. Some people even believe that COBOL is a real programming language. C++ has a philosophy,too: it believes that potential ambiguity is not an error.

Here's an example of potential ambiguity:

class B; // forward declaration for

// class B

class A {

public:

A(const B&); // an A can be

// constructed from a B

};

class B {

public:

operator A() const; // a B can be

// converted to an A

};

There's nothing wrong with these class declarations ? they can coexist in the same program without the slightesttrouble. However, look what happens when you combine these classes with a function that takes an A object, butis actually passed a B object:

void f(const A&);

B b;

f(b); // error! ? ambiguous

Seeing the call to f, compilers know they must somehow come up with an object of type A, even though whatthey have in hand is an object of type B. There are two equally good ways to do this (see Item M5). On onehand, the class A constructor could be called; this would construct a new A object using b as an argument. Onthe other hand, b could be converted into an A object by calling the client-defined conversion operator in classB. Because these two approaches are considered equally good, compilers refuse to choose between them.

Of course, you could use this program for some time without ever running across the ambiguity. That's theinsidious peril of potential ambiguity. It can lie dormant in a program for long periods of time, undetected andinactive, until the day when some unsuspecting programmer does something that actually is ambiguous, at whichpoint pandemonium breaks out. This gives rise to the disconcerting possibility that you might release a librarythat can be called ambiguously without even being aware that you're doing it.

A similar form of ambiguity arises from standard conversions in the language ? you don't even need any classes: void f(int);

void f(char);

double d = 6.02;

f(d); // error! ? ambiguous

Should d be converted into an int or a char? The conversions are equally good, so compilers won't judge.Fortunately, you can get around this problem by using an explicit cast:

f(static_cast<int>(d)); // fine, calls f(int)

Page 95: Effective C++ and More Effective C++

f(static_cast<char>(d)); // fine, calls f(char)

Multiple inheritance (see Item 43) is rife with possibilities for potential ambiguity. The most straightforwardcase occurs when a derived class inherits the same member name from more than one base class:

class Base1 {

public:

int doIt();

};

class Base2 {

public:

void doIt();

};

class Derived: public Base1, // Derived doesn't declare

public Base2 { // a function called doIt

...

};

Derived d;

d.doIt(); // error! ? ambiguous

When class Derived inherits two functions with the same name, C++ utters not a whimper; at this point theambiguity is only potential. However, the call to doIt forces compilers to face the issue, and unless youexplicitly disambiguate the call by specifying which base class function you want, the call is an error:

d.Base1::doIt(); // fine, calls Base1::doIt

d.Base2::doIt(); // fine, calls Base2::doIt

That doesn't upset too many people, but the fact that accessibility restrictions don't enter into the picture hascaused more than one otherwise pacifistic soul to contemplate distinctly unpacifistic actions:

class Base1 { ... }; // same as above

class Base2 {

private:

void doIt(); // this function is now

}; // private

class Derived: public Base1, public Base2

{ ... }; // same as above

Derived d;

int i = d.doIt(); // error! ? still ambiguous!

The call to doIt continues to be ambiguous, even though only the function in Base1 is accessible! The fact thatonly Base1::doIt returns a value that can be used to initialize an int is also irrelevant ? the call remainsambiguous. If you want to make this call, you simply must specify which class's doIt is the one you want.

As is the case for most initially unintuitive rules in C++, there is a good reason why access restrictions are nottaken into account when disambiguating references to multiply inherited members. It boils down to this: changing

Page 96: Effective C++ and More Effective C++

the accessibility of a class member should never change the meaning of a program.

For example, assume that in the previous example, access restrictions were taken into account. Then theexpression d.doIt() would resolve to a call to Base1::doIt, because Base2's version was inaccessible. Nowassume that Base1 was changed so that its version of doIt was protected instead of public, and Base2 waschanged so that its version was public instead of private.

Suddenly the same expression, d.doIt(), would result in a completely different function call, even though neitherthe calling code nor the functions had been modified! Now that's unintuitive, and there would be no way forcompilers to issue even a warning. Considering your choices, you may decide that having to explicitlydisambiguate references to multiply inherited members isn't quite as unreasonable as you originally thought.

Given that there are all these different ways to write programs and libraries harboring potential ambiguity,what's a good software developer to do? Primarily, you need to keep an eye out for it. It's next to impossible toroot out all the sources of potential ambiguity, particularly when programmers combine libraries that weredeveloped independently (see also Item 28), but by understanding the situations that often lead to potentialambiguity, you're in a better position to minimize its presence in the software you design and develop.

Back to Item 25: Avoid overloading on a pointer and a numerical type. Continue to Item 27: Explicitly disallow use of implicitly generated member functions you don't want.

Page 97: Effective C++ and More Effective C++

Back to Item 26: Guard against potential ambiguity. Continue to Item 28: Partition the global namespace.

Item 27: Explicitly disallow use of implicitly generated member functions you don't want.

Suppose you want to write a class template, Array, whose generated classes behave like built-in C++ arrays inevery way, except they perform bounds checking. One of the design problems you would face is how to prohibitassignment between Array objects, because assignment isn't legal for C++ arrays:

double values1[10];

double values2[10];

values1 = values2; // error!

For most functions, this wouldn't be a problem. If you didn't want to allow a function, you simply wouldn't put itin the class. However, the assignment operator is one of those distinguished member functions that C++, alwaysthe helpful servant, writes for you if you neglect to write it yourself (see Item 45). What then to do?

The solution is to declare the function, operator= in this case, private. By declaring a member functionexplicitly, you prevent compilers from generating their own version, and by making the function private, youkeep people from calling it.

However, the scheme isn't foolproof; member and friend functions can still call your private function. Unless,that is, you are clever enough not to define the function. Then if you inadvertently call the function, you'll get anerror at link-time (see Item 46).

For Array, your template definition would start out like this: template<class T>

class Array {

private:

// Don't define this function!

Array& operator=(const Array& rhs);

...

};

Now if a client tries to perform assignments on Array objects, compilers will thwart the attempt, and if youinadvertently try it in a member or a friend function, the linker will yelp.

Don't assume from this example that this Item applies only to assignment operators. It doesn't. It applies to eachof the compiler-generated functions described in Item 45. In practice, you'll find that the behavioral similaritiesbetween assignment and copy construction (see Items 11 and 16) almost always mean that anytime you want todisallow use of one, you'll want to disallow use of the other, too.

Back to Item 26: Guard against potential ambiguity. Continue to Item 28: Partition the global namespace.

Page 98: Effective C++ and More Effective C++

Back to Item 27: Explicitly disallow use of implicitly generated member functions you don't want. Continue to Classes and Functions: Implementation

Item 28: Partition the global namespace.

The biggest problem with the global scope is that there's only one of them. In a large software project, there isusually a bevy of people putting names in this singular scope, and invariably this leads to name conflicts. Forexample, library1.h might define a number of constants, including the following:

const double LIB_VERSION = 1.204;

Ditto for library2.h: const int LIB_VERSION = 3;

It doesn't take great insight to see that there is going to be a problem if a program tries to include both library1.hand library2.h. Unfortunately, outside of cursing under your breath, sending hate mail to the library authors, andediting the header files until the name conflicts are eliminated, there is little you can do about this kind ofproblem.

You can, however, take pity on the poor souls who'll have your libraries foisted on them. You probably alreadyprepend some hopefully-unique prefix to each of your global symbols, but surely you must admit that theresulting identifiers are less than pleasing to gaze upon.

A better solution is to use a C++ namespace. Boiled down to its essence, a namespace is just a fancy way ofletting you use the prefixes you know and love without making people look at them all the time. So instead ofthis,

const double sdmBOOK_VERSION = 2.0; // in this library,

// each symbol begins

class sdmHandle { ... }; // with "sdm"

sdmHandle& sdmGetHandle(); // see Item 47 for why you

// might want to declare

// a function like this

you write this: namespace sdm {

const double BOOK_VERSION = 2.0;

class Handle { ... };

Handle& getHandle();

}

Clients then access symbols in your namespace in any of the usual three ways: by importing all the symbols in anamespace into a scope, by importing individual symbols into a scope, or by explicitly qualifying a symbol forone-time use. Here are some examples:

void f1()

{

using namespace sdm; // make all symbols in sdm

// available w/o qualification

// in this scope

cout << BOOK_VERSION; // okay, resolves to

// sdm::BOOK_VERSION

...

Handle h = getHandle(); // okay, Handle resolves to

// sdm::Handle, getHandle

... // resolves to sdm::getHandle

Page 99: Effective C++ and More Effective C++

}

void f2()

{

using sdm::BOOK_VERSION; // make only BOOK_VERSION

// available w/o qualification

// in this scope

cout << BOOK_VERSION; // okay, resolves to

// sdm::BOOK_VERSION

...

Handle h = getHandle(); // error! neither Handle

// nor getHandle were

... // imported into this scope

}

void f3()

{

cout << sdm::BOOK_VERSION; // okay, makes BOOK_VERSION

// available for this one use

... // only

double d = BOOK_VERSION; // error! BOOK_VERSION is

// not in scope

Handle h = getHandle(); // error! neither Handle

// nor getHandle were

... // imported into this scope

}

(Some namespaces have no names. Such unnamed namespaces are used to limit the visibility of the elementsinside the namespace. For details, see Item M31.)

One of the nicest things about namespaces is that potential ambiguity is not an error (see Item 26). As a result,you can import the same symbol from more than one namespace, yet still live a carefree life (provided you neveractually use the symbol). For instance, if, in addition to namespace sdm, you had need to make use of thisnamespace,

namespace AcmeWindowSystem {

...

typedef int Handle;

...

}

you could use both sdm and AcmeWindowSystem without conflict, provided you never referenced the symbolHandle. If you did refer to it, you'd have to explicitly say which namespace's Handle you wanted:

void f()

{

using namespace sdm; // import sdm symbols

using namespace AcmeWindowSystem; // import Acme symbols

Page 100: Effective C++ and More Effective C++

... // freely refer to sdm

// and Acme symbols

// other than Handle

Handle h; // error! which Handle?

sdm::Handle h1; // fine, no ambiguity

AcmeWindowSystem::Handle h2; // also no ambiguity

...

}

Contrast this with the conventional header-file-based approach, where the mere inclusion of both sdm.h andacme.h would cause compilers to complain about multiple definitions of the symbol Handle.

Namespaces were added to C++ relatively late in the standardization game, so perhaps you think they're not thatimportant and you can live without them. You can't. You can't, because almost everything in the standard library(see Item 49) lives inside the namespace std. That may strike you as a minor detail, but it affects you in a verydirect manner: it's why C++ now sports funny-looking extensionless header names like <iostream>, <string>,etc. For details, turn to Item 49.

Because namespaces were introduced comparatively recently, your compilers might not yet support them. Ifthat's the case, there's still no reason to pollute the global namespace, because you can approximate namespaceswith structs. You do it by creating a struct to hold your global names, then putting your global names inside thisstruct as static members:

// definition of a struct emulating a namespace

struct sdm {

static const double BOOK_VERSION;

class Handle { ... };

static Handle& getHandle();

};

const double sdm::BOOK_VERSION = 2.0; // obligatory defn

// of static data

// member

Now when people want to access your global names, they simply prefix them with the struct name: void f()

{

cout << sdm::BOOK_VERSION;

...

sdm::Handle h = sdm::getHandle();

...

}

If there are no name conflicts at the global level, clients of your library may find it cumbersome to use the fullyqualified names. Fortunately, there is a way you can let them have their scopes and ignore them, too.

For your type names, provide typedefs that remove the need for explicit scoping. That is, for a type name T inyour namespace-like struct S, provide a (global) typedef such that T is a synonym for S::T:

Page 101: Effective C++ and More Effective C++

typedef sdm::Handle Handle;

For each (static) object X in your struct, provide a (global) reference X that is initialized with S::X: const double& BOOK_VERSION = sdm::BOOK_VERSION;

Frankly, after you've read Item 47, the thought of defining a non-local static object like BOOK_VERSION willprobably make you queasy. (You'll want to replace such objects with the functions described in Item 47.)

Functions are treated much like objects, but even though it's legal to define references to functions, futuremaintainers of your code will dislike you a lot less if you employ pointers to functions instead:

sdm::Handle& (* const getHandle)() = // getHandle is a

sdm::getHandle; // const pointer (see

// Item 21) to

// sdm::getHandle

Note that getHandle is a const pointer. You don't really want to let clients make it point to something other thansdm::getHandle, do you?

(If you're dying to know how to define a reference to a function, this should revitalize you:

sdm::Handle& (&getHandle)() = // getHandle is a reference

sdm::getHandle; // to sdm::getHandle

Personally, I think this is kind of cool, but there's a reason you've probably never seen this before. Except forhow they're initialized, references to functions and const pointers to functions behave identically, and pointers tofunctions are much more readily understood.)

Given these typedefs and references, clients not suffering from global name conflicts can just use the unqualifiedtype and object names, while clients who do have conflicts can ignore the typedef and reference definitions anduse fully qualified names. It's unlikely that all your clients will want to use the shorthand names, so you shouldbe sure to put the typedefs and references in a different header file from the one containing yournamespace-emulating struct.

structs are a nice approximation to namespaces, but they're a long trek from the real thing. They fall short in avariety of ways, one of the most obvious of which is their treatment of operators. Simply put, operators definedas static member functions of structs can be invoked only through a function call, never via the natural infixsyntax that operators are designed to support:

// define a namespace-emulating struct containing

// types and functions for Widgets. Widget objects

// support addition via operator+

struct widgets {

class Widget { ... };

// see Item 21 for why the return value is const

static const Widget operator+(const Widget& lhs,

const Widget& rhs);

...

};

// attempt to set up global (unqualified) names for

// Widget and operator+ as described above

typedef widgets::Widget Widget;

Page 102: Effective C++ and More Effective C++

const Widget (* const operator+)(const Widget&, // error!

const Widget&); // operator+

// can't be a

// pointer name

Widget w1, w2, sum;

sum = w1 + w2; // error! no operator+

// taking Widgets is

// declared at this

// scope

sum = widgets::operator+(w1, w2); // legal, but hardly

// "natural" syntax

Such limitations should spur you to adopt real namespaces as soon as your compilers make it practical. Back to Item 27: Explicitly disallow use of implicitly generated member functions you don't want.

Continue to Classes and Functions: Implementation

Page 103: Effective C++ and More Effective C++

Back to Item 28: Partition the global namespace. Continue to Item 29: Avoid returning "handles" to internal data.

Classes and Functions: Implementation

Because C++ is strongly typed, coming up with appropriate definitions for your classes and templates andappropriate declarations for your functions is the lion's share of the battle. Once you've got those right, it's hardto go wrong with the template, class, and function implementations. Yet, somehow, people manage to do it.

Some problems arise from inadvertently violating abstraction: accidentally allowing implementation details topeek out from behind the class and function boundaries that are supposed to contain them. Others originate inconfusion over the length of an object's lifetime. Still others stem from premature optimization, typicallytraceable to the seductive nature of the inline keyword. Finally, some implementation strategies, while fine on alocal scale, result in levels of coupling between source files that can make it unacceptably costly to rebuildlarge systems.

Each of these problems, as well as others like them, can be avoided if you know what to watch out for. Theitems that follow identify some situations in which you need to be especially vigilant.

Back to Item 28: Partition the global namespace. Continue to Item 29: Avoid returning "handles" to internal data.

Page 104: Effective C++ and More Effective C++

Back to Implementation Continue to Item 30: Avoid member functions that return non-const pointers or references to members less accessible than

themselves.

Item 29: Avoid returning "handles" to internal data.

A scene from an object-oriented romance:

Object A: Darling, don't ever change!

Object B: Don't worry, dear, I'm const.

Yet just as in real life, A wonders, "Can B be trusted?" And just as in real life, the answer often hinges on B'snature: the constitution of its member functions.

Suppose B is a constant String object: class String {

public:

String(const char *value); // see Item 11 for pos-

~String(); // sible implementations;

// see Item M5 for comments

// on the first constructor

operator char *() const; // convert String -> char*;

// see also Item M5

...

private:

char *data;

};

const String B("Hello World"); // B is a const object

Because B is const, it had better be the case that the value of B now and evermore is "Hello World". Of course,this supposes that programmers working with B are playing the game in a civilized fashion. In particular, itdepends on the fact that nobody is "casting away the constness" of B through nefarious ploys such as this (see Item 21):

String& alsoB = // make alsoB another name

const_cast<String&>(B); // for B, but without the

// constness

Given that no one is doing such evil deeds, however, it seems a safe bet that B will never change. Or does it?Consider this sequence of events:

char *str = B; // calls B.operator char*()

strcpy(str, "Hi Mom"); // modifies what str

// points to

Does B still have the value "Hello World", or has it suddenly mutated into something you might say to yourmother? The answer depends entirely on the implementation of String::operator char*.

Here's a careless implementation, one that does the wrong thing. However, it does it very efficiently, which iswhy so many programmers fall into this trap:

// a fast, but incorrect implementation

Page 105: Effective C++ and More Effective C++

inline String::operator char*() const

{ return data; }

The flaw in this function is that it's returning a "handle" ? in this case, a pointer ? to information that should behidden inside the String object on which the function is invoked. That handle gives callers unrestricted access towhat the private field data points to. In other words, after the statement

char *str = B;

the situation looks like this:

Clearly, any modification to the memory pointed to by str will also change the effective value of B. Thus, eventhough B is declared const, and even though only const member functions are invoked on B, B might still acquire

Page 106: Effective C++ and More Effective C++

different values as the program runs. In particular, if str modifies what it points to, B will also change.

There's nothing inherently wrong with the way String::operator char* is written. What's troublesome is that it canbe applied to constant objects. If the function weren't declared const, there would be no problem, because itcouldn't be applied to objects like B.

Yet it seems perfectly reasonable to turn a String object, even a constant one, into its equivalent char*, so you'dlike to keep this function const. If you want to do that, you must rewrite your implementation to avoid returning ahandle to the object's internal data:

// a slower, but safer implementation

inline String::operator char*() const

{

char *copy = new char[strlen(data) + 1];

strcpy(copy, data);

return copy;

}

This implementation is safe, because it returns a pointer to memory that contains a copy of the data to which theString object points; there is no way to change the value of the String object through the pointer returned by thisfunction. As usual, such safety commands a price: this version of String::operator char* is slower than thesimple version above, and callers of this function must remember to use delete on the pointer that's returned.

If you think this version of operator char* is too slow, or if the potential memory leak makes you nervous (aswell it should), a slightly different tack is to return a pointer to constant chars:

class String {

public:

operator const char *() const;

...

};

inline String::operator const char*() const

{ return data; }

This function is fast and safe, and, though it's not the same as the function you originally specified, it suffices formost applications. It's also the moral equivalent of the °C++ standardization committee's solution to thestring/char* conundrum: the standard string type contains a member function c_str that returns a const char*version of the string in question. For more information on the standard string type, turn to Item 49.

A pointer isn't the only way to return a handle to internal data. References are just as easy to abuse. Here's acommon way to do it, again using the String class:

class String {

public:

...

char& operator[](int index) const

{ return data[index]; }

private:

char *data;

};

String s = "I'm not constant";

s[0] = 'x'; // fine, s isn't const

Page 107: Effective C++ and More Effective C++

const String cs = "I'm constant";

cs[0] = 'x'; // this modifies the const

// string, but compilers

// won't notice

Notice how String::operator[] returns its result by reference. That means that the caller of this function getsback another name for the internal element data[index], and that other name can be used to modify the internaldata of the supposedly constant object. This is the same problem you saw before, but this time the culprit is areference as a return value, not a pointer.

The general solutions to this kind of problem are the same as they were for pointers: either make the functionnon-const, or rewrite it so that no handle is returned. For a solution to this particular problem ? how to writeString::operator[] so that it works for both const and non-const objects ? see Item 21.

const member functions aren't the only ones that need to worry about returning handles. Even non-const memberfunctions must reconcile themselves to the fact that the validity of a handle expires at the same time as the objectto which it corresponds. This may be sooner than a client expects, especially when the object in question is acompiler-generated temporary object.

For example, take a look at this function, which returns a String object:

String someFamousAuthor() // randomly chooses and

{ // returns an author's name

switch (rand() % 3) { // rand() is in <stdlib.h>

// (and <cstdlib> ? see

// Item 49)

case 0:

return "Margaret Mitchell"; // Wrote "Gone with the

// Wind," a true classic

case 1:

return "Stephen King"; // His stories have kept

// millions from sleeping

// at night

case 2:

return "Scott Meyers"; // Ahem, one of these

} // things is not like the

// others...

return ""; // we can't get here, but

// all paths in a value-

// returning function must

} // return a value, sigh

Kindly set aside your concerns about how "random" the values returned from rand are, and please humor mydelusions of grandeur in associating myself with real writers. Instead, focus on the fact that the return value ofsomeFamousAuthor is a String object, a temporary String object (see Item M19). Such objects are transient ?their lifetimes generally extend only until the end of the expression containing the call to the function creatingthem. In this case, that would be until the end of the expression containing the call to someFamousAuthor.

Now consider this use of someFamousAuthor, in which we assume that String declares an operator const char*member function as described above:

const char *pc = someFamousAuthor();

cout << pc; // uh oh...

Page 108: Effective C++ and More Effective C++

Believe it or not, you can't predict what this code will do, at least not with any certainty. That's because by thetime you try to print out the sequence of characters pointed to by pc, that sequence is undefined. The difficultyarises from the events that transpire during the initialization of pc:

1. A temporary String object is created to hold someFamousAuthor's return value. 2. That String is converted to a const char* via String's operator const char* member function, and pc is

initialized with the resulting pointer. 3. The temporary String object is destroyed, which means its destructor is called. Within the destructor, its

data pointer is deleted (the code is shown in Item 11). However, data points to the same memory as pcdoes, so pc now points to deleted memory ? memory with undefined contents.

Because pc was initialized with a handle into a temporary object and temporary objects are destroyed shortlyafter they're created, the handle became invalid before pc could do anything with it. For all intents and purposes,pc was dead on arrival. Such is the danger of handles into temporary objects.

For const member functions, then, returning handles is ill-advised, because it violates abstraction. Even fornon-const member functions, however, returning handles can lead to trouble, especially when temporary objectsget involved. Handles can dangle, just like pointers, and just as you labor to avoid dangling pointers, you shouldstrive to avoid dangling handles, too.

Still, there's no reason to get fascist about it. It's not possible to stomp out all possible dangling pointers innontrivial programs, and it's rarely possible to eliminate all possible dangling handles, either. Nevertheless, ifyou avoid returning handles when there's no compelling need, your programs will benefit, and so will yourreputation.

Back to Implementation Continue to Item 30: Avoid member functions that return non-const pointers or references to members less accessible than

themselves.

Page 109: Effective C++ and More Effective C++

Back to Item 29: Avoid returning "handles" to internal data. Continue to Item 31: Never return a reference to a local object or to a dereferenced pointer initialized by new within the

function. Item 30: Avoid member functions that return non-const pointers or references to members less accessible thanthemselves. The reason for making a member private or protected is to limit access to it, right? Your overworked,underpaid C++ compilers go to lots of trouble to make sure that your access restrictions aren't circumvented,right? So it doesn't make a lot of sense for you to write functions that give random clients the ability to freelyaccess restricted members, now, does it? If you think it does make sense, please reread this paragraph over andover until you agree that it doesn't. It's easy to violate this simple rule. Here's an example:

class Address { ... }; // where someone lives

class Person {

public:

Address& personAddress() { return address; }

...

private:

Address address;

...

};

The member function personAddress provides the caller with the Address object contained in the Personobject, but, probably due to efficiency considerations, the result is returned by reference instead of by value(see Item 22). Unfortunately, the presence of this member function defeats the purpose of makingPerson::address private:

Person scott(...); // parameters omitted for

// simplicity

Address& addr = // assume that addr is

scott.personAddress(); // global

Now the global object addr is another name for scott.address, and it can be used to read and writescott.address at will. For all practical purposes, scott.address is no longer private; it is public, and the source ofthis promotion in accessibility is the member function personAddress. Of course, there is nothing special aboutthe access level private in this example; if address were protected, exactly the same reasoning would apply. References aren't the only cause for concern. Pointers can play this game, too. Here's the same example, butusing pointers this time:

class Person {

public:

Address * personAddress() { return &address; }

...

private:

Address address;

...

};

Address *addrPtr =

scott.personAddress(); // same problem as above

With pointers, however, you have to worry not only about data members, but also about member functions.That's because it's possible to return a pointer to a member function:

class Person; // forward declaration

// PPMF = "pointer to Person member function"

typedef void (Person::*PPMF)();

class Person {

public:

static PPMF verificationFunction()

Page 110: Effective C++ and More Effective C++

{ return &Person::verifyAddress; }

...

private:

Address address;

void verifyAddress();

};

If you're not used to socializing with pointers to member functions and typedefs thereof, the declaration forPerson::verificationFunction may seem daunting. Don't be intimidated. All it says is

verificationFunction is a member function that takes no parameters; its return value is a pointer to a member function of the Person class; the pointed-to function (i.e., verificationFunction's return value) takes no parameters and returns nothing,

i.e., void. As for the word static, that means what it always means in a member declaration: there is only one copy of themember for the entire class, and the member can be accessed without an object. For the complete story, consultyour favorite introductory C++ textbook. (If your favorite introductory C++ textbook doesn't discuss staticmembers, carefully tear out all its pages and recycle them. Dispose of the book's cover in an environmentallysound manner, then borrow or buy a better textbook.) In this last example, verifyAddress is a private member function, indicating that it's really an implementationdetail of the class; only class members should know about it (and friends, too, of course). However, the publicmember function verificationFunction returns a pointer to verifyAddress, so clients can again pull this kind ofthing:

PPMF pmf = scott.verificationFunction();

(scott.*pmf)(); // same as calling

// scott.verifyAddress

Here, pmf has become a synonym for Person::verifyAddress, with the crucial difference that there are norestrictions on its use. In spite of the foregoing discussion, you may someday be faced with a situation in which, pressed to achieveperformance constraints, you honestly need to write a member function that returns a reference or a pointer to aless-accessible member. At the same time, however, you won't want to sacrifice the access restrictions thatprivate and protected afford you. In those cases, you can almost always achieve both goals by returning a pointeror a reference to a const object. For details, take a look at Item 21.

Back to Item 29: Avoid returning "handles" to internal data. Continue to Item 31: Never return a reference to a local object or to a dereferenced pointer initialized by new within the

function.

Page 111: Effective C++ and More Effective C++

Back to Item 30: Avoid member functions that return non-const pointers or references to members less accessible thanthemselves.

Continue to Item 32: Postpone variable definitions as long as possible.

Item 31: Never return a reference to a local object or to a dereferenced pointer initialized by new within thefunction.

This Item may sound complicated, but it's not. It's simple common sense. Really. Honest. Trust me.

Consider first the matter of returning a reference to a local object. The problem here is that local objects are justthat, local. That means they're constructed when they're defined, and they're destructed when they go out ofscope. Their scope, however, is that of the function body in which they're located. When the function returns,control leaves its scope, so the objects local to that function are automatically destructed. As a result, if youreturn a reference to a local object, that local object has been destructed before the caller of the function evergets its computational hands on it.

This problem usually raises its ugly head when you try to improve the efficiency of a function by returning itsresult by reference instead of by value. The following example is the same as the one in Item 23, which pursuesin detail the question of when you can return a reference and when you can't:

class Rational { // class for rational numbers

public:

Rational(int numerator = 0, int denominator = 1);

~Rational();

...

private:

int n, d; // numerator and denominator

// notice that operator* (incorrectly) returns a reference

friend const Rational& operator*(const Rational& lhs,

const Rational& rhs);

};

// an incorrect implementation of operator*

inline const Rational& operator*(const Rational& lhs,

const Rational& rhs)

{

Rational result(lhs.n * rhs.n, lhs.d * rhs.d);

return result;

}

Here, the local object result is constructed upon entry into the body of operator*. However, local objects areautomatically destroyed when they go out of scope. result will go out of scope after execution of the returnstatement, so when you write this,

Rational two = 2;

Rational four = two * two; // same as

// operator*(two, two)

what happens during the function call is this: 1. The local object result is constructed. 2. A reference is initialized to be another name for result, and this reference is squirreled away as

operator*'s return value. 3. The local object result is destroyed, and the space it used to occupy on the stack is made available for use

by other parts of the program or by other programs. 4. The object four is initialized using the reference of step 2.

Everything is fine until step 4, at which point there occurs, as they say in the highest of high-tech circles, "amajor lossage." The reference initialized in step 2 ceased to refer to a valid object as of the end of step 3, so the

Page 112: Effective C++ and More Effective C++

outcome of the initialization of object four is completely undefined.

The lesson should be clear: don't return a reference to a local object.

"Okay," you say, "the problem is that the object I want to use goes out of scope too soon. I can fix that. I'll justcall new instead of using a local object." Like this:

// another incorrect implementation of operator*

inline const Rational& operator*(const Rational& lhs,

const Rational& rhs)

{

// create a new object on the heap

Rational *result =

new Rational(lhs.n * rhs.n, lhs.d * rhs.d);

// return it

return *result;

}

This approach does indeed avoid the problem of the previous example, but it introduces a new one in its place.To avoid a memory leak in your software, you know you must ensure that delete is applied to every pointerconjured up by new, but ay, there's the rub: who's to make the matching call to delete for this function's use ofnew?

Clearly, the caller of operator* must see to it that delete is applied. Clear, yes, and even easy to document, butnonetheless the cause is hopeless. There are two reasons for this pessimistic assessment.

First, it's well-known that programmers, as a breed, are sloppy. That doesn't mean that you're sloppy or that I'msloppy, but rare is the programmer who doesn't work with someone who is ? shall we say? ? a little on the flakyside. What are the odds that such programmers ? and we all know that they exist ? will remember that wheneverthey call operator*, they must take the address of the result and then use delete on it? That is, they must useoperator* like this:

const Rational& four = two * two; // get dereferenced

// pointer; store it in

// a reference

...

delete &four; // retrieve pointer

// and delete it

The odds are vanishingly small. Remember, if only a single caller of operator* fails to follow the rules, youhave a memory leak.

Returning dereferenced pointers has a second, more serious, problem, because it persists even in the presence ofthe most conscientious of programmers. Often, the result of operator* is a temporary intermediate value, anobject that exists only for the purposes of evaluating a larger expression. For example:

Rational one(1), two(2), three(3), four(4);

Rational product;

product = one * two * three * four;

Evaluation of the expression to be assigned to product requires three separate calls to operator*, a fact thatbecomes more evident when you rewrite the expression in its equivalent functional form:

product = operator*(operator*(operator*(one, two), three), four);

You know that each of the calls to operator* returns an object that needs to be deleted, but there is nopossibility of applying delete, because none of the returned objects has been saved anywhere.

Page 113: Effective C++ and More Effective C++

The only solution to this difficulty is to ask clients to code like this: const Rational& temp1 = one * two;

const Rational& temp2 = temp1 * three;

const Rational& temp3 = temp2 * four;

delete &temp1;

delete &temp2;

delete &temp3;

Do that, and the best you can hope for is that people will ignore you. More realistically, you'd be skinned alive,or possibly sentenced to ten years hard labor writing microcode for waffle irons and toaster ovens.

Learn your lesson now, then: writing a function that returns a dereferenced pointer is a memory leak just waitingto happen.

By the way, if you think you've come up with a way to avoid the undefined behavior inherent in returning areference to a local object and the memory leak haunting the return of a reference to a heap-allocated object, turnto Item 23 and read why returning a reference to a local static object also fails to work correctly. It may saveyou the trouble of seeking medical care for the arm you're likely to strain trying to pat yourself on the back.

Back to Item 30: Avoid member functions that return non-const pointers or references to members less accessible thanthemselves.

Continue to Item 32: Postpone variable definitions as long as possible.

Page 114: Effective C++ and More Effective C++

Back to Item 31: Never return a reference to a local object or to a dereferenced pointer initialized by new within the function. Continue to Item 33: Use inlining judiciously.

Item 32: Postpone variable definitions as long as possible.

So you subscribe to the C philosophy that variables should be defined at the beginning of a block. Cancel thatsubscription! In C++, it's unnecessary, unnatural, and expensive.

Remember that when you define a variable of a type with a constructor or destructor, you incur the cost ofconstruction when control reaches the variable's definition, and you incur the cost of destruction when thevariable goes out of scope. This means there's a cost associated with unused variables, so you want to avoidthem whenever you can.

Suave and sophisticated in the ways of programming as I know you to be, you're probably thinking you neverdefine unused variables, so this Item's advice is inapplicable to your tight, lean coding style. You may need tothink again. Consider the following function, which returns an encrypted version of a password, provided thepassword is long enough. If the password is too short, the function throws an exception of type logic_error,which is defined in the standard C++ library (see Item 49):

// this function defines the variable "encrypted" too soon

string encryptPassword(const string& password)

{

string encrypted;

if (password.length() < MINIMUM_PASSWORD_LENGTH) {

throw logic_error("Password is too short");

}

do whatever is necessary to place an encrypted

version of password in encrypted;

return encrypted;

}

The object encrypted isn't completely unused in this function, but it's unused if an exception is thrown. That is,you'll pay for the construction and destruction of encrypted even if encryptPassword throws an exception (seealso Item M15). As a result, you're better off postponing encrypted's definition until you know you'll need it:

// this function postpones "encrypted"'s definition until

// it's truly necessary

string encryptPassword(const string& password)

{

if (password.length() < MINIMUM_PASSWORD_LENGTH) {

throw logic_error("Password is too short");

}

string encrypted;

do whatever is necessary to place an encrypted

version of password in encrypted;

return encrypted;

}

This code still isn't as tight as it might be, because encrypted is defined without any initialization arguments.That means its default constructor will be used. In many cases, the first thing you'll do to an object is give itsome value, often via an assignment. Item 12 explains why default-constructing an object and then assigning to itis a lot less efficient than initializing it with the value you really want it to have. That analysis applies here, too.For example, suppose the hard part of encryptPassword is performed in this function:

void encrypt(string& s); // encrypts s in place

Page 115: Effective C++ and More Effective C++

Then encryptPassword could be implemented like this, though it wouldn't be the best way to do it: // this function postpones "encrypted"'s definition until

// it's necessary, but it's still needlessly inefficient

string encryptPassword(const string& password)

{

... // check length as above

string encrypted; // default-construct encrypted

encrypted = password; // assign to encrypted

encrypt(encrypted);

return encrypted;

}

A preferable approach is to initialize encrypted with password, thus skipping the (pointless) defaultconstruction:

// finally, the best way to define and initialize encrypted

string encryptPassword(const string& password)

{

... // check length

string encrypted(password); // define and initialize

// via copy constructor

encrypt(encrypted);

return encrypted;

}

This suggests the real meaning of "as long as possible" in this Item's title. Not only should you postpone avariable's definition until right before you have to use the variable, you should try to postpone the definition untilyou have initialization arguments for it. By doing so, you avoid not only constructing and destructing unneededobjects, you also avoid pointless default constructions. Further, you help document the purpose of variables byinitializing them in contexts in which their meaning is clear. Remember how in C you're encouraged to put ashort comment after each variable definition to explain what the variable will eventually be used for? Well,combine decent variable names (see also Item 28) with contextually meaningful initialization arguments, and youhave every programmer's dream: a solid argument for eliminating some comments.

By postponing variable definitions, you improve program efficiency, increase program clarity, and reduce theneed to document variable meanings. It looks like it's time to kiss those block-opening variable definitionsgood-bye.

Back to Item 31: Never return a reference to a local object or to a dereferenced pointer initialized by new within the function. Continue to Item 33: Use inlining judiciously.

Page 116: Effective C++ and More Effective C++

Back to Item 32: Postpone variable definitions as long as possible. Continue to Item 34: Minimize compilation dependencies between files.

Item 33: Use inlining judiciously.

Inline functions -- what a wonderful idea! They look like functions, they act like functions, they're ever so muchbetter than macros (see Item 1), and you can call them without having to incur the overhead of a function call.What more could you possibly ask for?

You actually get more than you might think, because avoiding the cost of a function call is only half the story.Compiler optimization routines are typically designed to concentrate on stretches of code that lack functioncalls, so when you inline a function, you may enable compilers to perform context-specific optimizations on thebody of the function. Such optimizations would be impossible for "normal" function calls.

However, let's not get carried away. In programming, as in life, there is no free lunch, and inline functions are noexception. The whole idea behind an inline function is to replace each call of that function with its code body,and it doesn't take a Ph.D. in statistics to see that this is likely to increase the overall size of your object code.On machines with limited memory, overzealous inlining can give rise to programs that are too big for theavailable space. Even with virtual memory, inline-induced code bloat can lead to pathological paging behavior(thrashing) that will slow your program to a crawl. (It will, however, provide your disk controller with a niceexercise regimen.) Too much inlining can also reduce your instruction cache hit rate, thus reducing the speed ofinstruction fetch from that of cache memory to that of primary memory.

On the other hand, if an inline function body is very short, the code generated for the function body may actuallybe smaller than the code generated for a function call. If that is the case, inlining the function may actually leadto smaller object code and a higher cache hit rate!

Bear in mind that the inline directive, like register, is a hint to compilers, not a command. That means compilersare free to ignore your inline directives whenever they want to, and it's not that hard to make them want to. Forexample, most compilers refuse to inline "complicated" functions (e.g., those that contain loops or arerecursive), and all but the most trivial virtual function calls stop inlining routines dead in their tracks. (Thisshouldn't be much of a surprise. virtual means "wait until runtime to figure out which function to call," and inlinemeans "during compilation, replace the call site with the called function." If compilers don't know whichfunction will be called, you can hardly blame them for refusing to make an inline call to it.) It all adds up to this:whether a given inline function is actually inlined is dependent on the implementation of the compiler you'reusing. Fortunately, most compilers have a diagnostic level that will result in a warning (see Item 48) if they failto inline a function you've asked them to.

Suppose you've written some function f and you've declared it inline. What happens if a compiler chooses, forwhatever reason, not to inline that function? The obvious answer is that f will be treated like a non-inlinefunction: code for f will be generated as if it were a normal "outlined" function, and calls to f will proceed asnormal function calls.

In theory, this is precisely what will happen, but this is one of those occasions when theory and practice may gotheir separate ways. That's because this very tidy solution to the problem of what to do about "outlined inlines"was added to C++ relatively late in the standardization process. Earlier specifications for the language (such asthe ARM ? see Item 50) told compiler vendors to implement different behavior, and the older behavior is stillcommon enough that you need to understand what it is.

Think about it for a minute, and you'll realize that inline function definitions are virtually always put in headerfiles. This allows multiple translation units (source files) to include the same header files and reap theadvantages of the inline functions that are defined within them. Here's an example, in which I adopt theconvention that source files end in ".cpp"; this is probably the most prevalent of the file naming conventions inthe world of C++:

// This is file example.h

inline void f() { ... } // definition of f

...

Page 117: Effective C++ and More Effective C++

// This is file source1.cpp

#include "example.h" // includes definition of f

... // contains calls to f

// This is file source2.cpp

#include "example.h" // also includes definition

// of f

... // also calls f

Under the old "outlined inline" rules and the assumption that f is not being inlined, when source1.cpp iscompiled, the resulting object file will contain a function called f, just as if f had never been declared inline.Similarly, when source2.cpp is compiled, its generated object file will also hold a function called f. When youtry to link the two object files together, you can reasonably expect your linker to complain that your programcontains two definitions of f, an error.

To prevent this problem, the old rules decreed that compilers treat an un-inlined inline function as if the functionhad been declared static ? that is, local to the file currently being compiled. In the example you just saw,compilers following the old rules would treat f as if it were static in source1.cpp when that file was beingcompiled and as if it were static in source2.cpp when that file was being compiled. This strategy eliminates thelink-time problem, but at a cost: each translation unit that includes the definition of f (and that calls f) contains itsown static copy of f. If f itself defines local static variables, each copy of f gets its own copy of the variables,something sure to astonish programmers who believe that "static" inside a function means "only one copy."

This leads to a stunning realization. Under both new rules and old, if an inline function isn't inlined, you still payfor the cost of a function call at each call site, but under the old rules, you can also suffer an increase in codesize, because each translation unit that includes and calls f gets its own copy of f's code and f's static variables!(To make matters worse, each copy of f and each copy of f's static variables tend to end up on different virtualmemory pages, so two calls to different copies of f are likely to entail one or more page faults.)

There's more. Sometimes your poor, embattled compilers have to generate a function body for an inline functioneven when they are perfectly willing to inline the function. In particular, if your program ever takes the addressof an inline function, compilers must generate a function body for it. How can they come up with a pointer to afunction that doesn't exist?

inline void f() {...} // as above

void (*pf)() = f; // pf points to f

int main()

{

f(); // an inline call to f

pf(); // a non-inline call to f

// through pf

...

}

In this case, you end up in the seemingly paradoxical situation whereby calls to f are inlined, but ? under the oldrules ? each translation unit that takes f's address still generates a static copy of the function. (Under the newrules, only a single out-of-line copy of f will be generated, regardless of the number of translation unitsinvolved.)

This aspect of un-inlined inline functions can affect you even if you never use function pointers, becauseprogrammers aren't necessarily the only ones asking for pointers to functions. Sometimes compilers do it. Inparticular, compilers sometimes generate out-of-line copies of constructors and destructors so that they can get

Page 118: Effective C++ and More Effective C++

pointers to those functions for use in constructing and destructing arrays of objects of a class (see also Item M8).

In fact, constructors and destructors are often worse candidates for inlining than a casual examination wouldindicate. For example, consider the constructor for class Derived below:

class Base {

public:

...

private:

string bm1, bm2; // base members 1 and 2

};

class Derived: public Base {

public:

Derived() {} // Derived's ctor is

... // empty -- or is it?

private:

string dm1, dm2, dm3; // derived members 1-3

};

This constructor certainly looks like an excellent candidate for inlining, since it contains no code. But looks canbe deceiving. Just because it contains no code doesn't necessarily mean it contains no code. In fact, it maycontain a fair amount of code.

C++ makes various guarantees about things that happen when objects are created and destroyed. Items 5 and M8describes how when you use new, your dynamically created objects are automatically initialized by theirconstructors, and how when you use delete, the corresponding destructors are invoked. Item 13 explains thatwhen you create an object, each base class of and each data member in that object is automatically constructed,and the reverse process regarding destruction automatically occurs when an object is destroyed. Those itemsdescribe what C++ says must happen, but C++ does not say how they happen. That's up to compilerimplementers, but it should be clear that those things don't just happen by themselves. There has to be some codein your program to make those things happen, and that code ? the code written by compiler implementers andinserted into your program during compilation ? has to go somewhere. Sometimes, it ends up in yourconstructors and destructors, so some implementations will generate code equivalent to the following for theallegedly empty Derived constructor above:

// possible implementation of Derived constructor

Derived::Derived()

{

// allocate heap memory for this object if it's supposed

// to be on the heap; see Item 8 for info on operator new

if (this object is on the heap)

this = ::operator new(sizeof(Derived));

Base::Base(); // initialize Base part

dm1.string(); // construct dm1

dm2.string(); // construct dm2

dm3.string(); // construct dm3

}

You could never hope to get code like this to compile, because it's not legal C++ ? not for you, anyway. For onething, you have no way of asking whether an object is on the heap from inside its constructor. (For anexamination of what it takes to reliably determine whether an object is on the heap, see Item M27.) For another,you're forbidden from assigning to this. And you can't invoke constructors via function calls, either. Yourcompilers, however, labor under no such constraints ? they can do whatever they like. But the legality of thecode is not the point. The point is that code to call operator new (if necessary), to construct base class parts, andto construct data members may be silently inserted into your constructors, and when it is, those constructors

Page 119: Effective C++ and More Effective C++

increase in size, thus making them less attractive candidates for inlining. Of course, the same reasoning appliesto the Base constructor, so if it's inlined, all the code inserted into it is also inserted into the Derived constructor(via the Derived constructor's call to the Base constructor). And if the string constructor also happens to beinlined, the Derived constructor will gain five copies of that function's code, one for each of the five strings in aDerived object (the two it inherits plus the three it declares itself). Now do you see why it's not necessarily ano-brain decision whether to inline Derived's constructor? Of course, similar considerations apply to Derived'sdestructor, which, one way or another, must see to it that all the objects initialized by Derived's constructor areproperly destroyed. It may also need to free the dynamically allocated memory formerly occupied by thejust-destroyed Derived object.

Library designers must evaluate the impact of declaring functions inline, because inline functions make itimpossible to provide binary upgrades to the inline functions in a library. In other words, if f is an inlinefunction in a library, clients of the library compile the body of f into their applications. If a library implementerlater decides to change f, all clients who've used f must recompile. This is often highly undesirable (see also Item 34). On the other hand, if f is a non-inline function, a modification to f requires only that clients relink. Thisis a substantially less onerous burden than recompiling and, if the library containing the function is dynamicallylinked, one that may be absorbed in a way that's completely transparent to clients.

Static objects inside inline functions often exhibit counterintuitive behavior. For this reason, it's generally agood idea to avoid declaring functions inline if those functions contain static objects. For details, consult ItemM26.

For purposes of program development, it is important to keep all these considerations in mind, but from a purelypractical point of view during coding, one fact dominates all others: most debuggers have trouble with inlinefunctions.

This should be no great revelation. How do you set a breakpoint in a function that isn't there? How do you stepthrough such a function? How do you trap calls to it? Without being unreasonably clever (or deviouslyunderhanded), you simply can't. Happily, this leads to a logical strategy for determining which functions shouldbe declared inline and which should not.

Initially, don't inline anything, or at least limit your inlining to those functions that are truly trivial, such as agebelow:

class Person {

public:

int age() const { return personAge; }

...

private:

int personAge;

...

};

By employing inlines cautiously, you facilitate your use of a debugger, but you also put inlining in its properplace: as a hand-applied optimization. Don't forget the empirically determined rule of 80-20 (see Item M16),which states that a typical program spends 80 percent of its time executing only 20 percent of its code. It's animportant rule, because it reminds you that your goal as a software developer is to identify the 20 percent ofyour code that is actually capable of increasing your program's overall performance. You can inline andotherwise tweak your functions until the cows come home, but it's all wasted effort unless you're focusing on the right functions.

Once you've identified the set of important functions in your application, the ones whose inlining will actuallymake a difference (a set that is itself dependent on the architecture on which you're running), don't hesitate todeclare them inline. At the same time, however, be on the lookout for problems caused by code bloat, and watchout for compiler warnings (see Item 48) that indicate that your inline functions haven't been inlined.

Used judiciously, inline functions are an invaluable component of every C++ programmer's toolbox, but, as the

Page 120: Effective C++ and More Effective C++

foregoing discussion has revealed, they're not quite as simple and straightforward as you might have thought. Back to Item 32: Postpone variable definitions as long as possible.

Continue to Item 34: Minimize compilation dependencies between files.

Page 121: Effective C++ and More Effective C++

Back to Item 33: Use inlining judiciously. Continue to Inheritance and Object-Oriented Design

Item 34: Minimize compilation dependencies between files.

So you go into your C++ program and you make a minor change to the implementation of a class. Not the classinterface, mind you, just the implementation; only the private stuff. Then you get set to rebuild the program,figuring that the compilation and linking should take only a few seconds. After all, only one class has beenmodified. You click on Rebuild or type make (or its moral equivalent), and you are astonished, then mortified,as you realize that the whole world is being recompiled and relinked!

Don't you just hate it when that happens?

The problem is that C++ doesn't do a very good job of separating interfaces from implementations. In particular,class definitions include not only the interface specification, but also a fair number of implementation details.For example:

class Person {

public:

Person(const string& name, const Date& birthday,

const Address& addr, const Country& country);

virtual ~Person();

... // copy constructor and assignment

// operator omitted for simplicity

string name() const;

string birthDate() const;

string address() const;

string nationality() const;

private:

string name_; // implementation detail

Date birthDate_; // implementation detail

Address address_; // implementation detail

Country citizenship_; // implementation detail

};

This is hardly a Nobel Prize-winning class design, although it does illustrate an interesting naming conventionfor distinguishing private data from public functions when the same name makes sense for both: the former aretagged with a trailing underbar. The important thing to observe is that class Person can't be compiled unless thecompiler also has access to definitions for the classes in terms of which Person is implemented, namely, string,Date, Address, and Country. Such definitions are typically provided through #include directives, so at the top ofthe file defining the Person class, you are likely to find something like this:

#include <string> // for type string (see Item 49)

#include "date.h"

#include "address.h"

#include "country.h"

Unfortunately, this sets up a compilation dependency between the file defining Person and these include files.As a result, if any of these auxiliary classes changes its implementation, or if any of the classes on which itdepends changes its implementation, the file containing the Person class must be recompiled, as must any filesthat use the Person class. For clients of Person, this can be more than annoying. It can be downrightincapacitating.

You might wonder why C++ insists on putting the implementation details of a class in the class definition. Forexample, why can't you define Person this way,

class string; // "conceptual" forward declaration for the

// string type. See Item 49 for details.

Page 122: Effective C++ and More Effective C++

class Date; // forward declaration

class Address; // forward declaration

class Country; // forward declaration

class Person {

public:

Person(const string& name, const Date& birthday,

const Address& addr, const Country& country);

virtual ~Person();

... // copy ctor, operator=

string name() const;

string birthDate() const;

string address() const;

string nationality() const;

};

specifying the implementation details of the class separately? If that were possible, clients of Person wouldhave to recompile only if the interface to the class changed. Because interfaces tend to stabilize beforeimplementations do, such a separation of interface from implementation could save untold hours ofrecompilation and linking over the course of a large software effort.

Alas, the real world intrudes on this idyllic scenario, as you will appreciate when you consider something likethis:

int main()

{

int x; // define an int

Person p(...); // define a Person

// (arguments omitted for

... // simplicity)

}

When compilers see the definition for x, they know they must allocate enough space to hold an int. No problem.Each compiler knows how big an int is. When compilers see the definition for p, however, they know they haveto allocate enough space for a Person, but how are they supposed to know how big a Person object is? The onlyway they can get that information is to consult the class definition, but if it were legal for a class definition toomit the implementation details, how would compilers know how much space to allocate?

In principle, this is no insuperable problem. Languages such as Smalltalk, Eiffel, and Java get around it all thetime. The way they do it is by allocating only enough space for a pointer to an object when an object is defined.That is, they handle the code above as if it had been written like this:

int main()

{

int x; // define an int

Person *p; // define a pointer

// to a Person

...

}

It may have occurred to you that this is in fact legal C++, and it turns out that you can play the "hide the objectimplementation behind a pointer" game yourself.

Here's how you employ the technique to decouple Person's interface from its implementation. First, you put only

Page 123: Effective C++ and More Effective C++

the following in the header file declaring the Person class: // compilers still need to know about these type

// names for the Person constructor

class string; // again, see Item 49 for information

// on why this isn't correct for string

class Date;

class Address;

class Country;

// class PersonImpl will contain the implementation

// details of a Person object; this is just a

// forward declaration of the class name

class PersonImpl;

class Person {

public:

Person(const string& name, const Date& birthday,

const Address& addr, const Country& country);

virtual ~Person();

... // copy ctor, operator=

string name() const;

string birthDate() const;

string address() const;

string nationality() const;

private:

PersonImpl *impl; // pointer to implementation

};

Now clients of Person are completely divorced from the details of strings, dates, addresses, countries, andpersons. Those classes can be modified at will, but Person clients may remain blissfully unaware. More to thepoint, they may remain blissfully un-recompiled. In addition, because they're unable to see the details of Person'simplementation, clients are unlikely to write code that somehow depends on those details. This is a trueseparation of interface and implementation.

The key to this separation is replacement of dependencies on class definitions with dependencies on classdeclarations. That's all you need to know about minimizing compilation dependencies: make your header filesself-sufficient whenever it's practical, and when it's not practical, be dependent on class declarations, not classdefinitions. Everything else flows from this simple design strategy.

There are three immediate implications: Avoid using objects when object references and pointers will do. You may define references and

pointers to a type with only a declaration for the type. Defining objects of a type necessitates the presenceof the type's definition.

Use class declarations instead of class definitions whenever you can. Note that you never need a classdefinition to declare a function using that class, not even if the function passes or returns the class type byvalue:

class Date; // class declaration

Date returnADate(); // fine ? no definition

void takeADate(Date d); // of Date is needed

Of course, pass-by-value is generally a bad idea (see Item 22), but if you find yourself forced to use it forsome reason, there's still no justification for introducing unnecessary compilation dependencies.

If you're surprised that the declarations for returnADate and takeADate compile without a definition for

Page 124: Effective C++ and More Effective C++

Date, join the club; so was I. It's not as curious as it seems, however, because if anybody calls thosefunctions, Date's definition must be visible. Oh, I know what you're thinking: why bother to declarefunctions that nobody calls? Simple. It's not that nobody calls them, it's that not everybody calls them. Forexample, if you have a library containing hundreds of function declarations (possibly spread over severalnamespaces ? see Item 28), it's unlikely that every client calls every function. By moving the onus ofproviding class definitions (via #include directives) from your header file of function declarations toclients' files containing function calls, you eliminate artificial client dependencies on type definitions theydon't really need.

Don't #include header files in your header files unless your headers won't compile without them.Instead, manually declare the classes you need, and let clients of your header files #include the additionalheaders necessary to make their code compile. A few clients may grumble that this is inconvenient, butrest assured that you are saving them much more pain than you're inflicting. In fact, this technique is sowell-regarded, it's enshrined in the standard C++ library (see Item 49); the header <iosfwd> containsdeclarations (and only declarations) for the types in the iostream library.

Classes like Person that contain only a pointer to an unspecified implementation are often called Handle classesor Envelope classes. (In the former case, the classes they point to are called Body classes; in latter case, thepointed-to classes are known as Letter classes.) Occasionally, you may hear people refer to such classes asCheshire Cat classes, an allusion to the cat in Alice in Wonderland that could, when it chose, leave behind onlyits smile after the rest of it had vanished.

Lest you wonder how Handle classes actually do anything, the answer is simple: they forward all their functioncalls to the corresponding Body classes, and those classes do the real work. For example, here's how two ofPerson's member functions would be implemented:

#include "Person.h" // because we're implementing

// the Person class, we must

// #include its class definition

#include "PersonImpl.h" // we must also #include

// PersonImpl's class definition,

// otherwise we couldn't call

// its member functions. Note

// that PersonImpl has exactly

// the same member functions as

// Person ? their interfaces

// are identical

Person::Person(const string& name, const Date& birthday,

const Address& addr, const Country& country)

{

impl = new PersonImpl(name, birthday, addr, country);

}

string Person::name() const

{

return impl->name();

}

Note how the Person constructor calls the PersonImpl constructor (implicitly, by using new ? see Items 5 andM8) and how Person::name calls PersonImpl::name. This is important. Making Person a handle class doesn'tchange what Person does, it just changes where it does it.

An alternative to the Handle class approach is to make Person a special kind of abstract base class called a Protocol class. By definition, a Protocol class has no implementation; its raison d'être is to specify an interfacefor derived classes (see Item 36). As a result, it typically has no data members, no constructors, a virtualdestructor (see Item 14), and a set of pure virtual functions that specify the interface. A Protocol class for Personmight look like this:

class Person {

public:

Page 125: Effective C++ and More Effective C++

virtual ~Person();

virtual string name() const = 0;

virtual string birthDate() const = 0;

virtual string address() const = 0;

virtual string nationality() const = 0;

};

Clients of this Person class must program in terms of Person pointers and references, because it's not possibleto instantiate classes containing pure virtual functions. (It is, however, possible to instantiate classes derivedfrom Person ? see below.) Like clients of Handle classes, clients of Protocol classes need not recompile unlessthe Protocol class's interface is modified.

Of course, clients of a Protocol class must have some way of creating new objects. They typically do it bycalling a function that plays the role of the constructor for the hidden (derived) classes that are actuallyinstantiated. Such functions go by several names (among them factory functions and virtual constructors), butthey all behave the same way: they return pointers to dynamically allocated objects that support the Protocolclass's interface (see also Item M25). Such a function might be declared like this,

// makePerson is a "virtual constructor" (aka, a "factory

// function") for objects supporting the Person interface

Person*

makePerson(const string& name, // return a ptr to

const Date& birthday, // a new Person

const Address& addr, // initialized with

const Country& country); // the given params

and used by clients like this: string name;

Date dateOfBirth;

Address address;

Country nation;

...

// create an object supporting the Person interface

Person *pp = makePerson(name, dateOfBirth, address, nation);

...

cout << pp->name() // use the object via the

<< " was born on " // Person interface

<< pp->birthDate()

<< " and now lives at "

<< pp->address();

...

delete pp; // delete the object when

// it's no longer needed

Because functions like makePerson are closely associated with the Protocol class whose interface is supportedby the objects they create, it's good style to declare them static inside the Protocol class:

class Person {

public:

... // as above

// makePerson is now a member of the class

static Person * makePerson(const string& name,

const Date& birthday,

const Address& addr,

Page 126: Effective C++ and More Effective C++

const Country& country);

};

This avoids cluttering the global namespace (or any other namespace) with lots of functions of this nature (seealso Item 28).

At some point, of course, concrete classes supporting the Protocol class's interface must be defined and realconstructors must be called. That all happens behind the scenes inside the implementation files for the virtualconstructors. For example, the Protocol class Person might have a concrete derived class RealPerson thatprovides implementations for the virtual functions it inherits:

class RealPerson: public Person {

public:

RealPerson(const string& name, const Date& birthday,

const Address& addr, const Country& country)

: name_(name), birthday_(birthday),

address_(addr), country_(country)

{}

virtual ~RealPerson() {}

string name() const; // implementations of

string birthDate() const; // these functions are not

string address() const; // shown, but they are

string nationality() const; // easy to imagine

private:

string name_;

Date birthday_;

Address address_;

Country country_;

};

Given RealPerson, it is truly trivial to write Person::makePerson: Person * Person::makePerson(const string& name,

const Date& birthday,

const Address& addr,

const Country& country)

{

return new RealPerson(name, birthday, addr, country);

}

RealPerson demonstrates one of the two most common mechanisms for implementing a Protocol class: itinherits its interface specification from the Protocol class (Person), then it implements the functions in theinterface. A second way to implement a Protocol class involves multiple inheritance, a topic explored in Item 43.

Okay, so Handle classes and Protocol classes decouple interfaces from implementations, thereby reducingcompilation dependencies between files. Cynic that you are, I know you're waiting for the fine print. "What doesall this hocus-pocus cost me?" you mutter. The answer is the usual one in Computer Science: it costs you somespeed at runtime, plus some additional memory per object.

In the case of Handle classes, member functions have to go through the implementation pointer to get to theobject's data. That adds one level of indirection per access. And you must add the size of this implementationpointer to the amount of memory required to store each object. Finally, the implementation pointer has to beinitialized (in the Handle class's constructors) to point to a dynamically allocated implementation object, so youincur the overhead inherent in dynamic memory allocation (and subsequent deallocation) ? see Item 10.

For Protocol classes, every function call is virtual, so you pay the cost of an indirect jump each time you make a

Page 127: Effective C++ and More Effective C++

function call (see Items 14 and M24). Also, objects derived from the Protocol class must contain a virtualpointer (again, see Items 14 and M24). This pointer may increase the amount of memory needed to store anobject, depending on whether the Protocol class is the exclusive source of virtual functions for the object.

Finally, neither Handle classes nor Protocol classes can get much use out of inline functions. All practical usesof inlines require access to implementation details, and that's the very thing that Handle classes and Protocolclasses are designed to avoid in the first place.

It would be a serious mistake, however, to dismiss Handle classes and Protocol classes simply because theyhave a cost associated with them. So do virtual functions, and you wouldn't want to forgo those, would you? (Ifso, you're reading the wrong book.) Instead, consider using these techniques in an evolutionary manner. UseHandle classes and Protocol classes during development to minimize the impact on clients whenimplementations change. Replace Handle classes and Protocol classes with concrete classes for production usewhen it can be shown that the difference in speed and/or size is significant enough to justify the increasedcoupling between classes. Someday, we may hope, tools will be available to perform this kind of transformationautomatically.

A skillful blending of Handle classes, Protocol classes, and concrete classes will allow you to developsoftware systems that execute efficiently and are easy to evolve, but there is a serious disadvantage: you mayhave to cut down on the long breaks you've been taking while your programs recompile.

Back to Item 33: Use inlining judiciously. Continue to Inheritance and Object-Oriented Design

Page 128: Effective C++ and More Effective C++

Back to Item 34: Minimize compilation dependencies between files. Continue to Item 35: Make sure public inheritance models "isa."

Inheritance and Object-Oriented Design

Many people are of the opinion that inheritance is what object-oriented programming is all about. Whether that'sso is debatable, but the number of Items in the other sections of this book should convince you that as far aseffective C++ programming is concerned, you have a lot more tools at your disposal than simply specifyingwhich classes inherit from which other classes.

Still, designing and implementing class hierarchies is fundamentally different from anything found in the worldof C. Certainly it is in the area of inheritance and object-oriented design that you are most likely to radicallyrethink your approach to the construction of software systems. Furthermore, C++ provides a bewilderingassortment of object-oriented building blocks, including public, protected, and private base classes; virtual andnonvirtual base classes; and virtual and nonvirtual member functions. Each of these features interacts not onlywith one another, but also with the other components of the language. As a result, trying to understand what eachfeature means, when it should be used, and how it is best combined with the non-object-oriented aspects of C++can be a daunting endeavor.

Further complicating the matter is the fact that different features of the language appear to do more or less thesame thing. Examples:

You need a collection of classes with many shared characteristics. Should you use inheritance and haveall the classes derived from a common base class, or should you use templates and have them allgenerated from a common code skeleton?

Class A is to be implemented in terms of class B. Should A have a data member of type B, or should Aprivately inherit from B?

You need to design a type-safe homogeneous container class, one not present in the standard library. (See Item 49 for a list of containers the library does provide.) Should you use templates, or would it be betterto build type-safe interfaces around a class that is itself implemented using generic (void*) pointers?

In the Items in this section, I offer guidance on how to answer questions such as these. However, I cannot hope toaddress every aspect of object-oriented design. Instead, I concentrate on explaining what the different features inC++ really mean, on what you are really saying when you use a particular feature. For example, publicinheritance means "isa" (see Item 35), and if you try to make it mean anything else, you will run into trouble.Similarly, a virtual function means "interface must be inherited," while a nonvirtual function means "bothinterface and implementation must be inherited." Failing to distinguish between these meanings has caused manya C++ programmer untold grief.

If you understand the meanings of C++'s varied features, you'll find that your outlook on object-oriented designshifts. Instead of it being an exercise in differentiating between language constructs, it will properly become amatter of figuring out what it is you want to say about your software system. Once you know what you want tosay, you'll be able to translate that into the appropriate C++ features without too much difficulty.

The importance of saying what you mean and understanding what you're saying cannot be overestimated. Theitems that follow provide a detailed examination of how to do this effectively. Item 44 summarizes thecorrespondence between C++'s object-oriented constructs and what they mean. It serves as a nice capstone forthis section, as well as a concise reference for future consultation.

Back to Item 34: Minimize compilation dependencies between files. Continue to Item 35: Make sure public inheritance models "isa."

Page 129: Effective C++ and More Effective C++

Back to Inheritance and Object-Oriented Design Continue to Item 36: Differentiate between inheritance of interface and inheritance of implementation.

Item 35: Make sure public inheritance models "isa."

In his book, Some Must Watch While Some Must Sleep (W. H. Freeman and Company, 1974), William Dementrelates the story of his attempt to fix in the minds of his students the most important lessons of his course. It isclaimed, he told his class, that the average British schoolchild remembers little more history than that the Battleof Hastings was in 1066. If a child remembers little else, Dement emphasized, he or she remembers the date1066. For the students in his course, Dement went on, there were only a few central messages, including,interestingly enough, the fact that sleeping pills cause insomnia. He implored his students to remember these fewcritical facts even if they forgot everything else discussed in the course, and he returned to these fundamentalprecepts repeatedly during the term.

At the end of the course, the last question on the final exam was, "Write one thing from the course that you willsurely remember for the rest of your life." When Dement graded the exams, he was stunned. Nearly everyone hadwritten "1066."

It is thus with great trepidation that I proclaim to you now that the single most important rule in object-orientedprogramming with C++ is this: public inheritance means "isa." Commit this rule to memory.

If you write that class D ("Derived") publicly inherits from class B ("Base"), you are telling C++ compilers (aswell as human readers of your code) that every object of type D is also an object of type B, but not vice versa.You are saying that B represents a more general concept than D, that D represents a more specialized conceptthan B. You are asserting that anywhere an object of type B can be used, an object of type D can be used just aswell, because every object of type D is an object of type B. On the other hand, if you need an object of type D,an object of type B will not do: every D isa B, but not vice versa.

C++ enforces this interpretation of public inheritance. Consider this example: class Person { ... };

class Student: public Person { ... };

We know from everyday experience that every student is a person, but not every person is a student. That isexactly what this hierarchy asserts. We expect that anything that is true of a person ? for example, that he or shehas a date of birth ? is also true of a student, but we do not expect that everything that is true of a student ? that heor she is enrolled in a particular school, for instance ? is true of people in general. The notion of a person ismore general than is that of a student; a student is a specialized type of person.

Within the realm of C++, any function that expects an argument of type Person (or pointer-to-Person orreference-to-Person) will instead take a Student object (or pointer-to-Student or reference-to-Student):

void dance(const Person& p); // anyone can dance

void study(const Student& s); // only students study

Person p; // p is a Person

Student s; // s is a Student

dance(p); // fine, p is a Person

dance(s); // fine, s is a Student,

// and a Student isa Person

study(s); // fine

Page 130: Effective C++ and More Effective C++

study(p); // error! p isn't a Student

This is true only for public inheritance. C++ will behave as I've described only if Student is publicly derivedfrom Person. Private inheritance means something entirely different (see Item 42), and no one seems to knowwhat protected inheritance is supposed to mean. Furthermore, the fact that a Student isa Person does not meanthat an array of Student isa array of Person. For more information on that topic, see Item M3.

The equivalence of public inheritance and isa sounds simple, but in practice, things aren't always sostraightforward. Sometimes your intuition can mislead you. For example, it is a fact that a penguin is a bird, andit is a fact that birds can fly. If we naively try to express this in C++, our effort yields:

class Bird {

public:

virtual void fly(); // birds can fly

...

};

class Penguin:public Bird { // penguins are birds

...

};

Suddenly we are in trouble, because this hierarchy says that penguins can fly, which we know is not true. Whathappened?

In this case, we are the victims of an imprecise language (English). When we say that birds can fly, we don'treally mean that all birds can fly, only that, in general, birds have the ability to fly. If we were more precise,we'd recognize that there are in fact several types of non-flying birds, and we would come up with the followinghierarchy, which models reality much better:

class Bird {

... // no fly function is

}; // declared

class FlyingBird: public Bird {

public:

virtual void fly();

...

};

class NonFlyingBird: public Bird {

... // no fly function is

// declared

};

class Penguin: public NonFlyingBird {

... // no fly function is

// declared

};

This hierarchy is much more faithful to what we really know than was the original design.

Page 131: Effective C++ and More Effective C++

Even now we're not entirely finished with these fowl matters, because for some software systems, it may beentirely appropriate to say that a penguin is a bird. In particular, if your application has much to do with beaksand wings and nothing to do with flying, the original hierarchy might work out just fine. Irritating though this mayseem, it's a simple reflection of the fact that there is no one ideal design for all software. The best designdepends on what the system is expected to do, both now and in the future (see Item M32). If your application hasno knowledge of flying and isn't expected to ever have any, making Penguin a derived class of Bird may be aperfectly valid design decision. In fact, it may be preferable to a decision that makes a distinction between flyingand non-flying birds, because such a distinction would be absent from the world you are trying to model. Addingsuperfluous classes to a hierarchy can be just as bad a design decision as having the wrong inheritancerelationships between classes.

There is another school of thought on how to handle what I call the "All birds can fly, penguins are birds,penguins can't fly, uh oh" problem. That is to redefine the fly function for penguins so that it generates a runtimeerror:

void error(const string& msg); // defined elsewhere

class Penguin: public Bird {

public:

virtual void fly() { error("Penguins can't fly!"); }

...

};

Interpreted languages such as Smalltalk tend to adopt this approach, but it's important to recognize that this sayssomething entirely different from what you might think. This does not say, "Penguins can't fly." This says,"Penguins can fly, but it's an error for them to try to do so."

How can you tell the difference? From the time at which the error is detected. The injunction, "Penguins can'tfly," can be enforced by compilers, but violations of the statement, "It's an error for penguins to try to fly," canbe detected only at runtime.

To express the constraint, "Penguins can't fly," you make sure that no such function is defined for Penguinobjects:

class Bird {

... // no fly function is

// declared

};

class NonFlyingBird: public Bird {

... // no fly function is

// declared

};

class Penguin: public NonFlyingBird {

... // no fly function is

// declared

};

If you try to make a penguin fly, compilers will reprimand you for your transgression: Penguin p;

p.fly(); // error!

Page 132: Effective C++ and More Effective C++

This is very different from the behavior you get if you use the Smalltalk approach. With that methodology,compilers won't say a word.

The C++ philosophy is fundamentally different from the Smalltalk philosophy, so you're better off doing thingsthe C++ way as long as you're programming in C++. In addition, there are certain technical advantages todetecting errors during compilation instead of at runtime ? see Item 46.

Perhaps you'll concede that your ornithological intuition may be lacking, but you can rely on your mastery ofelementary geometry, right? I mean, how complicated can rectangles and squares be?

Well, answer this simple question: should class Square publicly inherit from class Rectangle?

"Duh!" you say, "Of course it should! Everybody knows that a square is a rectangle, but generally not viceversa." True enough, at least in high school. But I don't think we're in high school anymore. Consider this code:

class Rectangle {

public:

virtual void setHeight(int newHeight);

virtual void setWidth(int newWidth);

virtual int height() const; // return current

virtual int width() const; // values

...

};

void makeBigger(Rectangle& r) // function to

{ // increase r's area

int oldHeight = r.height();

r.setWidth(r.width() + 10); // add 10 to r's width

assert(r.height() == oldHeight); // assert that r's

} // height is unchanged

Clearly, the assertion should never fail. makeBigger only changes r's width. Its height is never modified.

Now consider this code, which uses public inheritance to allow squares to be treated like rectangles: class Square: public Rectangle { ... };

Square s;

...

assert(s.width() == s.height()); // this must be true

// for all squares

Page 133: Effective C++ and More Effective C++

makeBigger(s); // by inheritance, s

// isa Rectangle, so

// we can increase its

// area

assert(s.width() == s.height()); // this must still be

// true for all squares

It's just as clear here as it was above that this last assertion should also never fail. By definition, the width of asquare is the same as its height.

But now we have a problem. How can we reconcile the following assertions?

Before calling makeBigger, s's height is the same as its width;

Inside makeBigger, s's width is changed, but its height is not;

After returning from makeBigger, s's height is again the same as its width. (Note that s is passed tomakeBigger by reference, so makeBigger modifies s itself, not a copy of s.)

Well?

Welcome to the wonderful world of public inheritance, where the instincts you've developed in other fields ofstudy ? including mathematics ? may not serve you as well as you expect. The fundamental difficulty in this caseis that something applicable to a rectangle (its width may be modified independently of its height) is notapplicable to a square (its width and height are constrained to be the same). But public inheritance asserts thateverything applicable to base class objects ? everything! ? is also applicable to derived class objects. In thecase of rectangles and squares (and a similar example involving sets and lists in Item 40), that assertion fails tohold, so using public inheritance to model their relationship is just plain wrong. Compilers will let you do it, ofcourse, but as we've just seen, that's no guarantee the code will behave properly. As every programmer mustlearn (some more often than others), just because the code compiles doesn't mean it will work.

Now, don't fret that the software intuition you've developed over the years will fail you as you approachobject-oriented design. That knowledge is still valuable, but now that you've added inheritance to your arsenalof design alternatives, you'll have to augment your intuition with new insights to guide you in inheritance'sproper application. In time, the notion of having Penguin inherit from Bird or Square inherit from Rectangle willgive you the same funny feeling you probably get now when somebody shows you a function several pages long.It's possible that it's the right way to approach things, it's just not very likely.

Of course, the isa relationship is not the only one that can exist between classes. Two other common inter-classrelationships are "has-a" and "is-implemented-in-terms-of." These relationships are considered in Items 40 and42. It's not uncommon for C++ designs to go awry because one of these other important relationships wasincorrectly modeled as isa, so you should make sure that you understand the differences between theserelationships and that you know how they are best modeled in C++.

Back to Inheritance and Object-Oriented Design Continue to Item 36: Differentiate between inheritance of interface and inheritance of implementation.

Page 134: Effective C++ and More Effective C++

Back to Item 35: Make sure public inheritance models "isa." Continue to Item 37: Never redefine an inherited nonvirtual function.

Item 36: Differentiate between inheritance of interface and inheritance of implementation.

The seemingly straightforward notion of (public) inheritance turns out, upon closer examination, to becomposed of two separable parts: inheritance of function interfaces and inheritance of function implementations.The difference between these two kinds of inheritance corresponds exactly to the difference between functiondeclarations and function definitions discussed in the Introduction to this book.

As a class designer, you sometimes want derived classes to inherit only the interface (declaration) of a memberfunction; sometimes you want derived classes to inherit both the interface and the implementation for a function,but you want to allow them to override the implementation you provide; and sometimes you want them to inheritboth interface and implementation without allowing them to override anything.

To get a better feel for the differences among these options, consider a class hierarchy for representinggeometric shapes in a graphics application:

class Shape {

public:

virtual void draw() const = 0;

virtual void error(const string& msg);

int objectID() const;

...

};

class Rectangle: public Shape { ... };

class Ellipse: public Shape { ... };

Shape is an abstract class; its pure virtual function draw marks it as such. As a result, clients cannot createinstances of the Shape class, only of the classes derived from it. Nonetheless, Shape exerts a strong influence onall classes that (publicly) inherit from it, because

Member function interfaces are always inherited. As explained in Item 35, public inheritance means isa,so anything that is true of a base class must also be true of its derived classes. Hence, if a function appliesto a class, it must also apply to its subclasses.

Three functions are declared in the Shape class. The first, draw, draws the current object on an implicit display.The second, error, is called by member functions if they need to report an error. The third, objectID, returns aunique integer identifier for the current object; Item 17 gives an example of how such a function might be used.Each function is declared in a different way: draw is a pure virtual function; error is a simple (impure?) virtualfunction; and objectID is a nonvirtual function. What are the implications of these different declarations?

Consider first the pure virtual function draw. The two most salient features of pure virtual functions are that they must be redeclared by any concrete class that inherits them, and they typically have no definition in abstractclasses. Put these two traits together, and you realize that

The purpose of declaring a pure virtual function is to have derived classes inherit a function interfaceonly.

This makes perfect sense for the Shape::draw function, because it is a reasonable demand that all Shape objectsmust be drawable, but the Shape class can provide no reasonable default implementation for that function. Thealgorithm for drawing an ellipse is very different from the algorithm for drawing a rectangle, for example. Agood way to interpret the declaration of Shape::draw is as saying to designers of subclasses, "You must providea draw function, but I have no idea how you're going to implement it."

Page 135: Effective C++ and More Effective C++

Incidentally, it is possible to provide a definition for a pure virtual function. That is, you could provide animplementation for Shape::draw, and C++ wouldn't complain, but the only way to call it would be to fullyspecify the call with the class name:

Shape *ps = new Shape; // error! Shape is abstract

Shape *ps1 = new Rectangle; // fine

ps1->draw(); // calls Rectangle::draw

Shape *ps2 = new Ellipse; // fine

ps2->draw(); // calls Ellipse::draw

ps1->Shape::draw(); // calls Shape::draw

ps2->Shape::draw(); // calls Shape::draw

Aside from helping impress fellow programmers at cocktail parties, knowledge of this feature is generally oflimited utility. As you'll see below, however, it can be employed as a mechanism for providing asafer-than-usual default implementation for simple (impure) virtual functions.

Sometimes it's useful to declare a class containing nothing but pure virtual functions. Such a Protocol class canprovide only function interfaces for derived classes, never implementations. Protocol classes are described in Item 34 and are mentioned again in Item 43.

The story behind simple virtual functions is a bit different from that behind pure virtuals. As usual, derivedclasses inherit the interface of the function, but simple virtual functions traditionally provide an implementationthat derived classes may or may not choose to override. If you think about this for a minute, you'll realize that

The purpose of declaring a simple virtual function is to have derived classes inherit a function interfaceas well as a default implementation.

In the case of Shape::error, the interface says that every class must support a function to be called when an erroris encountered, but each class is free to handle errors in whatever way it sees fit. If a class doesn't want to doanything special, it can just fall back on the default error-handling provided in the Shape class. That is, thedeclaration of Shape::error says to designers of subclasses, "You've got to support an error function, but if youdon't want to write your own, you can fall back on the default version in the Shape class."

It turns out that it can be dangerous to allow simple virtual functions to specify both a function declaration and adefault implementation. To see why, consider a hierarchy of airplanes for XYZ Airlines. XYZ has only twokinds of planes, the Model A and the Model B, and both are flown in exactly the same way. Hence, XYZ designsthe following hierarchy:

class Airport { ... }; // represents airports

class Airplane {

public:

virtual void fly(const Airport& destination);

...

};

void Airplane::fly(const Airport& destination)

{

default code for flying an airplane to

the given destination

}

Page 136: Effective C++ and More Effective C++

class ModelA: public Airplane { ... };

class ModelB: public Airplane { ... };

To express that all planes have to support a fly function, and in recognition of the fact that different models ofplane could, in principle, require different implementations for fly, Airplane::fly is declared virtual. However,in order to avoid writing identical code in the ModelA and ModelB classes, the default flying behavior isprovided as the body of Airplane::fly, which both ModelA and ModelB inherit.

This is a classic object-oriented design. Two classes share a common feature (the way they implement fly), sothe common feature is moved into a base class, and the feature is inherited by the two classes. This design makescommon features explicit, avoids code duplication, facilitates future enhancements, and eases long-termmaintenance ? all the things for which object-oriented technology is so highly touted. XYZ Airlines should beproud.

Now suppose that XYZ, its fortunes on the rise, decides to acquire a new type of airplane, the Model C. TheModel C differs from the Model A and the Model B. In particular, it is flown differently.

XYZ's programmers add the class for Model C to the hierarchy, but in their haste to get the new model intoservice, they forget to redefine the fly function:

class ModelC: public Airplane {

... // no fly function is

// declared

};

In their code, then, they have something akin to the following:

Airport JFK(...); // JFK is an airport in

// New York City

Airplane *pa = new ModelC;

...

pa->fly(JFK); // calls Airplane::fly!

This is a disaster: an attempt is being made to fly a ModelC object as if it were a ModelA or a ModelB. That'snot the kind of behavior that inspires confidence in the traveling public.

The problem here is not that Airplane::fly has default behavior, but that ModelC was allowed to inherit thatbehavior without explicitly saying that it wanted to. Fortunately, it's easy to offer default behavior to subclasses,but not give it to them unless they ask for it. The trick is to sever the connection between the interface of thevirtual function and its default implementation. Here's one way to do it:

class Airplane {

public:

virtual void fly(const Airport& destination) = 0;

...

protected:

void defaultFly(const Airport& destination);

};

void Airplane::defaultFly(const Airport& destination)

{

default code for flying an airplane to

Page 137: Effective C++ and More Effective C++

the given destination

}

Notice how Airplane::fly has been turned into a pure virtual function. That provides the interface for flying. Thedefault implementation is also present in the Airplane class, but now it's in the form of an independent function,defaultFly. Classes like ModelA and ModelB that want to use the default behavior simply make an inline call todefaultFly inside their body of fly (but see Item 33 for information on the interaction of inlining and virtualfunctions):

class ModelA: public Airplane {

public:

virtual void fly(const Airport& destination)

{ defaultFly(destination); }

...

};

class ModelB: public Airplane {

public:

virtual void fly(const Airport& destination)

{ defaultFly(destination); }

...

};

For the ModelC class, there is no possibility of accidentally inheriting the incorrect implementation of fly,because the pure virtual in Airplane forces ModelC to provide its own version of fly.

class ModelC: public Airplane {

public:

virtual void fly(const Airport& destination);

...

};

void ModelC::fly(const Airport& destination)

{

code for flying a ModelC airplane to the given destination

}

This scheme isn't foolproof (programmers can still copy-and-paste themselves into trouble), but it's morereliable than the original design. As for Airplane::defaultFly, it's protected because it's truly an implementationdetail of Airplane and its derived classes. Clients using airplanes should care only that they can be flown, nothow the flying is implemented.

It's also important that Airplane::defaultFly is a nonvirtual function. This is because no subclass should redefinethis function, a truth to which Item 37 is devoted. If defaultFly were virtual, you'd have a circular problem: whatif some subclass forgets to redefine defaultFly when it's supposed to?

Some people object to the idea of having separate functions for providing interface and default implementation,such as fly and defaultFly above. For one thing, they note, it pollutes the class namespace with a proliferation ofclosely-related function names. Yet they still agree that interface and default implementation should beseparated. How do they resolve this seeming contradiction? By taking advantage of the fact that pure virtualfunctions must be redeclared in subclasses, but they may also have implementations of their own. Here's how theAirplane hierarchy could take advantage of the ability to define a pure virtual function:

class Airplane {

public:

virtual void fly(const Airport& destination) = 0;

Page 138: Effective C++ and More Effective C++

...

};

void Airplane::fly(const Airport& destination)

{

default code for flying an airplane to

the given destination

}

class ModelA: public Airplane {

public:

virtual void fly(const Airport& destination)

{ Airplane::fly(destination); }

...

};

class ModelB: public Airplane {

public:

virtual void fly(const Airport& destination)

{ Airplane::fly(destination); }

...

};

class ModelC: public Airplane {

public:

virtual void fly(const Airport& destination);

...

};

void ModelC::fly(const Airport& destination)

{

code for flying a ModelC airplane to the given destination

}

This is almost exactly the same design as before, except that the body of the pure virtual function Airplane::flytakes the place of the independent function Airplane::defaultFly. In essence, fly has been broken into its twofundamental components. Its declaration specifies its interface (which derived classes must use), while itsdefinition specifies its default behavior (which derived classes may use, but only if they explicitly request it). Inmerging fly and defaultFly, however, you've lost the ability to give the two functions different protection levels:the code that used to be protected (by being in defaultFly) is now public (because it's in fly).

Finally, we come to Shape's nonvirtual function, objectID. When a member function is nonvirtual, it's notsupposed to behave differently in derived classes. In fact, a nonvirtual member function specifies an invariantover specialization, because it identifies behavior that is not supposed to change, no matter how specialized aderived class becomes. As such,

The purpose of declaring a nonvirtual function is to have derived classes inherit a function interface aswell as a mandatory implementation.

You can think of the declaration for Shape::objectID as saying, "Every Shape object has a function that yieldsan object identifier, and that object identifier is always computed in the same way. That way is determined bythe definition of Shape::objectID, and no derived class should try to change how it's done." Because a nonvirtualfunction identifies an invariant over specialization, it should never be redefined in a subclass, a point that isdiscussed in detail in Item 37.

Page 139: Effective C++ and More Effective C++

The differences in declarations for pure virtual, simple virtual, and nonvirtual functions allow you to specifywith precision what you want derived classes to inherit: interface only, interface and a default implementation,or interface and a mandatory implementation, respectively. Because these different types of declarations meanfundamentally different things, you must choose carefully among them when you declare your member functions.If you do, you should avoid the two most common mistakes made by inexperienced class designers.

The first mistake is to declare all functions nonvirtual. That leaves no room for specialization in derivedclasses; nonvirtual destructors are particularly problematic (see Item 14). Of course, it's perfectly reasonable todesign a class that is not intended to be used as a base class. Item M34 gives an example of a case where youmight want to. In that case, a set of exclusively nonvirtual member functions is appropriate. Too often, however,such classes are declared either out of ignorance of the differences between virtual and nonvirtual functions oras a result of an unsubstantiated concern over the performance cost of virtual functions (see Item M24). The factof the matter is that almost any class that's to be used as a base class will have virtual functions (again, see Item14).

If you're concerned about the cost of virtual functions, allow me to bring up the rule of 80-20 (see Item M16),which states that in a typical program, 80 percent of the runtime will be spent executing just 20 percent of thecode. This rule is important, because it means that, on average, 80 percent of your function calls can be virtualwithout having the slightest detectable impact on your program's overall performance. Before you go grayworrying about whether you can afford the cost of a virtual function, then, take the simple precaution of makingsure that you're focusing on the 20 percent of your program where the decision might really make a difference.

The other common problem is to declare all member functions virtual. Sometimes this is the right thing to do ?witness Protocol classes (see Item 34), for example. However, it can also be a sign of a class designer wholacks the backbone to take a firm stand. Some functions should not be redefinable in derived classes, andwhenever that's the case, you've got to say so by making those functions nonvirtual. It serves no one to pretendthat your class can be all things to all people if they'll just take the time to redefine all your functions. Rememberthat if you have a base class B, a derived class D, and a member function mf, then each of the following calls tomf must work properly:

D *pd = new D;

B *pb = pd;

pb->mf(); // call mf through a

// pointer-to-base

pd->mf(); // call mf through a

// pointer-to-derived

Sometimes, you must make mf a nonvirtual function to ensure that everything behaves the way it's supposed to(see Item 37). If you have an invariant over specialization, don't be afraid to say so!

Back to Item 35: Make sure public inheritance models "isa." Continue to Item 37: Never redefine an inherited nonvirtual function.

Page 140: Effective C++ and More Effective C++

Back to Item 36: Differentiate between inheritance of interface and inheritance of implementation. Continue to Item 38: Never redefine an inherited default parameter value.

Item 37: Never redefine an inherited nonvirtual function.

There are two ways of looking at this issue: the theoretical way and the pragmatic way. Let's start with thepragmatic way. After all, theoreticians are used to being patient.

Suppose I tell you that a class D is publicly derived from a class B and that there is a public member function mfdefined in class B. The parameters and return type of mf are unimportant, so let's just assume they're both void.In other words, I say this:

class B {

public:

void mf();

...

};

class D: public B { ... };

Even without knowing anything about B, D, or mf, given an object x of type D,

D x; // x is an object of type D

you would probably be quite surprised if this,

B *pB = &x; // get pointer to x

pB->mf(); // call mf through pointer

behaved differently from this:

D *pD = &x; // get pointer to x

pD->mf(); // call mf through pointer

That's because in both cases you're invoking the member function mf on the object x. Because it's the samefunction and the same object in both cases, it should behave the same way, right?

Right, it should. But it might not. In particular, it won't if mf is nonvirtual and D has defined its own version ofmf:

class D: public B {

public:

void mf(); // hides B::mf; see Item 50

...

};

pB->mf(); // calls B::mf

pD->mf(); // calls D::mf

The reason for this two-faced behavior is that nonvirtual functions like B::mf and D::mf are statically bound(see Item 38). That means that because pB is declared to be of type pointer-to-B, nonvirtual functions invoked

Page 141: Effective C++ and More Effective C++

through pB will always be those defined for class B, even if pB points to an object of a class derived from B, asit does in this example.

Virtual functions, on the other hand, are dynamically bound (again, see Item 38), so they don't suffer from thisproblem. If mf were a virtual function, a call to mf through either pB or pD would result in an invocation ofD::mf, because what pB and pD really point to is an object of type D.

The bottom line, then, is that if you are writing class D and you redefine a nonvirtual function mf that you inheritfrom class B, D objects will likely exhibit schizophrenic behavior. In particular, any given D object may act likeeither a B or a D when mf is called, and the determining factor will have nothing to do with the object itself, butwith the declared type of the pointer that points to it. References exhibit the same baffling behavior as dopointers.

So much for the pragmatic argument. What you want now, I know, is some kind of theoretical justification for notredefining inherited nonvirtual functions. I am pleased to oblige.

Item 35 explains that public inheritance means isa, and Item 36 describes why declaring a nonvirtual function ina class establishes an invariant over specialization for that class. If you apply these observations to the classesB and D and to the nonvirtual member function B::mf, then

Everything that is applicable to B objects is also applicable to D objects, because every D object isa Bobject;

Subclasses of B must inherit both the interface and the implementation of mf, because mf is nonvirtual inB.

Now, if D redefines mf, there is a contradiction in your design. If D really needs to implement mf differentlyfrom B, and if every B object ? no matter how specialized ? really has to use the B implementation for mf, thenit's simply not true that every D isa B. In that case, D shouldn't publicly inherit from B. On the other hand, if D really has to publicly inherit from B, and if D really needs to implement mf differently from B, then it's just nottrue that mf reflects an invariant over specialization for B. In that case, mf should be virtual. Finally, if every D really isa B, and if mf really corresponds to an invariant over specialization for B, then D can't honestly need toredefine mf, and it shouldn't try to do so.

Regardless of which argument applies, something has to give, and under no conditions is it the prohibition onredefining an inherited nonvirtual function.

Back to Item 36: Differentiate between inheritance of interface and inheritance of implementation. Continue to Item 38: Never redefine an inherited default parameter value.

Page 142: Effective C++ and More Effective C++

Back to Item 37: Never redefine an inherited nonvirtual function. Continue to Item 39: Avoid casts down the inheritance hierarchy.

Item 38: Never redefine an inherited default parameter value.

Let's simplify this discussion right from the start. A default parameter can exist only as part of a function, andyou can inherit only two kinds of functions: virtual and nonvirtual. Therefore, the only way to redefine a defaultparameter value is to redefine an inherited function. However, it's always a mistake to redefine an inheritednonvirtual function (see Item 37), so we can safely limit our discussion here to the situation in which you inherita virtual function with a default parameter value.

That being the case, the justification for this Item becomes quite straightforward: virtual functions aredynamically bound, but default parameter values are statically bound.

What's that? You say you're not up on the latest object-oriented lingo, or perhaps the difference between staticand dynamic binding has slipped your already overburdened mind? Let's review, then.

An object's static type is the type you declare it to have in the program text. Consider this class hierarchy: enum ShapeColor { RED, GREEN, BLUE };

// a class for geometric shapes

class Shape {

public:

// all shapes must offer a function to draw themselves

virtual void draw(ShapeColor color = RED) const = 0;

...

};

class Rectangle: public Shape {

public:

// notice the different default parameter value - bad!

virtual void draw(ShapeColor color = GREEN) const;

...

};

class Circle: public Shape {

public:

virtual void draw(ShapeColor color) const;

...

};

Graphically, it looks like this:

Now consider these pointers:

Page 143: Effective C++ and More Effective C++

Shape *ps; // static type = Shape*

Shape *pc = new Circle; // static type = Shape*

Shape *pr = new Rectangle; // static type = Shape*

In this example, ps, pc, and pr are all declared to be of type pointer-to-Shape, so they all have that as their statictype. Notice that it makes absolutely no difference what they're really pointing to ? their static type is Shape*regardless.

An object's dynamic type is determined by the type of the object to which it currently refers. That is, its dynamictype indicates how it will behave. In the example above, pc's dynamic type is Circle*, and pr's dynamic type isRectangle*. As for ps, it doesn't really have a dynamic type, because it doesn't refer to any object (yet).

Dynamic types, as their name suggests, can change as a program runs, typically through assignments:

ps = pc; // ps's dynamic type is

// now Circle*

ps = pr; // ps's dynamic type is

// now Rectangle*

Virtual functions are dynamically bound, meaning that the particular function called is determined by thedynamic type of the object through which it's invoked:

pc->draw(RED); // calls Circle::draw(RED)

pr->draw(RED); // calls Rectangle::draw(RED)

This is all old hat, I know; you surely understand virtual functions. (If you'd like to understand how they'reimplemented, turn to Item M24.) The twist comes in when you consider virtual functions with default parametervalues, because, as I said above, virtual functions are dynamically bound, but default parameters are staticallybound. That means that you may end up invoking a virtual function defined in a derived class but using a defaultparameter value from a base class:

pr->draw(); // calls Rectangle::draw(RED)!

In this case, pr's dynamic type is Rectangle*, so the Rectangle virtual function is called, just as you wouldexpect. In Rectangle::draw, the default parameter value is GREEN. Because pr's static type is Shape*, however,the default parameter value for this function call is taken from the Shape class, not the Rectangle class! Theresult is a call consisting of a strange and almost certainly unanticipated combination of the declarations fordraw in both the Shape and Rectangle classes. Trust me when I tell you that you don't want your software tobehave this way, or at least believe me when I tell you that your clients won't want your software to behave thisway.

Needless to say, the fact that ps, pc, and pr are pointers is of no consequence in this matter. Were theyreferences, the problem would persist. The only important things are that draw is a virtual function, and one ofits default parameter values is redefined in a subclass.

Why does C++ insist on acting in this perverse manner? The answer has to do with runtime efficiency. If defaultparameter values were dynamically bound, compilers would have to come up with a way of determining theappropriate default value(s) for parameters of virtual functions at runtime, which would be slower and morecomplicated than the current mechanism of determining them during compilation. The decision was made to erron the side of speed and simplicity of implementation, and the result is that you now enjoy execution behaviorthat is efficient, but, if you fail to heed the advice of this Item, confusing.

Back to Item 37: Never redefine an inherited nonvirtual function. Continue to Item 39: Avoid casts down the inheritance hierarchy.

Page 144: Effective C++ and More Effective C++

Back to Item 38: Never redefine an inherited default parameter value. Continue to Item 40: Model "has-a" or "is-implemented-in-terms-of" through layering.

Item 39: Avoid casts down the inheritance hierarchy.

In these tumultuous economic times, it's a good idea to keep an eye on our financial institutions, so consider aProtocol class (see Item 34) for bank accounts:

class Person { ... };

class BankAccount {

public:

BankAccount(const Person *primaryOwner,

const Person *jointOwner);

virtual ~BankAccount();

virtual void makeDeposit(double amount) = 0;

virtual void makeWithdrawal(double amount) = 0;

virtual double balance() const = 0;

...

};

Many banks now offer a bewildering array of account types, but to keep things simple, let's assume there is onlyone type of bank account, namely, a savings account:

class SavingsAccount: public BankAccount {

public:

SavingsAccount(const Person *primaryOwner,

const Person *jointOwner);

~SavingsAccount();

void creditInterest(); // add interest to account

...

};

This isn't much of a savings account, but then again, what is these days? At any rate, it's enough for ourpurposes.

A bank is likely to keep a list of all its accounts, perhaps implemented via the list class template from thestandard library (see Item 49). Suppose this list is imaginatively named allAccounts:

list<BankAccount*> allAccounts; // all accounts at the

// bank

Like all standard containers, lists store copies of the things placed into them, so to avoid storing multiple copiesof each BankAccount, the bank has decided to have allAccounts hold pointers to BankAccounts instead ofBankAccounts themselves.

Now imagine you're supposed to write the code to iterate over all the accounts, crediting the interest due eachone. You might try this,

// a loop that won't compile (see below if you've never

// seen code using "iterators" before)

for (list<BankAccount*>::iterator p = allAccounts.begin();

p != allAccounts.end();

++p) {

Page 145: Effective C++ and More Effective C++

(*p)->creditInterest(); // error!

}

but your compilers would quickly bring you to your senses: allAccounts contains pointers to BankAccountobjects, not to SavingsAccount objects, so each time around the loop, p points to a BankAccount. That makes thecall to creditInterest invalid, because creditInterest is declared only for SavingsAccount objects, notBankAccounts.

If "list<BankAccount*>::iterator p = allAccounts.begin()" looks to you more like transmission line noise thanC++, you've apparently never had the pleasure of meeting the container class templates in the standard library.This part of the library is usually known as the Standard Template Library (the "STL"), and you can get anoverview of it in Items 49 and M35. For the time being, all you need to know is that the variable p acts like apointer that loops through the elements of allAccounts from beginning to end. That is, p acts as if its type wereBankAccount** and the list elements were stored in an array.

It's frustrating that the loop above won't compile. Sure, allAccounts is defined as holding BankAccount*s, butyou know that it actually holds SavingsAccount*s in the loop above, because SavingsAccount is the only classthat can be instantiated. Stupid compilers! You decide to tell them what you know to be obvious and what theyare too dense to figure out on their own: allAccounts really contains SavingsAccount*s:

// a loop that will compile, but that is nonetheless evil

for (list<BankAccount*>::iterator p = allAccounts.begin();

p != allAccounts.end();

++p) {

static_cast<SavingsAccount*>(*p)->creditInterest();

}

All your problems are solved! Solved clearly, solved elegantly, solved concisely, all by the simple use of acast. You know what type of pointer allAccounts really holds, your dopey compilers don't, so you use a cast totell them. What could be more logical?

There is a biblical analogy I'd like to draw here. Casts are to C++ programmers what the apple was to Eve.

This kind of cast ? from a base class pointer to a derived class pointer ? is called a downcast, because you'recasting down the inheritance hierarchy. In the example you just looked at, downcasting happens to work, but itleads to a maintenance nightmare, as you will soon see.

But back to the bank. Buoyed by the success of its savings accounts, let's suppose the bank decides to offerchecking accounts, too. Furthermore, assume that checking accounts also bear interest, just like savingsaccounts:

class CheckingAccount: public BankAccount {

public:

void creditInterest(); // add interest to account

...

};

Needless to say, allAccounts will now be a list containing pointers to both savings and checking accounts.Suddenly, the interest-crediting loop you wrote above is in serious trouble.

Your first problem is that it will continue to compile without your changing it to reflect the existence ofCheckingAccount objects. This is because compilers will foolishly believe you when you tell them (through thestatic_cast) that *p really points to a SavingsAccount*. After all, you're the boss. That's Maintenance NightmareNumber One. Maintenance Nightmare Number Two is what you're tempted to do to fix the problem, which istypically to write code like this:

Page 146: Effective C++ and More Effective C++

for (list<BankAccount*>::iterator p = allAccounts.begin();

p != allAccounts.end();

++p) {

if (*p points to a SavingsAccount)

static_cast<SavingsAccount*>(*p)->creditInterest();

else

static_cast<CheckingAccount*>(*p)->creditInterest();

}

Anytime you find yourself writing code of the form, "if the object is of type T1, then do something, but if it's oftype T2, then do something else," slap yourself. That isn't The C++ Way. Yes, it's a reasonable strategy in C, inPascal, even in Smalltalk, but not in C++. In C++, you use virtual functions.

Remember that with a virtual function, compilers are responsible for making sure that the right function iscalled, depending on the type of the object being used. Don't litter your code with conditionals or switchstatements; let your compilers do the work for you. Here's how:

class BankAccount { ... }; // as above

// new class representing accounts that bear interest

class InterestBearingAccount: public BankAccount {

public:

virtual void creditInterest() = 0;

...

};

class SavingsAccount: public InterestBearingAccount {

... // as above

};

class CheckingAccount: public InterestBearingAccount {

... // as above

};

Graphically, it looks like this:

Page 147: Effective C++ and More Effective C++

Because both savings and checking accounts earn interest, you'd naturally like to move that common behavior upinto a common base class. However, under the assumption that not all accounts in the bank will necessarily bearinterest (certainly a valid assumption in my experience), you can't move it into the BankAccount class. As aresult, you've introduced a new subclass of BankAccount called InterestBearingAccount, and you've madeSavingsAccount and CheckingAccount inherit from it.

The fact that both savings and checking accounts bear interest is indicated by the InterestBearingAccount purevirtual function creditInterest, which is presumably redefined in its subclasses SavingsAccount andCheckingAccount.

This new class hierarchy allows you to rewrite your loop as follows: // better, but still not perfect

for (list<BankAccount*>::iterator p = allAccounts.begin();

p != allAccounts.end();

++p) {

static_cast<InterestBearingAccount*>(*p)->creditInterest();

}

Although this loop still contains a nasty little cast, it's much more robust than it used to be, because it willcontinue to work even if new subclasses of InterestBearingAccount are added to your application.

To get rid of the cast entirely, you must make some additional changes to your design. One approach is to tightenup the specification of your list of accounts. If you could get a list of InterestBearingAccount objects instead ofBankAccount objects, everything would be peachy:

// all interest-bearing accounts in the bank

list<InterestBearingAccount*> allIBAccounts;

// a loop that compiles and works, both now and forever

for (list<InterestBearingAccount*>::iterator p =

allIBAccounts.begin();

p != allIBAccounts.end();

++p) {

Page 148: Effective C++ and More Effective C++

(*p)->creditInterest();

}

If getting a more specialized list isn't an option, it might make sense to say that the creditInterest operationapplies to all bank accounts, but that for non-interest-bearing accounts, it's just a no-op. That could be expressedthis way:

class BankAccount {

public:

virtual void creditInterest() {}

...

};

class SavingsAccount: public BankAccount { ... };

class CheckingAccount: public BankAccount { ... };

list<BankAccount*> allAccounts;

// look ma, no cast!

for (list<BankAccount*>::iterator p = allAccounts.begin();

p != allAccounts.end();

++p) {

(*p)->creditInterest();

}

Notice that the virtual function BankAccount::creditInterest provides an empty default implementation. This is aconvenient way to specify that its behavior is a no-op by default, but it can lead to unforeseen difficulties in itsown right. For the inside story on why, as well as how to eliminate the danger, consult Item 36. Notice also thatcreditInterest is (implicitly) an inline function. There's nothing wrong with that, but because it's also virtual, theinline directive will probably be ignored. Item 33 explains why.

As you have seen, downcasts can be eliminated in a number of ways. The best way is to replace such casts withcalls to virtual functions, possibly also making each virtual function a no-op for any classes to which it doesn'ttruly apply. A second method is to tighten up the typing so that there is no ambiguity between the declared typeof a pointer and the pointer type that you know is really there. Whatever the effort required to get rid ofdowncasts, it's effort well spent, because downcasts are ugly and error-prone, and they lead to code that'sdifficult to understand, enhance, and maintain (see Item M32).

What I've just written is the truth and nothing but the truth. It is not, however, the whole truth. There areoccasions when you really do have to perform a downcast.

For example, suppose you faced the situation we considered at the outset of this Item, i.e., allAccounts holdsBankAccount pointers, creditInterest is defined only for SavingsAccount objects, and you must write a loop tocredit interest to every account. Further suppose that all those things are beyond your control; you can't changethe definitions for BankAccount, SavingsAccount, or allAccounts. (This would happen if they were defined in alibrary to which you had read-only access.) If that were the case, you'd have to use downcasting, no matter howdistasteful you found the idea.

Nevertheless, there is a better way to do it than through a raw cast such as we saw above. The better way iscalled "safe downcasting," and it's implemented via C++'s dynamic_cast operator (see Item M2). When you usedynamic_cast on a pointer, the cast is attempted, and if it succeeds (i.e., if the dynamic type of the pointer (see Item 38) is consistent with the type to which it's being cast), a valid pointer of the new type is returned. If thedynamic_cast fails, the null pointer is returned.

Here's the banking example with safe downcasting added:

Page 149: Effective C++ and More Effective C++

class BankAccount { ... }; // as at the beginning of

// this Item

class SavingsAccount: // ditto

public BankAccount { ... };

class CheckingAccount: // ditto again

public BankAccount { ... };

list<BankAccount*> allAccounts; // this should look

// familiar...

void error(const string& msg); // error-handling function;

// see below

// well, ma, at least the casts are safe...

for (list<BankAccount*>::iterator p = allAccounts.begin();

p != allAccounts.end();

++p) {

// try safe-downcasting *p to a SavingsAccount*; see

// below for information on the definition of psa

if (SavingsAccount *psa =

dynamic_cast<SavingsAccount*>(*p)) {

psa->creditInterest();

}

// try safe-downcasting it to a CheckingAccount

else if (CheckingAccount *pca =

dynamic_cast<CheckingAccount*>(*p)) {

pca->creditInterest();

}

// uh oh ? unknown account type

else {

error("Unknown account type!");

}

}

This scheme is far from ideal, but at least you can detect when your downcasts fail, something that's impossiblewithout the use of dynamic_cast. Note, however, that prudence dictates you also check for the case where all thedowncasts fail. That's the purpose of the final else clause in the code above. With virtual functions, there'd be noneed for such a test, because every virtual call must resolve to some function. When you start downcasting,however, all bets are off. If somebody added a new type of account to the hierarchy, for example, but failed toupdate the code above, all the downcasts would fail. That's why it's important you handle that possibility. In alllikelihood, it's not supposed to be the case that all the casts can fail, but when you allow downcasting, bad thingsstart to happen to good programmers.

Did you check your glasses in a panic when you noticed what looks like variable definitions in the conditions ofthe if statements above? If so, worry not; your vision's fine. The ability to define such variables was added to thelanguage at the same time as dynamic_cast. This feature lets you write neater code, because you don't reallyneed psa or pca unless the dynamic_casts that initialize them succeed, and with the new syntax, you don't have todefine those variables outside the conditionals containing the casts. (Item 32 explains why you generally want toavoid superfluous variable definitions, anyway.) If your compilers don't yet support this new way of definingvariables, you can do it the old way:

for (list<BankAccount*>::iterator p = allAccounts.begin();

p != allAccounts.end();

++p) {

Page 150: Effective C++ and More Effective C++

SavingsAccount *psa; // traditional definition

CheckingAccount *pca; // traditional definition

if (psa = dynamic_cast<SavingsAccount*>(*p)) {

psa->creditInterest();

}

else if (pca = dynamic_cast<CheckingAccount*>(*p)) {

pca->creditInterest();

}

else {

error("Unknown account type!");

}

}

In the grand scheme of things, of course, where you place your definitions for variables like psa and pca is oflittle consequence. The important thing is this: the if-then-else style of programming that downcasting invariablyleads to is vastly inferior to the use of virtual functions, and you should reserve it for situations in which youtruly have no alternative. With any luck, you will never face such a bleak and desolate programming landscape.

Back to Item 38: Never redefine an inherited default parameter value. Continue to Item 40: Model "has-a" or "is-implemented-in-terms-of" through layering.

Page 151: Effective C++ and More Effective C++

Back to Item 39: Avoid casts down the inheritance hierarchy. Continue to Item 41: Differentiate between inheritance and templates.

Item 40: Model "has-a" or "is-implemented-in-terms-of" through layering.

Layering is the process of building one class on top of another class by having the layering class contain anobject of the layered class as a data member. For example:

class Address { ... }; // where someone lives

class PhoneNumber { ... };

class Person {

public:

...

private:

string name; // layered object

Address address; // ditto

PhoneNumber voiceNumber; // ditto

PhoneNumber faxNumber; // ditto

};

In this example, the Person class is said to be layered on top of the string, Address, and PhoneNumber classes,because it contains data members of those types. The term layering has lots of synonyms. It's also known ascomposition, containment, and embedding.

Item 35 explains that public inheritance means "isa." In contrast, layering means either "has-a" or"is-implemented-in-terms-of."

The Person class above demonstrates the has-a relationship. A Person object has a name, an address, andtelephone numbers for voice and FAX communication. You wouldn't say that a person is a name or that a personis an address. You would say that a person has a name and has an address, etc. Most people have little difficultywith this distinction, so confusion between the roles of isa and has-a is relatively rare.

Somewhat more troublesome is the difference between isa and is-implemented-in-terms-of. For example,suppose you need a template for classes representing sets of arbitrary objects, i.e., collections withoutduplicates. Because reuse is a wonderful thing, and because you wisely read Item 49's overview of the standardC++ library, your first instinct is to employ the library's set template. After all, why write a new template whenyou can use an established one written by somebody else?

As you delve into set's documentation, however, you discover a limitation your application can't live with: a setrequires that the elements contained within it be totally ordered, i.e., for every pair of objects a and b in the set,it must be possible to determine whether a<b or b<a. For many types, this requirement is easy to satisfy, andhaving a total ordering among objects allows set to offer certain attractive guarantees regarding its performance.(See Item 49 for more on performance guarantees in the standard library.) Your need, however, is for somethingmore general: a set-like class where objects need not be totally ordered, they need only be what the °C++standard colorfully terms "EqualityComparable": it's possible to determine whether a==b for objects a and b ofthe same type. This more modest requirement is better suited to types representing things like colors. Is red lessthan green or is green less than red? For your application, it seems you'll need to write your own template afterall.

Still, reuse is a wonderful thing. Being the data structure maven you are, you know that of the nearly limitlesschoices for implementing sets, one particularly simple way is to employ linked lists. But guess what? The listtemplate (which generates linked list classes) is just sitting there in the standard library! You decide to (re)useit.

In particular, you decide to have your nascent Set template inherit from list. That is, Set<T> will inherit fromlist<T>. After all, in your implementation, a Set object will in fact be a list object. You thus declare your Settemplate like this:

Page 152: Effective C++ and More Effective C++

// the wrong way to use list for Set

template<class T>

class Set: public list<T> { ... };

Everything may seem fine and dandy at this point, but in fact there is something quite wrong. As Item 35explains, if D isa B, everything true of B is also true of D. However, a list object may contain duplicates, so ifthe value 3051 is inserted into a list<int> twice, that list will contain two copies of 3051. In contrast, a Set maynot contain duplicates, so if the value 3051 is inserted into a Set<int> twice, the set contains only one copy ofthe value. It is thus a vicious lie that a Set isa list, because some of the things that are true for list objects are nottrue for Set objects.

Because the relationship between these two classes isn't isa, public inheritance is the wrong way to model thatrelationship. The right way is to realize that a Set object can be implemented in terms of a list object:

// the right way to use list for Set

template<class T>

class Set {

public:

bool member(const T& item) const;

void insert(const T& item);

void remove(const T& item);

int cardinality() const;

private:

list<T> rep; // representation for a set

};

Set's member functions can lean heavily on functionality already offered by list and other parts of the standardlibrary, so the implementation is neither difficult to write nor thrilling to read:

template<class T>

bool Set<T>::member(const T& item) const

{ return find(rep.begin(), rep.end(), item) != rep.end(); }

template<class T>

void Set<T>::insert(const T& item)

{ if (!member(item)) rep.push_back(item); }

template<class T>

void Set<T>::remove(const T& item)

{

list<T>::iterator it =

find(rep.begin(), rep.end(), item);

if (it != rep.end()) rep.erase(it);

}

template<class T>

int Set<T>::cardinality() const

{ return rep.size(); }

These functions are simple enough that they make reasonable candidates for inlining, though I know you'd wantto review the discussion in Item 33 before making any firm inlining decisions. (In the code above, functions likefind, begin, end, push_back, etc., are part of the standard library's framework for working with containertemplates like list. You'll find an overview of this framework in Item 49 and M35.)

It's worth remarking that the Set class interface fails the test of being complete and minimal (see Item 18). Interms of completeness, the primary omission is that of a way to iterate over the contents of a set, something thatmight well be necessary for many applications (and that is provided by all members of the standard library,including set). An additional drawback is that Set fails to follow the container class conventions embraced by

Page 153: Effective C++ and More Effective C++

the standard library (see Items 49 and M35), and that makes it more difficult to take advantage of other parts ofthe library when working with Sets.

Nits about Set's interface, however, shouldn't be allowed to overshadow what Set got indisputably right: therelationship between Set and list. That relationship is not isa (though it initially looked like it might be), it's"is-implemented-in-terms-of," and the use of layering to implement that relationship is something of which anyclass designer may be justly proud.

Incidentally, when you use layering to relate two classes, you create a compile-time dependency between thoseclasses. For information on why this should concern you, as well as what you can do to allay your worries, turnto Item 34.

Back to Item 39: Avoid casts down the inheritance hierarchy. Continue to Item 41: Differentiate between inheritance and templates.

Page 154: Effective C++ and More Effective C++

Back to Item 40: Differentiate between inheritance and templates. Continue to Item 42: Use private inheritance judiciously.

Item 41: Differentiate between inheritance and templates.

Consider the following two design problems: Being a devoted student of Computer Science, you want to create classes representing stacks of objects.

You'll need several different classes, because each stack must be homogeneous, i.e., it must have only asingle type of object in it. For example, you might have a class for stacks of ints, a second class for stacksof strings, a third for stacks of stacks of strings, etc. You're interested only in supporting a minimalinterface to the class (see Item 18), so you'll limit your operations to stack creation, stack destruction,pushing objects onto the stack, popping objects off the stack, and determining whether the stack is empty.For this exercise, you'll ignore the classes in the standard library (including stack ? see Item 49), becauseyou crave the experience of writing the code yourself. Reuse is a wonderful thing, but when your goal is adeep understanding of how something works, there's nothing quite like diving in and getting your handsdirty.

Being a devoted feline aficionado, you want to design classes representing cats. You'll need severaldifferent classes, because each breed of cat is a little different. Like all objects, cats can be created anddestroyed, but, as any cat-lover knows, the only other things cats do are eat and sleep. However, eachbreed of cat eats and sleeps in its own endearing way.

These two problem specifications sound similar, yet they result in utterly different software designs. Why?

The answer has to do with the relationship between each class's behavior and the type of object beingmanipulated. With both stacks and cats, you're dealing with a variety of different types (stacks containing objectsof type T, cats of breed T), but the question you must ask yourself is this: does the type T affect the behavior ofthe class? If T does not affect the behavior, you can use a template. If T does affect the behavior, you'll needvirtual functions, and you'll therefore use inheritance.

Here's how you might define a linked-list implementation of a Stack class, assuming that the objects to bestacked are of type T:

class Stack {

public:

Stack();

~Stack();

void push(const T& object);

T pop();

bool empty() const; // is stack empty?

private:

struct StackNode { // linked list node

T data; // data at this node

StackNode *next; // next node in list

// StackNode constructor initializes both fields

StackNode(const T& newData, StackNode *nextNode)

: data(newData), next(nextNode) {}

};

StackNode *top; // top of stack

Stack(const Stack& rhs); // prevent copying and

Stack& operator=(const Stack& rhs); // assignment (see Item 27)

};

Stack objects would thus build data structures that look like this:

Page 155: Effective C++ and More Effective C++

The linked list itself is made up of StackNode objects, but that's an implementation detail of the Stack class, soStackNode has been declared a private type of Stack. Notice that StackNode has a constructor to make sure allits fields are initialized properly. Just because you can write linked lists in your sleep is no reason to omittechnological advances such as constructors.

Here's a reasonable first cut at how you might implement the Stack member functions. As with many prototypeimplementations (and far too much production software), there's no checking for errors, because in aprototypical world, nothing ever goes wrong.

Stack::Stack(): top(0) {} // initialize top to null

void Stack::push(const T& object)

{

top = new StackNode(object, top); // put new node at

} // front of list

Page 156: Effective C++ and More Effective C++

T Stack::pop()

{

StackNode *topOfStack = top; // remember top node

top = top->next;

T data = topOfStack->data; // remember node data

delete topOfStack;

return data;

}

Stack::~Stack() // delete all in stack

{

while (top) {

StackNode *toDie = top; // get ptr to top node

top = top->next; // move to next node

delete toDie; // delete former top node

}

}

bool Stack::empty() const

{ return top == 0; }

There's nothing riveting about these implementations. In fact, the only interesting thing about them is this: youare able to write each member function knowing essentially nothing about T. (You assume you can call T's copyconstructor, but, as Item 45 explains, that's a pretty reasonable assumption.) The code you write for construction,destruction, pushing, popping, and determining whether the stack is empty is the same, no matter what T is.Except for the assumption that you can call T's copy constructor, the behavior of a stack does not depend on T inany way. That's the hallmark of a template class: the behavior doesn't depend on the type.

Turning your Stack class into a template, by the way, is so simple, even °Dilbert's pointy-haired boss could doit:

template<class T> class Stack {

... // exactly the same as above

};

But on to cats. Why won't templates work with cats?

Reread the specification and note the requirement that "each breed of cat eats and sleeps in its own endearingway." That means you're going to have to implement different behavior for each type of cat. You can't just writea single function to handle all cats, all you can do is specify an interface for a function that each type of cat mustimplement. Aha! The way to propagate a function interface only is to declare a pure virtual function (see Item36):

class Cat {

public:

virtual ~Cat(); // see Item 14

virtual void eat() = 0; // all cats eat

virtual void sleep() = 0; // all cats sleep

};

Subclasses of Cat ? say, Siamese and BritishShortHairedTabby ? must of course redefine the eat and sleepfunction interfaces they inherit:

class Siamese: public Cat {

Page 157: Effective C++ and More Effective C++

public:

void eat();

void sleep();

...

};

class BritishShortHairedTabby: public Cat {

public:

void eat();

void sleep();

...

};

Okay, you now know why templates work for the Stack class and why they won't work for the Cat class. Youalso know why inheritance works for the Cat class. The only remaining question is why inheritance won't workfor the Stack class. To see why, try to declare the root class of a Stack hierarchy, the single class from which allother stack classes would inherit:

class Stack { // a stack of anything

public:

virtual void push(const ??? object) = 0;

virtual ??? pop() = 0;

...

};

Now the difficulty becomes clear. What types are you going to declare for the pure virtual functions push andpop? Remember that each subclass must redeclare the virtual functions it inherits with exactly the sameparameter types and with return types consistent with the base class declarations. Unfortunately, a stack of intswill want to push and pop int objects, whereas a stack of, say, Cats, will want to push and pop Cat objects. Howcan the Stack class declare its pure virtual functions in such a way that clients can create both stacks of ints andstacks of Cats? The cold, hard truth is that it can't, and that's why inheritance is unsuitable for creating stacks.

But maybe you're the sneaky type. Maybe you think you can outsmart your compilers by using generic (void*)pointers. As it turns out, generic pointers don't help you here. You simply can't get around the requirement that avirtual function's declarations in derived classes must never contradict its declaration in the base class.However, generic pointers can help with a different problem, one related to the efficiency of classes generatedfrom templates. For details, see Item 42.

Now that we've dispensed with stacks and cats, we can summarize the lessons of this Item as follows: A template should be used to generate a collection of classes when the type of the objects does not affect

the behavior of the class's functions. Inheritance should be used for a collection of classes when the type of the objects does affect the behavior

of the class's functions.

Internalize these two little bullet points, and you'll be well on your way to mastering the choice betweeninheritance and templates.

Back to Item 40: Differentiate between inheritance and templates. Continue to Item 42: Use private inheritance judiciously.

Page 158: Effective C++ and More Effective C++

Back to Item 41: Differentiate between inheritance and templates. Continue to Item 43: Use multiple inheritance judiciously.

Item 42: Use private inheritance judiciously.

Item 35 demonstrates that C++ treats public inheritance as an isa relationship. It does this by showing thatcompilers, when given a hierarchy in which a class Student publicly inherits from a class Person, implicitlyconvert Students to Persons when that is necessary for a function call to succeed. It's worth repeating a portionof that example using private inheritance instead of public inheritance:

class Person { ... };

class Student: // this time we use

private Person { ... }; // private inheritance

void dance(const Person& p); // anyone can dance

void study(const Student& s); // only students study

Person p; // p is a Person

Student s; // s is a Student

dance(p); // fine, p is a Person

dance(s); // error! a Student isn't

// a Person

Clearly, private inheritance doesn't mean isa. What does it mean then?

"Whoa!" you say. "Before we get to the meaning, let's cover the behavior. How does private inheritancebehave?" Well, the first rule governing private inheritance you've just seen in action: in contrast to publicinheritance, compilers will generally not convert a derived class object (such as Student) into a base classobject (such as Person) if the inheritance relationship between the classes is private. That's why the call todance fails for the object s. The second rule is that members inherited from a private base class become privatemembers of the derived class, even if they were protected or public in the base class. So much for behavior.

That brings us to meaning. Private inheritance means is-implemented-in-terms-of. If you make a class Dprivately inherit from a class B, you do so because you are interested in taking advantage of some of the codethat has already been written for class B, not because there is any conceptual relationship between objects oftype B and objects of type D. As such, private inheritance is purely an implementation technique. Using the termsintroduced in Item 36, private inheritance means that implementation only should be inherited; interface shouldbe ignored. If D privately inherits from B, it means that D objects are implemented in terms of B objects, nothingmore. Private inheritance means nothing during software design, only during software implementation.

The fact that private inheritance means is-implemented-in-terms-of is a little disturbing, because Item 40 pointsout that layering can mean the same thing. How are you supposed to choose between them? The answer issimple: use layering whenever you can, use private inheritance whenever you must. When must you? Whenprotected members and/or virtual functions enter the picture ? but more on that in a moment.

Item 41 shows a way to write a Stack template that generates classes holding objects of different types. You maywish to familiarize yourself with that Item now. Templates are one of the most useful features in C++, but onceyou start using them regularly, you'll discover that if you instantiate a template a dozen times, you are likely toinstantiate the code for the template a dozen times. In the case of the Stack template, the code making upStack<int>'s member functions will be completely separate from the code making up Stack<double>'s memberfunctions. Sometimes this is unavoidable, but such code replication is likely to exist even if the templatefunctions could in fact share code. There is a name for the resultant increase in object code size:

Page 159: Effective C++ and More Effective C++

template-induced code bloat. It is not a good thing.

For certain kinds of classes, you can use generic pointers to avoid it. The classes to which this approach isapplicable store pointers instead of objects, and they are implemented by:

1. Creating a single class that stores void* pointers to objects. 2. Creating a set of additional classes whose only purpose is to enforce strong typing. These classes all use

the generic class of step 1 for the actual work.

Here's an example using the non-template Stack class of Item 41, except here it stores generic pointers instead ofobjects:

class GenericStack {

public:

GenericStack();

~GenericStack();

void push(void *object);

void * pop();

bool empty() const;

private:

struct StackNode {

void *data; // data at this node

StackNode *next; // next node in list

StackNode(void *newData, StackNode *nextNode)

: data(newData), next(nextNode) {}

};

StackNode *top; // top of stack

GenericStack(const GenericStack& rhs); // prevent copying and

GenericStack& // assignment (see

operator=(const GenericStack& rhs); // Item 27)

};

Because this class stores pointers instead of objects, it is possible that an object is pointed to by more than onestack (i.e., has been pushed onto multiple stacks). It is thus of critical importance that pop and the classdestructor not delete the data pointer of any StackNode object they destroy, although they must continue to deletethe StackNode object itself. After all, the StackNode objects are allocated inside the GenericStack class, so theymust also be deallocated inside that class. As a result, the implementation of the Stack class in Item 41 sufficesalmost perfectly for the GenericStack class. The only changes you need to make involve substitutions of void*for T.

The GenericStack class by itself is of little utility ? it's too easy to misuse. For example, a client couldmistakenly push a pointer to a Cat object onto a stack meant to hold only pointers to ints, and compilers wouldmerrily accept it. After all, a pointer's a pointer when it comes to void* parameters.

To regain the type safety to which you have become accustomed, you create interface classes to GenericStack,like this:

class IntStack { // interface class for ints

public:

void push(int *intPtr) { s.push(intPtr); }

int * pop() { return static_cast<int*>(s.pop()); }

bool empty() const { return s.empty(); }

private:

GenericStack s; // implementation

Page 160: Effective C++ and More Effective C++

};

class CatStack { // interface class for cats

public:

void push(Cat *catPtr) { s.push(catPtr); }

Cat * pop() { return static_cast<Cat*>(s.pop()); }

bool empty() const { return s.empty(); }

private:

GenericStack s; // implementation

};

As you can see, the IntStack and CatStack classes serve only to enforce strong typing. Only int pointers can bepushed onto an IntStack or popped from it, and only Cat pointers can be pushed onto a CatStack or popped fromit. Both IntStack and CatStack are implemented in terms of the class GenericStack, a relationship that isexpressed through layering (see Item 40), and IntStack and CatStack will share the code for the functions inGenericStack that actually implement their behavior. Furthermore, the fact that all IntStack and CatStack memberfunctions are (implicitly) inline means that the runtime cost of using these interface classes is zip, zero, nada,nil.

But what if potential clients don't realize that? What if they mistakenly believe that use of GenericStack is moreefficient, or what if they're just wild and reckless and think only wimps need type-safety nets? What's to keepthem from bypassing IntStack and CatStack and going straight to GenericStack, where they'll be free to make thekinds of type errors C++ was specifically designed to prevent?

Nothing. Nothing prevents that. But maybe something should.

I mentioned at the outset of this Item that an alternative way to assert an is-implemented-in-terms-of relationshipbetween classes is through private inheritance. In this case, that technique offers an advantage over layering,because it allows you to express the idea that GenericStack is too unsafe for general use, that it should be usedonly to implement other classes. You say that by protecting GenericStack's member functions:

class GenericStack {

protected:

GenericStack();

~GenericStack();

void push(void *object);

void * pop();

bool empty() const;

private:

... // same as above

};

GenericStack s; // error! constructor is

// protected

class IntStack: private GenericStack {

public:

void push(int *intPtr) { GenericStack::push(intPtr); }

int * pop() { return static_cast<int*>(GenericStack::pop()); }

bool empty() const { return GenericStack::empty(); }

};

class CatStack: private GenericStack {

public:

void push(Cat *catPtr) { GenericStack::push(catPtr); }

Cat * pop() { return static_cast<Cat*>(GenericStack::pop()); }

Page 161: Effective C++ and More Effective C++

bool empty() const { return GenericStack::empty(); }

};

IntStack is; // fine

CatStack cs; // also fine

Like the layering approach, the implementation based on private inheritance avoids code duplication, becausethe type-safe interface classes consist of nothing but inline calls to the underlying GenericStack functions.

Building type-safe interfaces on top of the GenericStack class is a pretty slick maneuver, but it's awfullyunpleasant to have to type in all those interface classes by hand. Fortunately, you don't have to. You can usetemplates to generate them automatically. Here's a template to generate type-safe stack interfaces using privateinheritance:

template<class T>

class Stack: private GenericStack {

public:

void push(T *objectPtr) { GenericStack::push(objectPtr); }

T * pop() { return static_cast<T*>(GenericStack::pop()); }

bool empty() const { return GenericStack::empty(); }

};

This is amazing code, though you may not realize it right away. Because of the template, compilers willautomatically generate as many interface classes as you need. Because those classes are type-safe, client typeerrors are detected during compilation. Because GenericStack's member functions are protected and interfaceclasses use it as a private base class, clients are unable to bypass the interface classes. Because each interfaceclass member function is (implicitly) declared inline, no runtime cost is incurred by use of the type-safe classes;the generated code is exactly the same as if clients programmed with GenericStack directly (assuming compilersrespect the inline request ? see Item 33). And because GenericStack uses void* pointers, you pay for only onecopy of the code for manipulating stacks, no matter how many different types of stack you use in your program.In short, this design gives you code that's both maximally efficient and maximally type safe. It's difficult to dobetter than that.

One of the precepts of this book is that C++'s features interact in remarkable ways. This example, I hope you'llagree, is pretty remarkable.

The insight to carry away from this example is that it could not have been achieved using layering. Onlyinheritance gives access to protected members, and only inheritance allows for virtual functions to be redefined.(For an example of how the existence of virtual functions can motivate the use of private inheritance, see Item 43.) Because virtual functions and protected members exist, private inheritance is sometimes the only practicalway to express an is-implemented-in-terms-of relationship between classes. As a result, you shouldn't be afraidto use private inheritance when it's the most appropriate implementation technique at your disposal. At the sametime, however, layering is the preferable technique in general, so you should employ it whenever you can.

Back to Item 41: Differentiate between inheritance and templates. Continue to Item 43: Use multiple inheritance judiciously.

Page 162: Effective C++ and More Effective C++

Back to Item 42: Use private inheritance judiciously. Continue to Item 44: Say what you mean; understand what you're saying.

Item 43: Use multiple inheritance judiciously.

Depending on who's doing the talking, multiple inheritance (MI) is either the product of divine inspiration or themanifest work of the devil. Proponents hail it as essential to the natural modeling of real-world problems, whilecritics argue that it is slow, difficult to implement, and no more powerful than single inheritance.Disconcertingly, the world of object-oriented programming languages remains split on the issue: C++, Eiffel,and the Common LISP Object System (CLOS) offer MI; Smalltalk, Objective C, and Object Pascal do not; andJava supports only a restricted form of it. What's a poor, struggling programmer to believe?

Before you believe anything, you need to get your facts straight. The one indisputable fact about MI in C++ isthat it opens up a Pandora's box of complexities that simply do not exist under single inheritance. Of these, themost basic is ambiguity (see Item 26). If a derived class inherits a member name from more than one base class,any reference to that name is ambiguous; you must explicitly say which member you mean. Here's an examplethat's based on a discussion in the ARM (see Item 50):

class Lottery {

public:

virtual int draw();

...

};

class GraphicalObject {

public:

virtual int draw();

...

};

class LotterySimulation: public Lottery,

public GraphicalObject {

... // doesn't declare draw

};

LotterySimulation *pls = new LotterySimulation;

pls->draw(); // error! ? ambiguous

pls->Lottery::draw(); // fine

pls->GraphicalObject::draw(); // fine

This looks clumsy, but at least it works. Unfortunately, the clumsiness is difficult to eliminate. Even if one of theinherited draw functions were private and hence inaccessible, the ambiguity would remain. (There's a goodreason for that, but a complete explanation of the situation is provided in Item 26, so I won't repeat it here.)

Explicitly qualifying members is more than clumsy, however, it's also limiting. When you explicitly qualify avirtual function with a class name, the function doesn't act virtual any longer. Instead, the function called isprecisely the one you specify, even if the object on which it's invoked is of a derived class:

class SpecialLotterySimulation: public LotterySimulation {

public:

virtual int draw();

...

Page 163: Effective C++ and More Effective C++

};

pls = new SpecialLotterySimulation;

pls->draw(); // error! ? still ambiguous

pls->Lottery::draw(); // calls Lottery::draw

pls->GraphicalObject::draw(); // calls GraphicalObject::draw

In this case, notice that even though pls points to a SpecialLotterySimulation object, there is no way (short of adowncast ? see Item 39) to invoke the draw function defined in that class.

But wait, there's more. The draw functions in both Lottery and GraphicalObject are declared virtual so thatsubclasses can redefine them (see Item 36), but what if LotterySimulation would like to redefine both of them?The unpleasant truth is that it can't, because a class is allowed to have only a single function called draw thattakes no arguments. (There is a special exception to this rule if one of the functions is const and one is not ? see Item 21.)

At one point, this difficulty was considered a serious enough problem to justify a change in the language. TheARM discusses the possibility of allowing inherited virtual functions to be "renamed," but then it wasdiscovered that the problem can be circumvented by the addition of a pair of new classes:

class AuxLottery: public Lottery {

public:

virtual int lotteryDraw() = 0;

virtual int draw() { return lotteryDraw(); }

};

class AuxGraphicalObject: public GraphicalObject {

public:

virtual int graphicalObjectDraw() = 0;

virtual int draw() { return graphicalObjectDraw(); }

};

class LotterySimulation: public AuxLottery,

public AuxGraphicalObject {

public:

virtual int lotteryDraw();

virtual int graphicalObjectDraw();

...

};

Each of the two new classes, AuxLottery and AuxGraphicalObject, essentially declares a new name for thedraw function that each inherits. This new name takes the form of a pure virtual function, in this caselotteryDraw and graphicalObjectDraw; the functions are pure virtual so that concrete subclasses must redefinethem. Furthermore, each class redefines the draw that it inherits to itself invoke the new pure virtual function.The net effect is that within this class hierarchy, the single, ambiguous name draw has effectively been split intotwo unambiguous, but operationally equivalent, names: lotteryDraw and graphicalObjectDraw:

LotterySimulation *pls = new LotterySimulation;

Lottery *pl = pls;

GraphicalObject *pgo = pls;

// this calls LotterySimulation::lotteryDraw

pl->draw();

// this calls LotterySimulation::graphicalObjectDraw

pgo->draw();

Page 164: Effective C++ and More Effective C++

This strategy, replete as it is with the clever application of pure virtual, simple virtual, and inline functions(see Item 33), should be committed to memory. In the first place, it solves a problem that you may encountersome day. In the second, it can serve to remind you of the complications that can arise in the presence ofmultiple inheritance. Yes, this tactic works, but do you really want to be forced to introduce new classes just soyou can redefine a virtual function? The classes AuxLottery and AuxGraphicalObject are essential to the correctoperation of this hierarchy, but they correspond neither to an abstraction in the problem domain nor to anabstraction in the implementation domain. They exist purely as an implementation device ? nothing more. Youalready know that good software is "device independent." That rule of thumb applies here, too.

The ambiguity problem, interesting though it is, hardly begins to scratch the surface of the issues you'll confrontwhen you flirt with MI. Another one grows out of the empirical observation that an inheritance hierarchy thatstarts out looking like this,

has a distressing tendency to evolve into one that looks like this:

Now, it may or may not be true that diamonds are a girl's best friend, but it is certainly true that adiamond-shaped inheritance hierarchy such as this is not very friendly. If you create a hierarchy such as this, youare immediately confronted with the question of whether to make A a virtual base class, i.e., whether inheritancefrom A should be virtual. In practice, the answer is almost invariably that it should; only rarely will you want anobject of type D to contain multiple copies of the data members of A. In recognition of this truth, B and C above

Page 165: Effective C++ and More Effective C++

declare A as a virtual base class.

Unfortunately, at the time you define B and C, you may not know whether any class will decide to inherit fromboth of them, and in fact you shouldn't need to know this in order to define them correctly. As a class designer,this puts you in a dreadful quandary. If you do not declare A as a virtual base of B and C, a later designer of Dmay need to modify the definitions of B and C in order to use them effectively. Frequently, this is unacceptable,often because the definitions of A, B, and C are read-only. This would be the case if A, B, and C were in alibrary, for example, and D was written by a library client.

On the other hand, if you do declare A as a virtual base of B and C, you typically impose an additional cost inboth space and time on clients of those classes. That is because virtual base classes are often implemented as pointers to objects, rather than as objects themselves. It goes without saying that the layout of objects in memoryis compiler-dependent, but the fact remains that the memory layout for an object of type D with A as a nonvirtualbase is typically a contiguous series of memory locations, whereas the memory layout for an object of type Dwith A as a virtual base is sometimes a contiguous series of memory locations, two of which contain pointers tothe memory locations containing the data members of the virtual base class:

Even compilers that don't use this particular implementation strategy generally impose some kind of spacepenalty for using virtual inheritance.

In view of these considerations, it would seem that effective class design in the presence of MI calls for

Page 166: Effective C++ and More Effective C++

clairvoyance on the part of library designers. Seeing as how run-of-the-mill common sense is an increasinglyrare commodity these days, you would be ill-advised to rely too heavily on a language feature that calls fordesigners to be not only anticipatory of future needs, but downright prophetic (see also M32).

Of course, this could also be said of the choice between virtual and nonvirtual functions in a base class, butthere is a crucial difference. Item 36 explains that a virtual function has a well-defined high-level meaning that isdistinct from the equally well-defined high-level meaning of a nonvirtual function, so it is possible to choosebetween the two based on what you want to communicate to writers of subclasses. However, the decisionwhether a base class should be virtual or nonvirtual lacks a well-defined high-level meaning. Rather, thatdecision is usually based on the structure of the entire inheritance hierarchy, and as such it cannot be made untilthe entire hierarchy is known. If you need to know exactly how your class is going to be used before you candefine it correctly, it becomes very difficult to design effective classes.

Once you're past the problem of ambiguity and you've settled the question of whether inheritance from your baseclass(es) should be virtual, still more complications confront you. Rather than belaboring things, I'll simplymention two other issues you need to keep in mind:

Passing constructor arguments to virtual base classes. Under nonvirtual inheritance, arguments for abase class constructor are specified in the member initialization lists of the classes that are immediatelyderived from the base class. Because single inheritance hierarchies need only nonvirtual bases, argumentsare passed up the inheritance hierarchy in a very natural fashion: the classes at level n of the hierarchypass arguments to the classes at level n-1. For constructors of a virtual base class, however, arguments arespecified in the member initialization lists of the classes that are most derived from the base. As a result,the class initializing a virtual base may be arbitrarily far from it in the inheritance graph, and the classperforming the initialization can change as new classes are added to the hierarchy. (A good way to avoidthis problem is to eliminate the need to pass constructor arguments to virtual bases. The easiest way to dothat is to avoid putting data members in such classes. This is the essence of the Java solution to theproblem: virtual base classes in Java (i.e., "Interfaces") are prohibited from containing data.)

Dominance of virtual functions. Just when you thought you had ambiguity all figured out, they change therules on you. Consider again the diamond-shaped inheritance graph involving classes A, B, C, and D.Suppose that A defines a virtual member function mf, and C redefines it; B and D, however, do notredefine mf:

From our earlier discussion, you'd expect this to be ambiguous: D *pd = new D;

pd->mf(); // A::mf or C::mf?

Which mf should be called for a D object, the one directly inherited from C or the one indirectly inherited (viaB) from A? The answer is that it depends on how B and C inherit from A. In particular, if A is a nonvirtual baseof B or C, the call is ambiguous, but if A is a virtual base of both B and C, the redefinition of mf in C is said to dominate the original definition in A, and the call to mf through pd will resolve (unambiguously) to C::mf. If yousit down and work it all out, it emerges that this is the behavior you want, but it's kind of a pain to have to sitdown and work it all out before it makes sense.

Page 167: Effective C++ and More Effective C++

Perhaps by now you agree that MI can lead to complications. Perhaps you are convinced that no one in theirright mind would ever use it. Perhaps you are prepared to propose to the international °C++ standardizationcommittee that multiple inheritance be removed from the language, or at least to propose to your manager thatprogrammers at your company be physically barred from using it.

Perhaps you are being too hasty.

Bear in mind that the designer of C++ didn't set out to make multiple inheritance hard to use, it just turned outthat making all the pieces work together in a more or less reasonable fashion inherently entailed the introductionof certain complexities. In the above discussion, you may have noticed that the bulk of these complexities arisein conjunction with the use of virtual base classes. If you can avoid the use of virtual bases ? that is, if you canavoid the creation of the deadly diamond inheritance graph ? things become much more manageable.

For example, Item 34 describes how a Protocol class exists only to specify an interface for derived classes; ithas no data members, no constructors, a virtual destructor (see Item 14), and a set of pure virtual functions thatspecify the interface. A Protocol Person class might look like this:

class Person {

public:

virtual ~Person();

virtual string name() const = 0;

virtual string birthDate() const = 0;

virtual string address() const = 0;

virtual string nationality() const = 0;

};

Clients of this class must program in terms of Person pointers and references, because abstract classes cannotbe instantiated.

To create objects that can be manipulated as Person objects, clients of Person use factory functions (see Item 34) to instantiate concrete subclasses of that class:

// factory function to create a Person object from a

// unique database ID

Person * makePerson(DatabaseID personIdentifier);

DatabaseID askUserForDatabaseID();

DatabaseID pid = askUserForDatabaseID();

Person *pp = makePerson(pid); // create object supporting

// the Person interface

... // manipulate *pp via

// Person's member functions

delete pp; // delete the object when

// it's no longer needed

This just begs the question: how does makePerson create the objects to which it returns pointers? Clearly, theremust be some concrete class derived from Person that makePerson can instantiate.

Suppose this class is called MyPerson. As a concrete class, MyPerson must provide implementations for thepure virtual functions it inherits from Person. It could write these from scratch, but it would be better softwareengineering to take advantage of existing components that already do most or all of what's necessary. Forexample, let's suppose a creaky old database-specific class PersonInfo already exists that provides the essence

Page 168: Effective C++ and More Effective C++

of what MyPerson needs: class PersonInfo {

public:

PersonInfo(DatabaseID pid);

virtual ~PersonInfo();

virtual const char * theName() const;

virtual const char * theBirthDate() const;

virtual const char * theAddress() const;

virtual const char * theNationality() const;

virtual const char * valueDelimOpen() const; // see

virtual const char * valueDelimClose() const; // below

...

};

You can tell this is an old class, because the member functions return const char*s instead of string objects.Still, if the shoe fits, why not wear it? The names of this class's member functions suggest that the result is likelyto be pretty comfortable.

You come to discover that PersonInfo, however, was designed to facilitate the process of printing databasefields in various formats, with the beginning and end of each field value delimited by special strings. By default,the opening and closing delimiters for field values are braces, so the field value "Ring-tailed Lemur" would beformatted this way:

[Ring-tailed Lemur]

In recognition of the fact that braces are not universally desired by clients of PersonInfo, the virtual functionsvalueDelimOpen and valueDelimClose allow derived classes to specify their own opening and closingdelimiter strings. The implementations of PersonInfo's theName, theBirthDate, theAddress, and theNationalitycall these virtual functions to add the appropriate delimiters to the values they return. Using PersonInfo::name asan example, the code looks like this:

const char * PersonInfo::valueDelimOpen() const

{

return "["; // default opening delimiter

}

const char * PersonInfo::valueDelimClose() const

{

return "]"; // default closing delimiter

}

const char * PersonInfo::theName() const

{

// reserve buffer for return value. Because this is

// static, it's automatically initialized to all zeros

static char value[MAX_FORMATTED_FIELD_VALUE_LENGTH];

// write opening delimiter

strcpy(value, valueDelimOpen());

append to the string in value this object's name field

// write closing delimiter

strcat(value, valueDelimClose());

return value;

}

Page 169: Effective C++ and More Effective C++

One might quibble with the design of PersonInfo::theName (especially the use of a fixed-size static buffer ? see Item 23), but set your quibbles aside and focus instead on this: theName calls valueDelimOpen to generate theopening delimiter of the string it will return, then it generates the name value itself, then it callsvalueDelimClose. Because valueDelimOpen and valueDelimClose are virtual functions, the result returned bytheName is dependent not only on PersonInfo, but also on the classes derived from PersonInfo.

As the implementer of MyPerson, that's good news, because while perusing the fine print in the Persondocumentation, you discover that name and its sister member functions are required to return unadorned values,i.e., no delimiters are allowed. That is, if a person is from Madagascar, a call to that person's nationalityfunction should return "Madagascar", not "[Madagascar]".

The relationship between MyPerson and PersonInfo is that PersonInfo happens to have some functions that makeMyPerson easier to implement. That's all. There's no isa or has-a relationship anywhere in sight. Theirrelationship is thus is-implemented-in-terms-of, and we know that can be represented in two ways: via layering(see Item 40) and via private inheritance (see Item 42). Item 42 points out that layering is the generally preferredapproach, but private inheritance is necessary if virtual functions are to be redefined. In this case, MyPersonneeds to redefine valueDelimOpen and valueDelimClose, so layering won't do and private inheritance it mustbe: MyPerson must privately inherit from PersonInfo.

But MyPerson must also implement the Person interface, and that calls for public inheritance. This leads to onereasonable application of multiple inheritance: combine public inheritance of an interface with privateinheritance of an implementation:

class Person { // this class specifies

public: // the interface to be

virtual ~Person(); // implemented

virtual string name() const = 0;

virtual string birthDate() const = 0;

virtual string address() const = 0;

virtual string nationality() const = 0;

};

class DatabaseID { ... }; // used below; details

// are unimportant

class PersonInfo { // this class has functions

public: // useful in implementing

PersonInfo(DatabaseID pid); // the Person interface

virtual ~PersonInfo();

virtual const char * theName() const;

virtual const char * theBirthDate() const;

virtual const char * theAddress() const;

virtual const char * theNationality() const;

virtual const char * valueDelimOpen() const;

virtual const char * valueDelimClose() const;

...

};

class MyPerson: public Person, // note use of

private PersonInfo { // multiple inheritance

public:

MyPerson(DatabaseID pid): PersonInfo(pid) {}

// redefinitions of inherited virtual delimiter functions

const char * valueDelimOpen() const { return ""; }

Page 170: Effective C++ and More Effective C++

const char * valueDelimClose() const { return ""; }

// implementations of the required Person member functions

string name() const

{ return PersonInfo::theName(); }

string birthDate() const

{ return PersonInfo::theBirthDate(); }

string address() const

{ return PersonInfo::theAddress(); }

string nationality() const

{ return PersonInfo::theNationality(); }

};

Graphically, it looks like this:

This kind of example demonstrates that MI can be both useful and comprehensible, although it's no accident thatthe dreaded diamond-shaped inheritance graph is conspicuously absent.

Still, you must guard against temptation. Sometimes you can fall into the trap of using MI to make a quick fix toan inheritance hierarchy that would be better served by a more fundamental redesign. For example, supposeyou're working with a hierarchy for animated cartoon characters. At least conceptually, it makes sense for anykind of character to dance and sing, but the way in which each type of character performs these activities differs.Furthermore, the default behavior for singing and dancing is to do nothing.

The way to say all that in C++ is like this: class CartoonCharacter {

public:

virtual void dance() {}

virtual void sing() {}

};

Virtual functions naturally model the constraint that dancing and singing make sense for all CartoonCharacterobjects. Do-nothing default behavior is expressed by the empty definitions of those functions in the class (see Item 36). Suppose a particular type of cartoon character is a grasshopper, which dances and sings in its own

Page 171: Effective C++ and More Effective C++

particular way: class Grasshopper: public CartoonCharacter {

public:

virtual void dance(); // definition is elsewhere

virtual void sing(); // definition is elsewhere

};

Now suppose that after implementing the Grasshopper class, you decide you also need a class for crickets: class Cricket: public CartoonCharacter {

public:

virtual void dance();

virtual void sing();

};

As you sit down to implement the Cricket class, you realize that a lot of the code you wrote for the Grasshopperclass can be reused. However, it needs to be tweaked a bit here and there to account for the differences insinging and dancing between grasshoppers and crickets. You are suddenly struck by a clever way to reuse yourexisting code: you'll implement the Cricket class in terms of the Grasshopper class, and you'll use virtualfunctions to allow the Cricket class to customize Grasshopper behavior!

You immediately recognize that these twin requirements ? an is-implemented-in-terms-of relationship and theability to redefine virtual functions ? mean that Cricket will have to privately inherit from Grasshopper, but ofcourse a cricket is still a cartoon character, so you redefine Cricket to inherit from both Grasshopper andCartoonCharacter:

class Cricket: public CartoonCharacter,

private Grasshopper {

public:

virtual void dance();

virtual void sing();

};

You then set out to make the necessary modifications to the Grasshopper class. In particular, you need todeclare some new virtual functions for Cricket to redefine:

class Grasshopper: public CartoonCharacter {

public:

virtual void dance();

virtual void sing();

protected:

virtual void danceCustomization1();

virtual void danceCustomization2();

virtual void singCustomization();

};

Dancing for grasshoppers is now defined like this: void Grasshopper::dance()

{

perform common dancing actions;

danceCustomization1();

perform more common dancing actions;

danceCustomization2();

perform final common dancing actions;

}

Page 172: Effective C++ and More Effective C++

Grasshopper singing is similarly orchestrated.

Clearly, the Cricket class must be updated to take into account the new virtual functions it must redefine: class Cricket:public CartoonCharacter,

private Grasshopper {

public:

virtual void dance() { Grasshopper::dance(); }

virtual void sing() { Grasshopper::sing(); }

protected:

virtual void danceCustomization1();

virtual void danceCustomization2();

virtual void singCustomization();

};

This seems to work fine. When a Cricket object is told to dance, it will execute the common dance code in theGrasshopper class, then execute the dance customization code in the Cricket class, then continue with the code inGrasshopper::dance, etc.

There is a serious flaw in your design, however, and that is that you have run headlong into Occam's razor, a badidea with a razor of any kind, and especially so when it belongs to William of Occam. Occamism preaches thatentities should not be multiplied beyond necessity, and in this case, the entities in question are inheritancerelationships. If you believe that multiple inheritance is more complicated than single inheritance (and I hopethat you do), then the design of the Cricket class is needlessly complex.

Fundamentally, the problem is that it is not true that the Cricket class is-implemented-in-terms-of theGrasshopper class. Rather, the Cricket class and the Grasshopper class share common code. In particular, theyshare the code that determines the dancing and singing behavior that grasshoppers and crickets have in common.

The way to say that two classes have something in common is not to have one class inherit from the other, but tohave both of them inherit from a common base class. The common code for grasshoppers and crickets doesn'tbelong in the Grasshopper class, nor does it belong in the Cricket class. It belongs in a new class from whichthey both inherit, say, Insect:

class CartoonCharacter { ... };

class Insect: public CartoonCharacter {

public:

virtual void dance(); // common code for both

virtual void sing(); // grasshoppers and crickets

protected:

virtual void danceCustomization1() = 0;

virtual void danceCustomization2() = 0;

virtual void singCustomization() = 0;

};

class Grasshopper: public Insect {

protected:

virtual void danceCustomization1();

virtual void danceCustomization2();

virtual void singCustomization();

};

class Cricket: public Insect {

protected:

virtual void danceCustomization1();

virtual void danceCustomization2();

virtual void singCustomization();

Page 173: Effective C++ and More Effective C++

};

Notice how much cleaner this design is. Only single inheritance is involved, and furthermore, only publicinheritance is used. Grasshopper and Cricket define only customization functions; they inherit the dance and singfunctions unchanged from Insect. William of Occam would be proud.

Although this design is cleaner than the one involving MI, it may initially have appeared to be inferior. After all,compared to the MI approach, this single-inheritance architecture calls for the introduction of a brand new class,a class unnecessary if MI is used. Why introduce an extra class if you don't have to?

This brings you face to face with the seductive nature of multiple inheritance. On the surface, MI seems to be theeasier course of action. It adds no new classes, and though it calls for the addition of some new virtual functionsto the Grasshopper class, those functions have to be added somewhere in any case.

Imagine now a programmer maintaining a large C++ class library, one in which a new class has to be added,much as the Cricket class had to be added to the existing CartoonCharacter/Grasshopper hierarchy. Theprogrammer knows that a large number of clients use the existing hierarchy, so the bigger the change to thelibrary, the greater the disruption to clients. The programmer is determined to minimize that kind of disruption.Mulling over the options, the programmer realizes that if a single private inheritance link from Grasshopper to

Page 174: Effective C++ and More Effective C++

Cricket is added, no other change to the hierarchy will be needed. The programmer smiles at the thought,pleased with the prospect of a large increase in functionality at the cost of only a slight increase in complexity.

Imagine now that that maintenance programmer is you. Resist the seduction. Back to Item 42: Use private inheritance judiciously.

Continue to Item 44: Say what you mean; understand what you're saying.

Page 175: Effective C++ and More Effective C++

Back to Item 43: Use multiple inheritance judiciously. Continue to Miscellany

Item 44: Say what you mean; understand what you're saying.

In the introduction to this section on inheritance and object-oriented design, I emphasized the importance ofunderstanding what different object-oriented constructs in C++ mean. This is quite different from just knowingthe rules of the language. For example, the rules of C++ say that if class D publicly inherits from class B, thereis a standard conversion from a D pointer to a B pointer; that the public member functions of B are inherited aspublic member functions of D, etc. That's all true, but it's close to useless if you're trying to translate your designinto C++. Instead, you need to understand that public inheritance means isa, that if D publicly inherits from B,every object of type D isa object of type B, too. Thus, if you mean isa in your design, you know you have to usepublic inheritance.

Saying what you mean is only half the battle. The flip side of the coin is understanding what you're saying, andit's just as important. For example, it's irresponsible, if not downright immoral, to run around declaring memberfunctions nonvirtual without recognizing that in so doing you are imposing constraints on subclasses. In declaringa nonvirtual member function, what you're really saying is that the function represents an invariant overspecialization, and it would be disastrous if you didn't know that.

The equivalence of public inheritance and isa, and of nonvirtual member functions and invariance overspecialization, are examples of how certain C++ constructs correspond to design-level ideas. The list belowsummarizes the most important of these mappings.

A common base class means common traits. If class D1 and class D2 both declare class B as a base, D1and D2 inherit common data members and/or common member functions from B. See Item 43.

Public inheritance means isa. If class D publicly inherits from class B, every object of type D is also anobject of type B, but not vice versa. See Item 35.

Private inheritance means is-implemented-in-terms-of. If class D privately inherits from class B,objects of type D are simply implemented in terms of objects of type B; no conceptual relationship existsbetween objects of types B and D. See Item 42.

Layering means has-a or is-implemented-in-terms-of. If class A contains a data member of type B,objects of type A either have a component of type B or are implemented in terms of objects of type B. See Item 40.

The following mappings apply only when public inheritance is involved: A pure virtual function means that only the function's interface is inherited. If a class C declares a

pure virtual member function mf, subclasses of C must inherit the interface for mf, and concrete subclassesof C must supply their own implementations for it. See Item 36.

A simple virtual function means that the function's interface plus a default implementation isinherited. If a class C declares a simple (not pure) virtual function mf, subclasses of C must inherit theinterface for mf, and they may also inherit a default implementation, if they choose. See Item 36.

A nonvirtual function means that the function's interface plus a mandatory implementation isinherited. If a class C declares a nonvirtual member function mf, subclasses of C must inherit both theinterface for mf and its implementation. In effect, mf defines an invariant over specialization of C. See Item 36.

Back to Item 43: Use multiple inheritance judiciously. Continue to Miscellany

Page 176: Effective C++ and More Effective C++

Back to Item 44: Say what you mean; understand what you're saying. Continue to Item 45: Know what functions C++ silently writes and calls.

Miscellany

Some guidelines for effective C++ programming defy convenient categorization. This section is where suchguidelines come to roost. Not that that diminishes their importance. If you are to write effective software, youmust understand what compilers are doing for you (to you?) behind your back, how to ensure that non-localstatic objects are initialized before they are used, what you can expect from the standard library, and where togo for insights into the language's underlying design philosophy. In this final section of the book, I expound onthese issues, and more.

Back to Item 44: Say what you mean; understand what you're saying. Continue to Item 45: Know what functions C++ silently writes and calls.

Page 177: Effective C++ and More Effective C++

Back to Miscellany Continue to Item 46: Prefer compile-time and link-time errors to runtime errors.

Item 45: Know what functions C++ silently writes and calls.

When is an empty class not an empty class? When C++ gets through with it. If you don't declare them yourself,your thoughtful compilers will declare their own versions of a copy constructor, an assignment operator, adestructor, and a pair of address-of operators. Furthermore, if you don't declare any constructors, they willdeclare a default constructor for you, too. All these functions will be public. In other words, if you write this,

class Empty{};

it's the same as if you'd written this: class Empty {

public:

Empty(); // default constructor

Empty(const Empty& rhs); // copy constructor

~Empty(); // destructor ? see

// below for whether

// it's virtual

Empty&

operator=(const Empty& rhs); // assignment operator

Empty* operator&(); // address-of operators

const Empty* operator&() const;

};

Now these functions are generated only if they are needed, but it doesn't take much to need them. The followingcode will cause each function to be generated:

const Empty e1; // default constructor;

// destructor

Empty e2(e1); // copy constructor

e2 = e1; // assignment operator

Empty *pe2 = &e2; // address-of

// operator (non-const)

const Empty *pe1 = &e1; // address-of

// operator (const)

Given that compilers are writing functions for you, what do the functions do? Well, the default constructor andthe destructor don't really do anything. They just enable you to create and destroy objects of the class. (They alsoprovide a convenient place for implementers to place code whose execution takes care of "behind the scenes"behavior ? see Items 33 and M24.) Note that the generated destructor is nonvirtual (see Item 14) unless it's for aclass inheriting from a base class that itself declares a virtual destructor. The default address-of operators justreturn the address of the object. These functions are effectively defined like this:

inline Empty::Empty() {}

inline Empty::~Empty() {}

inline Empty * Empty::operator&() { return this; }

Page 178: Effective C++ and More Effective C++

inline const Empty * Empty::operator&() const

{ return this; }

As for the copy constructor and the assignment operator, the official rule is this: the default copy constructor(assignment operator) performs memberwise copy construction (assignment) of the nonstatic data members ofthe class. That is, if m is a nonstatic data member of type T in a class C and C declares no copy constructor(assignment operator), m will be copy constructed (assigned) using the copy constructor (assignment operator)defined for T, if there is one. If there isn't, this rule will be recursively applied to m's data members until a copyconstructor (assignment operator) or built-in type (e.g., int, double, pointer, etc.) is found. By default, objects ofbuilt-in types are copy constructed (assigned) using bitwise copy from the source object to the destinationobject. For classes that inherit from other classes, this rule is applied to each level of the inheritance hierarchy,so user-defined copy constructors and assignment operators are called at whatever level they are declared.

I hope that's crystal clear.

But just in case it's not, here's an example. Consider the definition of a NamedObject template, whose instancesare classes allowing you to associate names with objects:

template<class T>

class NamedObject {

public:

NamedObject(const char *name, const T& value);

NamedObject(const string& name, const T& value);

...

private:

string nameValue;

T objectValue;

};

Because the NamedObject classes declare at least one constructor, compilers won't generate defaultconstructors, but because the classes fail to declare copy constructors or assignment operators, compilers willgenerate those functions (if they are needed).

Consider the following call to a copy constructor: NamedObject<int> no1("Smallest Prime Number", 2);

NamedObject<int> no2(no1); // calls copy constructor

The copy constructor generated by your compilers must initialize no2.nameValue and no2.objectValue usingno1.nameValue and no1.objectValue, respectively. The type of nameValue is string, and string has a copyconstructor (which you can verify by examining string in the standard library ? see Item 49), so no2.nameValuewill be initialized by calling the string copy constructor with no1.nameValue as its argument. On the other hand,the type of NamedObject<int>::objectValue is int (because T is int for this template instantiation), and no copyconstructor is defined for ints, so no2.objectValue will be initialized by copying the bits over fromno1.objectValue.

The compiler-generated assignment operator for NamedObject<int> would behave the same way, but in general,compiler-generated assignment operators behave as I've described only when the resulting code is both legaland has a reasonable chance of making sense. If either of these tests fails, compilers will refuse to generate anoperator= for your class, and you'll receive some lovely diagnostic during compilation.

For example, suppose NamedObject were defined like this, where nameValue is a reference to a string andobjectValue is a const T:

template<class T>

class NamedObject {

public:

Page 179: Effective C++ and More Effective C++

// this ctor no longer takes a const name, because name-

// Value is now a reference-to-non-const string. The char*

// ctor is gone, because we must have a string to refer to

NamedObject(string& name, const T& value);

... // as above, assume no

// operator= is declared

private:

string& nameValue; // this is now a reference

const T objectValue; // this is now const

};

Now consider what should happen here: string newDog("Persephone");

string oldDog("Satch");

NamedObject<int> p(newDog, 2); // as I write this, our dog

// °Persephone is about to

// have her second birthday

NamedObject<int> s(oldDog, 29); // the family dog Satch

// (from my childhood)

// would be 29 if she were

// still alive

p = s; // what should happen to

// the data members in p?

Before the assignment, p.nameValue refers to some string object and s.nameValue also refers to a string, thoughnot the same one. How should the assignment affect p.nameValue? After the assignment, should p.nameValuerefer to the string referred to by s.nameValue, i.e., should the reference itself be modified? If so, that breaks newground, because C++ doesn't provide a way to make a reference refer to a different object (see Item M1).Alternatively, should the string object to which p.nameValue refers be modified, thus affecting other objects thathold pointers or references to that string, i.e., objects not directly involved in the assignment? Is that what thecompiler-generated assignment operator should do?

Faced with such a conundrum, C++ refuses to compile the code. If you want to support assignment in a classcontaining a reference member, you must define the assignment operator yourself. Compilers behave similarlyfor classes containing const members (such as objectValue in the modified class above); it's not legal to modifyconst members, so compilers are unsure how to treat them during an implicitly generated assignment function.Finally, compilers refuse to generate assignment operators for derived classes that inherit from base classesdeclaring the standard assignment operator private. After all, compiler-generated assignment operators forderived classes are supposed to handle base class parts, too (see Items 16 and M33), but in doing so, theycertainly shouldn't invoke member functions the derived class has no right to call.

All this talk of compiler-generated functions gives rise to the question, what do you do if you want to disallowuse of those functions? That is, what if you deliberately don't declare, for example, an operator= because younever ever want to allow assignment of objects in your class? The solution to that little teaser is the subject ofItem 27. For a discussion of the often-overlooked interactions between pointer members and compiler-generatedcopy constructors and assignment operators, check out Item 11.

Back to Miscellany Continue to Item 46: Prefer compile-time and link-time errors to runtime errors.

Page 180: Effective C++ and More Effective C++

Back to Item 45: Know what functions C++ silently writes and calls. Continue to Item 47: Ensure that non-local static objects are initialized before they're used.

Item 46: Prefer compile-time and link-time errors to runtime errors.

Other than in the few situations that cause C++ to throw exceptions (e.g., running out of memory ? see Item 7),the notion of a runtime error is as foreign to C++ as it is to C. There's no detection of underflow, overflow,division by zero, no checking for array bounds violations, etc. Once your program gets past a compiler andlinker, you're on your own ? there's no safety net of any consequence. Much as with skydiving, some people areexhilarated by this state of affairs, others are paralyzed with fear. The motivation behind the philosophy, ofcourse, is efficiency: without runtime checks, programs are smaller and faster.

There is a different way to approach things. Languages like Smalltalk and LISP generally detect fewer kinds oferrors during compilation and linking, but they provide hefty runtime systems that catch errors during execution.Unlike C++, these languages are almost always interpreted, and you pay a performance penalty for the extraflexibility they offer.

Never forget that you are programming in C++. Even if you find the Smalltalk/LISP philosophy appealing, put itout of your mind. There's a lot to be said for adhering to the party line, and in this case, that means eschewingruntime errors. Whenever you can, push the detection of an error back from runtime to link-time, or, ideally, tocompile-time.

Such a methodology pays dividends not only in terms of program size and speed, but also in terms of reliability.If your program gets through compilers and a linker without eliciting error messages, you may be confident therearen't any compiler- or linker-detectable errors in your program, period. (The other possibility, of course, is thatthere are bugs in your compilers or linkers, but let us not depress ourselves by admitting to such possibilities.)

With runtime errors, the situation is very different. Just because your program doesn't generate any runtimeerrors during a particular run, how can you be sure it won't generate errors during a different run, when you dothings in a different order, use different data, or run for a longer or shorter period of time? You can test yourprogram until you're blue in the face, but you'll still never cover all the possibilities. As a result, detecting errorsat runtime is simply less secure than is catching them during compilation or linking.

Often, by making relatively minor changes to your design, you can catch during compilation what mightotherwise be a runtime error. This frequently involves the addition of new types to the program. (See also ItemM33.) For example, suppose you are writing a class to represent dates in time. Your first cut might look likethis:

class Date {

public:

Date(int day, int month, int year);

...

};

If you were to implement this constructor, one of the problems you'd face would be that of sanity checking onthe values for the day and the month. Let's see how you can eliminate the need to validate the value passed in forthe month.

One obvious approach is to employ an enumerated type instead of an integer: enum Month { Jan = 1, Feb = 2, ... , Nov = 11, Dec = 12 };

class Date {

public:

Date(int day, Month month, int year);

...

};

Page 181: Effective C++ and More Effective C++

Unfortunately, this doesn't buy you that much, because enums don't have to be initialized: Month m;

Date d(22, m, 1857); // m is undefined

As a result, the Date constructor would still have to validate the value of the month parameter.

To achieve enough security to dispense with runtime checks, you've got to use a class to represent months, andyou must ensure that only valid months are created:

class Month {

public:

static const Month Jan() { return 1; }

static const Month Feb() { return 2; }

...

static const Month Dec() { return 12; }

int asInt() const // for convenience, make

{ return monthNumber; } // it possible to convert

// a Month to an int

private:

Month(int number): monthNumber(number) {}

const int monthNumber;

};

class Date {

public:

Date(int day, const Month& month, int year);

...

};

Several aspects of this design combine to make it work the way it does. First, the Month constructor is private.This prevents clients from creating new months. The only ones available are those returned by Month's staticmember functions, plus copies thereof. Second, each Month object is const, so it can't be changed. (Otherwisethe temptation to transform January into June might sometimes prove overwhelming, at least in northernlatitudes.) Finally, the only way to get a Month object is by calling a function or by copying an existing Month(via the implicit Month copy constructor ? see Item 45). This makes it possible to use Month objects anywhereand anytime; there's no need to worry about accidently using one before it's been initialized. (Item 47 explainswhy this might otherwise be a problem.)

Given these classes, it is all but impossible for a client to specify an invalid month. It would be completelyimpossible were it not for the following abomination:

Month *pm; // define uninitialized ptr

Date d(1, *pm, 1997); // arghhh! use it!

However, this involves dereferencing an uninitialized pointer, the results of which are undefined. (See Item 3for my feelings about undefined behavior.) Unfortunately, I know of no way to prevent or detect this kind ofheresy. However, if we assume this never happens, or if we don't care how our software behaves if it does, theDate constructor can dispense with sanity checking on its Month parameter. On the other hand, the constructormust still check the day parameter for validity ? how many days hath September, April, June, and November?

This Date example replaces runtime checks with compile-time checks. You may be wondering when it ispossible to use link-time checks. In truth, not very often. C++ uses the linker to ensure that needed functions aredefined exactly once (see Item 45 for a description of what it takes to "need" a function). It also uses the linkerto ensure that static objects (see Item 47) are defined exactly once. You'll tend to use the linker in the same way.For example, Item 27 describes how the linker's checks can make it useful to deliberately avoid defining a

Page 182: Effective C++ and More Effective C++

function you explicitly declare.

Now don't get carried away. It's impractical to eliminate the need for all runtime checking. Any program thataccepts interactive input, for example, is likely to have to validate that input. Similarly, a class implementingarrays that perform bounds checking (see Item 18) is usually going to have to validate the array index against thebounds every time an array access is made. Nonetheless, shifting checks from runtime to compile- or link-time isalways a worthwhile goal, and you should pursue that goal whenever it is practical. Your reward for doing so isprograms that are smaller, faster, and more reliable.

Back to Item 45: Know what functions C++ silently writes and calls. Continue to Item 47: Ensure that non-local static objects are initialized before they're used.

Page 183: Effective C++ and More Effective C++

Back to Item 46: Prefer compile-time and link-time errors to runtime errors. Continue to Item 48: Pay attention to compiler warnings.

Item 47: Ensure that non-local static objects are initialized before they're used.

You're an adult now, so you don't need me to tell you it's foolhardy to use an object before it's been initialized.In fact, the whole notion may strike you as absurd; constructors make sure objects are initialized when they'recreated, n'est-ce pas?

Well, yes and no. Within a particular translation unit (i.e., source file), everything works fine, but things gettrickier when the initialization of an object in one translation unit depends on the value of another object in adifferent translation unit and that second object itself requires initialization.

For example, suppose you've authored a library offering an abstraction of a file system, possibly including suchcapabilities as making files on the Internet look like they're local. Since your library makes the world look like asingle file system, you might create a special object, theFileSystem, within your library's namespace (see Item28) for clients to use whenever they need to interact with the file system abstraction your library provides:

class FileSystem { ... }; // this class is in your

// library

FileSystem theFileSystem; // this is the object

// with which library

// clients interact

Because theFileSystem represents something complicated, it's no surprise that its construction is both nontrivialand essential; use of theFileSystem before it had been constructed would yield very undefined behavior.(However, consult Item M17 for ideas on how the effective initialization of objects like theFileSystem cansafely be delayed.)

Now suppose some client of your library creates a class for directories in a file system. Naturally, their classuses theFileSystem:

class Directory { // created by library client

public:

Directory();

...

};

Directory::Directory()

{

create a Directory object by invoking member

functions on theFileSystem;

}

Further suppose this client decides to create a distinguished global Directory object for temporary files:

Directory tempDir; // directory for temporary

// files

Now the problem of initialization order becomes apparent: unless theFileSystem is initialized before tempDir,tempDir's constructor will attempt to use theFileSystem before it's been initialized. But theFileSystem andtempDir were created by different people at different times in different files. How can you be sure thattheFileSystem will be created before tempDir?

This kind of question arises anytime you have non-local static objects that are defined in different translationunits and whose correct behavior is dependent on their being initialized in a particular order. Non-local staticobjects are objects that are

Page 184: Effective C++ and More Effective C++

defined at global or namespace scope (e.g., theFileSystem and tempDir), declared static in a class, or defined static at file scope.

Regrettably, there is no shorthand term for "non-local static objects," so you should accustom yourself to thissomewhat awkward phrase.

You do not want the behavior of your software to be dependent on the initialization order of non-local staticobjects in different translation units, because you have no control over that order. Let me repeat that. You haveabsolutely no control over the order in which non-local static objects in different translation units areinitialized.

It is reasonable to wonder why this is the case.

It is the case because determining the "proper" order in which to initialize non-local static objects is hard. Veryhard. Halting-Problem hard. In its most general form ? with multiple translation units and non-local staticobjects generated through implicit template instantiations (which may themselves arise via implicit templateinstantiations) ? it's not only impossible to determine the right order of initialization, it's typically not even worthlooking for special cases where it is possible to determine the right order.

In the field of Chaos Theory, there is a principle known as the "Butterfly Effect." This principle asserts that thetiny atmospheric disturbance caused by the beating of a butterfly's wings in one part of the world can lead toprofound changes in weather patterns in places far distant. Somewhat more rigorously, it asserts that for sometypes of systems, minute perturbations in inputs can lead to radical changes in outputs.

The development of software systems can exhibit a Butterfly Effect of its own. Some systems are highlysensitive to the particulars of their requirements, and small changes in requirements can significantly affect theease with which a system can be implemented. For example, Item 29 describes how changing the specificationfor an implicit conversion from String-to-char* to String-to-const-char* makes it possible to replace a slow orerror-prone function with a fast, safe one.

The problem of ensuring that non-local static objects are initialized before use is similarly sensitive to thedetails of what you want to achieve. If, instead of demanding access to non-local static objects, you're willing tosettle for access to objects that act like non-local static objects (except for the initialization headaches), the hardproblem vanishes. In its stead is left a problem so easy to solve, it's hardly worth calling a problem any longer.

The technique ? sometimes known as the Singleton pattern ? is simplicity itself. First, you move each non-localstatic object into its own function, where you declare it static. Next, you have the function return a reference tothe object it contains. Clients call the function instead of referring to the object. In other words, you replacenon-local static objects with objects that are static inside functions. (See also Item M26.)

The basis of this approach is the observation that although C++ says next to nothing about when a non-localstatic object is initialized, it specifies quite precisely when a static object inside a function (i.e. a local staticobject) is initialized: it's when the object's definition is first encountered during a call to that function. So if youreplace direct accesses to non-local static objects with calls to functions that return references to local staticobjects inside them, you're guaranteed that the references you get back from the functions will refer to initializedobjects. As a bonus, if you never call a function emulating a non-local static object, you never incur the cost ofconstructing and destructing the object, something that can't be said for true non-local static objects.

Here's the technique applied to both theFileSystem and tempDir:

class FileSystem { ... }; // same as before

FileSystem& theFileSystem() // this function replaces

{ // the theFileSystem object

static FileSystem tfs; // define and initialize

// a local static object

Page 185: Effective C++ and More Effective C++

// (tfs = "the file system")

return tfs; // return a reference to it

}

class Directory { ... }; // same as before

Directory::Directory()

{

same as before, except references to theFileSystem are

replaced by references to theFileSystem();

}

Directory& tempDir() // this function replaces

{ // the tempDir object

static Directory td; // define/initialize local

// static object

return td; // return reference to it

}

Clients of this modified system program exactly as they used to, except they now refer to theFileSystem() andtempDir() instead of theFileSystem and tempDir. That is, they refer only to functions returning references tothose objects, never to the objects themselves.

The reference-returning functions dictated by this scheme are always simple: define and initialize a local staticobject on line 1, return it on line 2. That's it. Because they're so simple, you may be tempted to declare theminline. Item 33 explains that late-breaking revisions to the C++ language specification make this a perfectly validimplementation strategy, but it also explains why you'll want to confirm your compilers' conformance with thisaspect of °the standard before putting it to use. If you try it with a compiler not yet in accord with the relevantparts of the standard, you risk getting multiple copies of both the access function and the static object definedwithin it. That's enough to make a grown programmer cry.

Now, there's no magic going on here. For this technique to be effective, it must be possible to come up with areasonable initialization order for your objects. If you set things up such that object A must be initialized beforeobject B, and you also make A's initialization dependent on B's having already been initialized, you are going toget in trouble, and frankly, you deserve it. If you steer shy of such pathological situations, however, the schemedescribed in this Item should serve you quite nicely.

Back to Item 46: Prefer compile-time and link-time errors to runtime errors. Continue to Item 48: Pay attention to compiler warnings.

Page 186: Effective C++ and More Effective C++

Back to Item 47: Ensure that non-local static objects are initialized before they're used. Continue to Item 49: Familiarize yourself with the standard library.

Item 48: Pay attention to compiler warnings.

Many programmers routinely ignore compiler warnings. After all, if the problem were serious, it'd be an error,right? This kind of thinking may be relatively harmless in other languages, but in C++, it's a good bet compilerwriters have a better grasp of what's going on than you do. For example, here's an error everybody makes at onetime or another:

class B {

public:

virtual void f() const;

};

class D: public B {

public:

virtual void f();

};

The idea is for D::f to redefine the virtual function B::f, but there's a mistake: in B, f is a const member function,but in D it's not declared const. One compiler I know says this about that:

warning: D::f() hides virtual B::f()

Too many inexperienced programmers respond to this message by saying to themselves, "Of course D::f hidesB::f ? that's what it's supposed to do!" Wrong. What this compiler is trying to tell you is that the f declared in Bhas not been redeclared in D, it's been hidden entirely (see Item 50 for a description of why this is so). Ignoringthis compiler warning will almost certainly lead to erroneous program behavior, followed by a lot of debuggingto find out about something that this compiler detected in the first place.

After you gain experience with the warning messages from a particular compiler, of course, you'll learn tounderstand what the different messages mean (which is often very different from what they seem to mean, alas).Once you have that experience, there may be a whole range of warnings you'll choose to ignore. That's fine, butit's important to make sure that before you dismiss a warning, you understand exactly what it's trying to tell you.

As long as we're on the topic of warnings, recall that warnings are inherently implementation-dependent, so it'snot a good idea to get sloppy in your programming, relying on compilers to spot your mistakes for you. Thefunction-hiding code above, for instance, goes through a different (but widely used) compiler with nary asquawk. Compilers are supposed to translate C++ into an executable format, not act as your personal safety net.You want that kind of safety? Program in Ada.

Back to Item 47: Ensure that non-local static objects are initialized before they're used. Continue to Item 49: Familiarize yourself with the standard library.

Page 187: Effective C++ and More Effective C++

Back to Item 48: Pay attention to compiler warnings. Continue to Item 50: Improve your understanding of C++.

Item 49: Familiarize yourself with the standard library.

C++'s standard library is big. Very big. Incredibly big. How big? Let me put it this way: the specification takesover 300 closely-packed pages in the °C++ standard, and that all but excludes the standard C library, which isincluded in the C++ library "by reference." (That's the term they use, honest.)

Bigger isn't always better, of course, but in this case, bigger is better, because a big library contains lots offunctionality. The more functionality in the standard library, the more functionality you can lean on as youdevelop your applications. The C++ library doesn't offer everything (support for concurrency and for graphicaluser interfaces is notably absent), but it does offer a lot. You can lean almost anything against it.

Before summarizing what's in the library, I need to tell you a bit about how it's organized. Because the libraryhas so much in it, there's a reasonable chance you (or someone like you) may choose a class or function namethat's the same as a name in the standard library. To shield you from the name conflicts that would result,virtually everything in the standard library is nestled in the namespace std (see Item 28). But that leads to a newproblem. Gazillions of lines of existing C++ rely on functionality in the pseudo-standard library that's been inuse for years, e.g., functionality declared in the headers <iostream.h>, <complex.h>, <limits.h>, etc. Thatexisting software isn't designed to use namespaces, and it would be a shame if wrapping the standard library bystd caused the existing code to break. (Authors of the broken code would likely use somewhat harsher languagethan "shame" to describe their feelings about having the library rug pulled out from underneath them.)

Mindful of the destructive power of rioting bands of incensed programmers, the °standardization committeedecided to create new header names for the std-wrapped components. The algorithm they chose for generatingthe new header names is as trivial as the results it produces are jarring: the .h on the existing C++ headers wassimply dropped. So <iostream.h> became <iostream>, <complex.h> became <complex>, etc. For C headers, thesame algorithm was applied, but a c was prepended to each result. Hence C's <string.h> became <cstring>,<stdio.h> became <cstdio>, etc. For a final twist, the old C++ headers were officially deprecated (i.e., listed asno longer supported), but the old C headers were not (to maintain C compatibility). In practice, compilervendors have no incentive to disavow their customers' legacy software, so you can expect the old C++ headersto be supported for many years.

Practically speaking, then, this is the C++ header situation: Old C++ header names like <iostream.h> are likely to continue to be supported, even though they aren't in

the °official standard. The contents of such headers are not in namespace std. New C++ header names like <iostream> contain the same basic functionality as the corresponding old

headers, but the contents of the headers are in namespace std. (During standardization, the details of someof the library components were modified, so there isn't necessarily an exact match between the entities inan old C++ header and those in a new one.)

Standard C headers like <stdio.h> continue to be supported. The contents of such headers are not in std. New C++ headers for the functionality in the C library have names like <cstdio>. They offer the same

contents as the corresponding old C headers, but the contents are in std.

All this seems a little weird at first, but it's really not that hard to get used to. The biggest challenge is keepingall the string headers straight: <string.h> is the old C header for char*-based string manipulation functions,<string> is the std-wrapped C++ header for the new string classes (see below), and <cstring> is thestd-wrapped version of the old C header. If you can master that (and I know you can), the rest of the library iseasy.

The next thing you need to know about the standard library is that almost everything in it is a template. Consideryour old friend iostreams. (If you and iostreams aren't friends, turn to Item 2 to find out why you should cultivatea relationship.) Iostreams help you manipulate streams of characters, but what's a character? Is it a char? Awchar_t? A Unicode character? Some other multi-byte character? There's no obviously right answer, so thelibrary lets you choose. All the stream classes are really class templates, and you specify the character typewhen you instantiate a stream class. For example, the standard library defines the type of cout to be ostream, butostream is really a typedef for basic_ostream<char>.

Page 188: Effective C++ and More Effective C++

Similar considerations apply to most of the other classes in the standard library. string isn't a class, it's a classtemplate: a type parameter defines the type of characters in each string class. complex isn't a class, it's a classtemplate: a type parameter defines the type of the real and imaginary components in each complex class. vectorisn't a class, it's a class template. On and on it goes.

You can't escape the templates in the standard library, but if you're used to working with only streams andstrings of chars, you can mostly ignore them. That's because the library defines typedefs for char instantiationsfor these components of the library, thus letting you continue to program in terms of the objects cin, cout, cerr,etc., and the types istream, ostream, string, etc., without having to worry about the fact that cin's real type isbasic_istream<char> and string's is basic_string<char>.

Many components in the standard library are templatized much more than this suggests. Consider again theseemingly straightforward notion of a string. Sure, it can be parameterized based on the type of characters itholds, but different character sets differ in details, e.g., special end-of-file characters, most efficient way ofcopying arrays of them, etc. Such characteristics are known in the standard as traits, and they are specified forstring instantiations by an additional template parameter. In addition, string objects are likely to performdynamic memory allocation and deallocation, but there are lots of different ways to approach that task (see Item10). Which is best? You get to choose: the string template takes an Allocator parameter, and objects of typeAllocator are used to allocate and deallocate the memory used by string objects.

Here's a full-blown declaration for the basic_string template and the string typedef that builds on it; you can findthis (or something equivalent to it) in the header <string>:

namespace std {

template<class charT,

class traits = char_traits<charT>,

class Allocator = allocator<charT> >

class basic_string;

typedef basic_string<char> string;

}

Notice how basic_string has default values for its traits and Allocator parameters. This is typical of thestandard library. It offers flexibility to those who need it, but "typical" clients who just want to do the "normal"thing can ignore the complexity that makes possible the flexibility. In other words, if you just want string objectsthat act more or less like C strings, you can use string objects and remain merrily ignorant of the fact that you'rereally using objects of type basic_string<char, char_traits<char>, allocator<char> >.

Well, usually you can. Sometimes you have to peek under the hood a bit. For example, Item 34 discusses theadvantages of declaring a class without providing its definition, and it remarks that the following is the wrongway to declare the string type:

class string; // this will compile, but

// you don't want to do it

Setting aside namespace considerations for a moment, the real problem here is that string isn't a class, it's atypedef. It would be nice if you could solve the problem this way:

typedef basic_string<char> string;

but that won't compile. "What is this basic_string of which you speak?," your compilers will wonder, thoughthey'll probably phrase the question rather differently. No, to declare string, you would first have to declare allthe templates on which it depends. If you could do it, it would look something like this:

template<class charT> struct char_traits;

template<class T> class allocator;

template<class charT,

Page 189: Effective C++ and More Effective C++

class traits = char_traits<charT>,

class Allocator = allocator<charT> >

class basic_string;

typedef basic_string<char> string;

However, you can't declare string. At least you shouldn't. That's because library implementers are allowed todeclare string (or anything else in the std namespace) differently from what's specified in °the standard as long asthe result offers standard-conforming behavior. For example, a basic_string implementation could add a fourthtemplate parameter, but that parameter's default value would have to yield code that acts as the standard says anunadorned basic_string must.

End result? Don't try to manually declare string (or any other part of the standard library). Instead, just includethe appropriate header, e.g. <string>.

With this background on headers and templates under our belts, we're in a position to survey the primarycomponents of the standard C++ library:

The standard C library. It's still there, and you can still use it. A few minor things have been tweakedhere and there, but for all intents and purposes, it's the same C library that's been around for years.

Iostreams. Compared to "traditional" iostream implementations, it's been templatized, its inheritancehierarchy has been modified, it's been augmented with the ability to throw exceptions, and it's beenupdated to support strings (via the stringstream classes) and internationalization (via locales ? see below).Still, most everything you've come to expect from the iostream library continues to exist. In particular, itstill supports stream buffers, formatters, manipulators, and files, plus the objects cin, cout, cerr, and clog.That means you can treat strings and files as streams, and you have extensive control over streambehavior, including buffering and formatting.

Strings. string objects were designed to eliminate the need to use char* pointers in most applications.They support the operations you'd expect (e.g., concatenation, constant-time access to individualcharacters via operator[], etc.), they're convertible to char*s for compatibility with legacy code, and theyhandle memory management automatically. Some string implementations employ reference counting (see Item M29), which can lead to better performance (in both time and space) than char*-based strings.

Containers. Stop writing your own basic container classes! The library offers efficient implementationsof vectors (they act like dynamically extensible arrays), lists (doubly-linked), queues, stacks, deques,maps, sets, and bitsets. Alas, there are no hash tables in the library (though many vendors offer them asextensions), but compensating somewhat is the fact that strings are containers. That's important, because itmeans anything you can do to a container (see below), you can also do to a string.

What's that? You want to know how I know the library implementations are efficient? Easy: the libraryspecifies each class's interface, and part of each interface specification is a set of performance guarantees.So, for example, no matter how vector is implemented, it's not enough to offer just access to its elements,it must offer constant-time access. If it doesn't, it's not a valid vector implementation.

In many C++ programs, dynamically allocated strings and arrays account for most uses of new and delete,and new/delete errors ? especially leaks caused by failure to delete newed memory ? are distressinglycommon. If you use string and vector objects (both of which perform their own memory management)instead of char*s and pointers to dynamically allocated arrays, many of your news and deletes will vanish,and so will the difficulties that frequently accompany their use (e.g., Items 6 and 11).

Algorithms. Having standard containers is nice, but it's even nicer when there's an easy way to do thingswith them. The standard library offers over two dozen easy ways (i.e., predefined functions, officiallyknown as algorithms ? they're really function templates), most of which work with all the containers in thelibrary ? as well as with built-in arrays!

Algorithms treat the contents of a container as a sequence, and each algorithm may be applied to either thesequence corresponding to all the values in a container or to a subsequence. Among the standardalgorithms are for_each (apply a function to each element of a sequence), find (find the first location in asequence holding a given value ? Item M35 shows its implementation), count_if (count the number of

Page 190: Effective C++ and More Effective C++

elements in a sequence for which a given predicate is true), equal (determine whether two sequences holdequal-valued elements), search (find the first position in one sequence where a second sequence occurs asa subsequence), copy (copy one sequence into another), unique (remove duplicate values from asequence), rotate (rotate the values in a sequence) and sort (sort the values in a sequence). Note that this isjust a sampling of the algorithms available; the library contains many others.

Just as container operations come with performance guarantees, so do algorithms. For example, thestable_sort algorithm is required to perform no more than O(N log N) comparisons. (If the "Big O"notation in the previous sentence is foreign to you, don't sweat it. What it really means is that, broadlyspeaking, stable_sort must offer performance at the same level as the most efficient general-purpose serialsorting algorithms.)

Support for internationalization. Different cultures do things in different ways. Like the C library, theC++ library offers features to facilitate the production of internationalized software, but the C++approach, though conceptually akin to that of C, is different. It should not surprise you, for example, tolearn that C++'s support for internationalization makes extensive use of templates, and it takes advantageof inheritance and virtual functions, too.

The primary library components supporting internationalization are facets and locales. Facets describehow particular characteristics of a culture should be handled, including collation rules (i.e., how strings inthe local character set should be sorted), how dates and times should be expressed, how numeric andmonetary values should be presented, how to map from message identifiers to (natural) language-specificmessages, etc. Locales bundle together sets of facets. For example, a locale for the United States wouldinclude facets describing how to sort strings in American English, read and write dates and times, readand write monetary and numeric values, etc., in a way appropriate for people in the USA. A locale forFrance, on the other hand, would describe how to perform these tasks in a manner to which the French areaccustomed. C++ allows multiple locales to be active within a single program, so different parts of anapplication may employ different conventions.

Support for numeric processing. The end for FORTRAN may finally be near. The C++ library offers atemplate for complex number classes (the precision of the real and imaginary parts may be float, double,or long double) as well as for special array types specifically designed to facilitate numeric programming.Objects of type valarray, for example, are defined to hold elements that are free from aliasing. This allowscompilers to be much more aggressive in their optimizations, especially for vector machines. The libraryalso offers support for two different types of array slices, as well as providing algorithms to computeinner products, partial sums, adjacent differences, and more.

Diagnostic support. The standard library offers support for three ways to report errors: via C's assertions(see Item 7), via error numbers, and via exceptions. To help provide some structure to exception types, thelibrary defines the following hierarchy of exception classes:

Page 191: Effective C++ and More Effective C++
Page 192: Effective C++ and More Effective C++

Exceptions of type logic_error (or its subclasses) represent errors in the logic of software. In theory, sucherrors could have been prevented by more careful programming. Exceptions of type runtime_error (or itsderived classes) represent errors detectable only at runtime.

You may use these classes as is, you may inherit from them to create your own exception classes, or youmay ignore them. Their use is not mandatory.

This list doesn't describe everything in the standard library. Remember, the specification runs over 300 pages.Still, it should give you the basic lay of the land.

The part of the library pertaining to containers and algorithms is commonly known as Standard TemplateLibrary (the STL ? see Item M35). There is actually a third component to the STL ? Iterators ? that I haven'tdescribed. Iterators are pointer-like objects that allow STL algorithms and containers to work together. Youneed not understand iterators for the high-level description of the standard library I give here. If you're interestedin them, however, you can find examples of their use in Items 39 and M35.

The STL is the most revolutionary part of the standard library, not because of the containers and algorithms itoffers (though they are undeniably useful), but because of its architecture. Simply put, the architecture isextensible: you can add to the STL. Of course, the components of the standard library itself are fixed, but if youfollow the conventions on which the STL is built, you can write your own containers, algorithms, and iteratorsthat work as well with the standard STL components as the STL components work with one another. You canalso take advantage of STL-compliant containers, algorithms, and iterators written by others, just as they cantake advantage of yours. What makes the STL revolutionary is that it's not really software, it's a set of conventions. The STL components in the standard library are simply manifestations of the good that can come

Page 193: Effective C++ and More Effective C++

from following those conventions.

By using the components in the standard library, you can generally dispense with designing your ownfrom-the-ground-up mechanisms for stream I/O, strings, containers (including iteration and commonmanipulations), internationalization, numeric data structures, and diagnostics. That leaves you a lot more timeand energy for the really important part of software development: implementing the things that distinguish yourwares from those of your competitors.

Back to Item 48: Pay attention to compiler warnings. Continue to Item 50: Improve your understanding of C++.

Page 194: Effective C++ and More Effective C++

Back to Item 49: Familiarize yourself with the standard library. Continue to Afterword

Item 50: Improve your understanding of C++.

There's a lot of stuff in C++. C stuff. Overloading stuff. Object-oriented stuff. Template stuff. Exception stuff.Namespace stuff. Stuff, stuff, stuff! Sometimes it can be overwhelming. How do you make sense of all that stuff?

It's not that hard once you understand the design goals that forged C++ into what it is. Foremost amongst thosegoals are the following:

Compatibility with C. Lots and lots of C exists, as do lots and lots of C programmers. C++ takesadvantage of and builds on ? er, I mean it "leverages" ? that base.

Efficiency. °Bjarne Stroustrup, the designer and first implementer of C++, knew from the outset that the Cprogrammers he hoped to win over wouldn't look twice if they had to pay a performance penalty forswitching languages. As a result, he made sure C++ was competitive with C when it came to efficiency ?like within 5%.

Compatibility with traditional tools and environments. Fancy development environments run here andthere, but compilers, linkers, and editors run almost everywhere. C++ is designed to work in environmentsfrom mice to mainframes, so it brings along as little baggage as possible. You want to port C++? You porta language and take advantage of existing tools on the target platform. (However, it is often possible toprovide a better implementation if, for example, the linker can be modified to address some of the moredemanding aspects of inlining and templates.)

Applicability to real problems. C++ wasn't designed to be a nice, pure language, good for teachingstudents how to program, it was designed to be a powerful tool for professional programmers solving realproblems in diverse domains. The real world has some rough edges, so it's no surprise there's theoccasional scratch marring the finish of the tools on which the pros rely.

These goals explain a multitude of language details that might otherwise merely chafe. Why doimplicitly-generated copy constructors and assignment operators behave the way they do, especially for pointers(see Items 11 and 45)? Because that's how C copies and assigns structs, and compatibility with C is important.Why aren't destructors automatically virtual (see Item 14), and why must implementation details appear in classdefinitions (see Item 34)? Because doing otherwise would impose a performance penalty, and efficiency isimportant. Why can't C++ detect initialization dependencies between non-local static objects (see Item 47)?Because C++ supports separate translation (i.e., the ability to compile source modules separately, then linkseveral object files together to form an executable), relies on existing linkers, and doesn't mandate the existenceof program databases. As a result, C++ compilers almost never know everything about an entire program.Finally, why doesn't C++ free programmers from tiresome duties like memory management (see Items 5-10) andlow-level pointer manipulations? Because some programmers need those capabilities, and the needs of realprogrammers are of paramount importance.

This barely hints at how the design goals behind C++ shape the behavior of the language. To cover everythingwould take an entire book, so it's convenient that Stroustrup wrote one. That book is °The Design and Evolutionof C++ (Addison-Wesley, 1994), sometimes known as simply "D&E." Read it, and you'll see what featureswere added to C++, in what order, and why. You'll also learn about features that were rejected, and why. You'lleven get the inside story on how the dynamic_cast feature (see Items 39 and M2) was considered, rejected,reconsidered, then accepted ? and why. If you're having trouble making sense of C++, D&E should dispel muchof your confusion.

The Design and Evolution of C++ offers a wealth of insights into how C++ came to be what it is, but it'snothing like a formal specification for the language. For that you must turn to the °international standard for C++,an impressive exercise in formalese running some 700 pages. There you can read such riveting prose as this:

A virtual function call uses the default arguments in the declaration of the virtual function determined by thestatic type of the pointer or reference denoting the object. An overriding function in a derived class does notacquire default arguments from the function it overrides.

This paragraph is the basis for Item 38 ("Never redefine an inherited default parameter value"), but I hope mytreatment of the topic is somewhat more accessible than the text above.

Page 195: Effective C++ and More Effective C++

The standard is hardly bedtime reading, but it's your best recourse ? your standard recourse ? if you andsomeone else (a compiler vendor, say, or a developer of some other tool that processes source code) disagreeon what is and isn't C++. The whole purpose of a standard is to provide definitive information that settlesarguments like that.

The standard's official title is a mouthful, but if you need to know it, you need to know it. Here it is: International Standard for Information Systems?Programming Language C++. It's published by WorkingGroup 21 of the °International Organization for Standardization (ISO). (If you insist on being picky about it, it'sreally published by ? I am not making this up ? ISO/IEC JTC1/SC22/WG21.) You can order a copy of theofficial standard from your national standards body (in the United States, that's ANSI, the °American NationalStandards Institute), but copies of late drafts of the standard ? which are quite similar (though not identical) tothe final document ? are freely available on the World Wide Web. A good place to look for a copy is at °theCygnus Solutions Draft Standard C++ Page, but given the pace of change in cyberspace, don't be surprised if thislink is broken by the time you try it. If it is, your favorite Web search engine will doubtless turn up a URL thatworks.

As I said, The Design and Evolution of C++ is fine for insights into the language's design, and the standard isgreat for nailing down language details, but it would be nice if there were a comfortable middle ground betweenD&E's view from 10,000 meters and the standard's micron-level examination. Textbooks are supposed to fillthis niche, but they generally drift toward the standard's perspective, whereby what the language is receives a lotmore attention than why it's that way.

Enter the ARM. The ARM is another book, °The Annotated C++ Reference Manual, by Margaret Ellis and °Bjarne Stroustrup (Addison-Wesley, 1990). Upon its publication, it became the authority on C++, and theinternational standard started with the ARM (along with the existing C standard) as its basis. In the interveningyears, the language specified by the standard has in some ways parted company with that described by the ARM,so the ARM is no longer the authority it once was. It's still a useful reference, however, because most of what itsays is still true, and it's not uncommon for vendors to adhere to the ARM specification in areas of C++ wherethe standard has only recently settled down.

What makes the ARM really useful, however, isn't the RM part (the Reference Manual), it's the A part: theannotations. The ARM provides extensive commentary on why many features of C++ behave the way they do.Some of this information is in D&E, but much of it isn't, and you do want to know it. For instance, here'ssomething that drives most people crazy when they first encounter it:

class Base {

public:

virtual void f(int x);

};

class Derived: public Base {

public:

virtual void f(double *pd);

};

Derived *pd = new Derived;

pd->f(10); // error!

The problem is that Derived::f hides Base::f, even though they take different parameter types, so compilersdemand that the call to f take a double*, which the literal 10 most certainly is not.

This is inconvenient, but the ARM provides an explanation for this behavior. Suppose that when you called f,you really did want to call the version in Derived, but you accidentally used the wrong parameter type. Furthersuppose that Derived is way down in an inheritance hierarchy and that you were unaware that Derived indirectlyinherits from some base class BaseClass, and that BaseClass declares a virtual function f that takes an int. In thatcase, you would have inadvertently called BaseClass::f, a function you didn't even know existed! This kind oferror could occur frequently where large class hierarchies are used, so Stroustrup decided to nip it in the bud byhaving derived class members hide base class members on a per-name basis.

Note, by the way, that if the writer of Derived wants to allow clients to access Base::f, this is easily

Page 196: Effective C++ and More Effective C++

accomplished via a simple using declaration: class Derived: public Base {

public:

using Base::f; // import Base::f into

// Derived's scope

virtual void f(double *pd);

};

Derived *pd = new Derived;

pd->f(10); // fine, calls Base::f

For compilers not yet supporting using declarations, an alternative is to employ an inline function: class Derived: public Base {

public:

virtual void f(int x) { Base::f(x); }

virtual void f(double *pd);

};

Derived *pd = new Derived;

pd->f(10); // fine, calls Derived::f(int),

// which calls Base::f(int)

Between D&E and the ARM, you'll gain insights into the design and implementation of C++ that make itpossible to appreciate the sound, no-nonsense architecture behind a sometimes baroque-looking facade. Fortifythose insights with the detailed information in the standard, and you've got a foundation for softwaredevelopment that leads to truly effective C++.

Back to Item 49: Familiarize yourself with the standard library. Continue to Afterword

Page 197: Effective C++ and More Effective C++

Back to Item 50: Improve your understanding of C++. Continue to Index

Afterword

If, having digested 50 ways to improve your programs and designs, you still find yourself hungry for C++guidelines, you may be interested in my second book on the subject, More Effective C++: 35 New Ways toImprove Your Programs and Design. Like Effective C++, More Effective C++ covers material that's essentialfor effective C++ software development, but Effective C++ focuses more on fundamentals, while MoreEffective C++ also spends time on newer language features and on advanced programming techniques.

You can find detailed information on More Effective C++ ? including four complete Items, the book's list ofrecommended reading, and more ? at the °More Effective C++ web site. In case you can't wait, the contents ofMore Effective C++ are summarized below. DedicationAcknowledgmentsIntroduction

BasicsItem 1: Distinguish between

pointers andreferences

Item 2: Prefer C++-stylecasts

Item 3: Never treat arrayspolymorphically

Item 4: Avoid gratuitousdefault constructors

OperatorsItem 5: Be wary of

user-definedconversion functions

Item 6: Distinguish betweenprefix and postfixforms of increment anddecrement operators

Item 7: Never overload &&, ||,or ,

Item 8: Understand thedifferent meanings ofnew and delete

Page 198: Effective C++ and More Effective C++

ExceptionsItem 9: Use destructors to

prevent resourceleaks

Item 10: Prevent resource leaksin constructors

Item 11: Prevent exceptionsfrom leavingdestructors

Item 12: Understand howthrowing an exceptiondiffers from passing aparameter or calling avirtual function

Item 13: Catch exceptions byreference

Item 14: Use exceptionspecificationsjudiciously

Item 15: Understand the costsof exception handling

EfficiencyItem 16: Remember the 80-20

rule Item 17: Consider using lazy

evaluation Item 18: Amortize the cost of

expectedcomputations

Item 19: Understand the originof temporary objects

Item 20: Facilitate the returnvalue optimization

Item 21: Overload to avoidimplicit typeconversions

Item 22: Consider using op=instead of stand-alone op

Item 23: Consider alternativelibraries

Item 24: Understand the costsof virtual functions,multiple inheritance,virtual base classes,and RTTI

Page 199: Effective C++ and More Effective C++

TechniquesItem 25: Virtualizing

constructors andnon-member functions

Item 26: Limiting the number ofobjects of a class

Item 27: Requiring orprohibitingheap-based objects

Item 28: Smart pointers Item 29: Reference counting Item 30: Proxy classes Item 31: Making functions

virtual with respect tomore than one object

MiscellanyItem 32: Program in the future

tense Item 33: Make non-leaf classes

abstract Item 34: Understand how to

combine C++ and C inthe same program

Item 35: Familiarize yourselfwith the languagestandard

Recommended ReadingAn auto_ptr ImplementationGeneral Index

Back to Item 50: Improve your understanding of C++. Continue to Index

Page 200: Effective C++ and More Effective C++

Effective C++ by Scott Meyers

Page 201: Effective C++ and More Effective C++

Dedication

For Clancy, my favorite enemy within. Continue to Acknowledgements

Page 202: Effective C++ and More Effective C++

Back to Dedication Continue to Introduction

Acknowledgments

A great number of people helped bring this book into existence. Some contributed ideas for technical topics,some helped with the process of producing the book, and some just made life more fun while I was working onit.

When the number of contributors to a book is large, it is not uncommon to dispense with individualacknowledgments in favor of a generic "Contributors to this book are too numerous to mention." I prefer tofollow the expansive lead of John L. Hennessy and David A. Patterson in °Computer Architecture: AQuantitative Approach (Morgan Kaufmann, 1995). In addition to motivating the comprehensiveacknowledgments that follow, their book provides hard data for the 90-10 rule, which I refer to in Item 16.

The Items

With the exception of direct quotations, all the words in this book are mine. However, many of the ideas Idiscuss came from others. I have done my best to keep track of who contributed what, but I know I have includedinformation from sources I now fail to recall, foremost among them many posters to the Usenet newsgroups °comp.lang.c++ and °comp.std.c++.

Many ideas in the C++ community have been developed independently by many people. In what follows, I noteonly where I was exposed to particular ideas, not necessarily where those ideas originated.

Brian Kernighan suggested the use of macros to approximate the syntax of the new C++ casting operators Idescribe in Item 2.

In Item 3, my warning about deleting an array of derived class objects through a base class pointer is based onmaterial in Dan Saks' "Gotchas" talk, which he's given at several conferences and trade shows.

In Item 5, the proxy class technique for preventing unwanted application of single-argument constructors isbased on material in Andrew Koenig's column in the January 1994 °C++ Report.

James Kanze made a posting to °comp.lang.c++ on implementing postfix increment and decrement operators viathe corresponding prefix functions; I use his technique in Item 6.

David Cok, writing me about material I covered in Effective C++, brought to my attention the distinctionbetween operator new and the new operator that is the crux of Item 8. Even after reading his letter, I didn't reallyunderstand the distinction, but without his initial prodding, I probably still wouldn't.

The notion of using destructors to prevent resource leaks (used in Item 9) comes from section 15.3 of MargaretA. Ellis' and Bjarne Stroustrup's °The Annotated C++ Reference Manual (see page 285). There the technique iscalled resource acquisition is initialization. Tom Cargill suggested I shift the focus of the approach fromresource acquisition to resource release.

Some of my discussion in Item 11 was inspired by material in Chapter 4 of °Taligent's Guide to DesigningPrograms (Addison-Wesley, 1994).

My description of over-eager memory allocation for the DynArray class in Item 18 is based on Tom Cargill'sarticle, "A Dynamic vector is harder than it looks," in the June 1992 °C++ Report. A more sophisticated designfor a dynamic array class can be found in Cargill's follow-up column in the January 1994 °C++ Report.

Item 21 was inspired by Brian Kernighan's paper, "An AWK to C++ Translator," at the 1991 USENIX C++Conference. His use of overloaded operators (sixty-seven of them!) to handle mixed-type arithmetic operations,though designed to solve a problem unrelated to the one I explore in Item 21, led me to consider multipleoverloadings as a solution to the problem of temporary creation.

In Item 26, my design of a template class for counting objects is based on a posting to °comp.lang.c++ by

Page 203: Effective C++ and More Effective C++

Jamshid Afshar.

The idea of a mixin class to keep track of pointers from operator new (see Item 27) is based on a suggestion byDon Box. Steve Clamage made the idea practical by explaining how dynamic_cast can be used to find thebeginning of memory for an object.

The discussion of smart pointers in Item 28 is based in part on Steven Buroff's and Rob Murray's C++ Oraclecolumn in the October 1993 °C++ Report; on Daniel R. Edelson's classic paper, "Smart Pointers: They'reSmart, but They're Not Pointers," in the proceedings of the 1992 USENIX C++ Conference; on section 15.9.1 ofBjarne Stroustrup's °The Design and Evolution of C++ (see page 285); on Gregory Colvin's "C++ MemoryManagement" class notes from C/C++ Solutions '95; and on Cay Horstmann's column in the March-April 1993issue of the °C++ Report. I developed some of the material myself, though. Really.

In Item 29, the use of a base class to store reference counts and of smart pointers to manipulate those counts isbased on Rob Murray's discussions of the same topics in sections 6.3.2 and 7.4.2, respectively, of his °C++Strategies and Tactics (see page 286). The design for adding reference counting to existing classes follows thatpresented by Cay Horstmann in his March-April 1993 column in the °C++ Report.

In Item 30, my discussion of lvalue contexts is based on comments in Dan Saks' column in the C User's Journal °C/C++ Users Journal) of January 1993. The observation that non-proxy member functions are unavailable whencalled through proxies comes from an unpublished paper by Cay Horstmann.

The use of runtime type information to build vtbl-like arrays of function pointers (in Item 31) is based on ideasput forward by Bjarne Stroustrup in postings to °comp.lang.c++ and in section 13.8.1 of his °The Design andEvolution of C++ (see page 285).

The material in Item 33 is based on several of my °C++ Report columns in 1994 and 1995. Those columns, inturn, included comments I received from Klaus Kreft about how to use dynamic_cast to implement a virtualoperator= that detects arguments of the wrong type.

Much of the material in Item 34 was motivated by Steve Clamage's article, "Linking C++ with other languages,"in the May 1992 °C++ Report. In that same Item, my treatment of the problems caused by functions like strdupwas motivated by an anonymous reviewer.

The Book

Reviewing draft copies of a book is hard ? and vitally important ? work. I am grateful that so many people werewilling to invest their time and energy on my behalf. I am especially grateful to Jill Huchital, Tim Johnson, BrianKernighan, Eric Nagler, and Chris Van Wyk, as they read the book (or large portions of it) more than once. Inaddition to these gluttons for punishment, complete drafts of the manuscript were read by Katrina Avery, DonBox, Steve Burkett, Tom Cargill, Tony Davis, Carolyn Duby, Bruce Eckel, Read Fleming, Cay Horstmann,James Kanze, Russ Paielli, Steve Rosenthal, Robin Rowe, Dan Saks, Chris Sells, Webb Stacy, Dave Swift,Steve Vinoski, and Fred Wild. Partial drafts were reviewed by Bob Beauchaine, Gerd Hoeren, Jeff Jackson, andNancy L. Urbano. Each of these reviewers made comments that greatly improved the accuracy, utility, andpresentation of the material you find here.

Once the book came out, I received corrections and suggestions from many people. I've listed these sharp-eyedreaders in the order in which I received their missives: Luis Kida, John Potter, Tim Uttormark, Mike Fulkerson,Dan Saks, Wolfgang Glunz, Clovis Tondo, Michael Loftus, Liz Hanks, Wil Evers, Stefan Kuhlins, JimMcCracken, Alan Duchan, John Jacobsma, Ramesh Nagabushnam, Ed Willink, Kirk Swenson, Jack Reeves,Doug Schmidt, Tim Buchowski, Paul Chisholm, Andrew Klein, Eric Nagler, Jeffrey Smith, Sam Bent, OlegShteynbuk, Anton Doblmaier, Ulf Michaelis, Sekhar Muddana, Michael Baker, Yechiel Kimchi, David Papurt,Ian Haggard, Robert Schwartz, David Halpin, Graham Mark, David Barrett, Damian Kanarek, Ron Coutts,Lance Whitesel, Jon Lachelt, Cheryl Ferguson, Munir Mahmood, Klaus-Georg Adams, David Goh, ChrisMorley, and Rainer Baumschlager. Their suggestions allowed me to improve More Effective C++ in updatedprintings (such as this one), and I greatly appreciate their help.

During preparation of this book, I faced many questions about the emerging °ISO/ANSI standard for C++, and Iam grateful to Steve Clamage and Dan Saks for taking the time to respond to my incessant email queries.

Page 204: Effective C++ and More Effective C++

John Max Skaller and Steve Rumsby conspired to get me the HTML for the draft ANSI C++ standard before itwas widely available. Vivian Neou pointed me to the °Netscape WWW browser as a stand-alone HTML viewerunder (16 bit) Microsoft Windows, and I am deeply grateful to the folks at Netscape Communications for makingtheir fine viewer freely available on such a pathetic excuse for an operating system.

Bryan Hobbs and Hachemi Zenad generously arranged to get me a copy of the internal engineering version ofthe °MetaWare C++ compiler so I could check the code in this book using the latest features of the language. CayHorstmann helped me get the compiler up and running in the very foreign world of DOS and DOS extenders.Borland (now °Inprise) provided a beta copy of their most advanced compiler, and Eric Nagler and Chris Sellsprovided invaluable help in testing code for me on compilers to which I had no access.

Without the staff at the Corporate and Professional Publishing Division of Addison-Wesley, there would be nobook, and I am indebted to Kim Dawley, Lana Langlois, Simone Payment, Marty Rabinowitz, Pradeepa Siva,John Wait, and the rest of the staff for their encouragement, patience, and help with the production of this work.

Chris Guzikowski helped draft the back cover copy for this book, and Tim Johnson stole time from his researchon low-temperature physics to critique later versions of that text.

Tom Cargill graciously agreed to make his °C++ Report article on exceptions available.

The People

Kathy Reed was responsible for my introduction to programming; surely she didn't deserve to have to put upwith a kid like me. Donald French had faith in my ability to develop and present C++ teaching materials when Ihad no track record. He also introduced me to John Wait, my editor at Addison-Wesley, an act for which I willalways be grateful. The triumvirate at Beaver Ridge ? Jayni Besaw, Lorri Fields, and Beth McKee ? provideduntold entertainment on my breaks as I worked on the book.

My wife, Nancy L. Urbano, put up with me and put up with me and put up with me as I worked on the book,continued to work on the book, and kept working on the book. How many times did she hear me say we'd dosomething after the book was done? Now the book is done, and we will do those things. She amazes me. I loveher.

Finally, I must acknowledge our puppy, °Persephone, whose existence changed our world forever. Without her,this book would have been finished both sooner and with less sleep deprivation, but also with substantially lesscomic relief.

Back to Dedication Continue to Introduction

Page 205: Effective C++ and More Effective C++

Back to Acknowledgments Continue to Basics

Introduction

These are heady days for C++ programmers. Commercially available less than a decade, C++ has neverthelessemerged as the language of choice for systems programming on nearly all major computing platforms.Companies and individuals with challenging programming problems increasingly embrace the language, and thequestion faced by those who do not use C++ is often when they will start, not if. Standardization of C++ iscomplete, and the breadth and scope of the accompanying library ? which both dwarfs and subsumes that of C ?makes it possible to write rich, complex programs without sacrificing portability or implementing commonalgorithms and data structures from scratch. C++ compilers continue to proliferate, the features they offercontinue to expand, and the quality of the code they generate continues to improve. Tools and environments forC++ development grow ever more abundant, powerful, and robust. Commercial libraries all but obviate theneed to write code in many application areas.

As the language has matured and our experience with it has increased, our needs for information about it havechanged. In 1990, people wanted to know what C++ was. By 1992, they wanted to know how to make it work.Now C++ programmers ask higher-level questions: How can I design my software so it will adapt to futuredemands? How can I improve the efficiency of my code without compromising its correctness or making itharder to use? How can I implement sophisticated functionality not directly supported by the language?

In this book, I answer these questions and many others like them.

This book shows how to design and implement C++ software that is more effective: more likely to behavecorrectly; more robust in the face of exceptions; more efficient; more portable; makes better use of languagefeatures; adapts to change more gracefully; works better in a mixed-language environment; is easier to usecorrectly; is harder to use incorrectly. In short, software that's just better.

The material in this book is divided into 35 Items. Each Item summarizes accumulated wisdom of the C++programming community on a particular topic. Most Items take the form of guidelines, and the explanationaccompanying each guideline describes why the guideline exists, what happens if you fail to follow it, and underwhat conditions it may make sense to violate the guideline anyway.

Items fall into several categories. Some concern particular language features, especially newer features withwhich you may have little experience. For example, Items 9 through 15 are devoted to exceptions (as are themagazine articles by Tom Cargill, Jack Reeves, and Herb Sutter). Other Items explain how to combine thefeatures of the language to achieve higher-level goals. Items 25 through 31, for instance, describe how toconstrain the number or placement of objects, how to create functions that act "virtual" on the type of more thanone object, how to create "smart pointers," and more. Still other Items address broader topics; Items 16 through24 focus on efficiency. No matter what the topic of a particular Item, each takes a no-nonsense approach to thesubject. In More Effective C++, you learn how to use C++ more effectively. The descriptions of languagefeatures that make up the bulk of most C++ texts are in this book mere background information.

An implication of this approach is that you should be familiar with C++ before reading this book. I take forgranted that you understand classes, protection levels, virtual and nonvirtual functions, etc., and I assume you areacquainted with the concepts behind templates and exceptions. At the same time, I don't expect you to be alanguage expert, so when poking into lesser-known corners of C++, I always explain what's going on.

The C++ in More Effective C++

The C++ I describe in this book is the language specified by the °Final Draft International Standard of the °ISO/ANSI standardization committee in November 1997. In all likelihood, this means I use a few features yourcompilers don't yet support. Don't worry. The only "new" feature I assume you have is templates, and templatesare now almost universally available. I use exceptions, too, but that use is largely confined to Items 9 through 15, which are specifically devoted to exceptions. If you don't have access to a compiler offering exceptions, that'sokay. It won't affect your ability to take advantage of the material in the other parts of the book. Furthermore, youshould read Items 9 through 15 even if you don't have support for exceptions, because those items (as well asthe associated articles) examine issues you need to understand in any case.

Page 206: Effective C++ and More Effective C++

I recognize that just because the standardization committee blesses a feature or endorses a practice, there's noguarantee that the feature is present in current compilers or the practice is applicable to existing environments.When faced with a discrepancy between theory (what the committee says) and practice (what actually works), Idiscuss both, though my bias is toward things that work. Because I discuss both, this book will aid you as yourcompilers approach conformance with the standard. It will show you how to use existing constructs toapproximate language features your compilers don't yet support, and it will guide you when you decide totransform workarounds into newly- supported features.

Notice that I refer to your compilers ? plural. Different compilers implement varying approximations to thestandard, so I encourage you to develop your code under at least two compilers. Doing so will help you avoidinadvertent dependence on one vendor's proprietary language extension or its misinterpretation of the standard.It will also help keep you away from the bleeding edge of compiler technology, e.g., from new featuressupported by only one vendor. Such features are often poorly implemented (buggy or slow ? frequently both),and upon their introduction, the C++ community lacks experience to advise you in their proper use. Blazing trailscan be exciting, but when your goal is producing reliable code, it's often best to let others test the waters beforejumping in.

There are two constructs you'll see in this book that may not be familiar to you. Both are relatively recentlanguage extensions. Some compilers support them, but if your compilers don't, you can easily approximate themwith features you do have.

The first construct is the bool type, which has as its values the keywords true and false. If your compilers haven'timplemented bool, there are two ways to approximate it. One is to use a global enum:

enum bool { false, true };

This allows you to overload functions on the basis of whether they take a bool or an int, but it has thedisadvantage that the built-in comparison operators (i.e., ==, <, >=, etc.) still return ints. As a result, code likethe following will not behave the way it's supposed to:

void f(int);

void f(bool);

int x, y;

...

f( x < y ); // calls f(int), but it

// should call f(bool)

The enum approximation may thus lead to code whose behavior changes when you submit it to a compiler thattruly supports bool.

An alternative is to use a typedef for bool and constant objects for true and false: typedef int bool;

const bool false = 0;

const bool true = 1;

This is compatible with the traditional semantics of C and C++, and the behavior of programs using thisapproximation won't change when they're ported to bool-supporting compilers. The drawback is that you can'tdifferentiate between bool and int when overloading functions. Both approximations are reasonable. Choose theone that best fits your circumstances.

The second new construct is really four constructs, the casting forms static_cast, const_cast, dynamic_cast, andreinterpret_cast. If you're not familiar with these casts, you'll want to turn to Item 2 and read all about them. Notonly do they do more than the C-style casts they replace, they do it better. I use these new casting formswhenever I need to perform a cast in this book.

There is more to C++ than the language itself. There is also the standard library (see Item E49). Where possible,I employ the standard string type instead of using raw char* pointers, and I encourage you to do the same. stringobjects are no more difficult to manipulate than char*-based strings, and they relieve you of most

Page 207: Effective C++ and More Effective C++

memory-management concerns. Furthermore, string objects are less susceptible to memory leaks if an exceptionis thrown (see Items 9 and 10). A well-implemented string type can hold its own in an efficiency contest with itschar* equivalent, and it may even do better. (For insight into how this could be, see Item 29.) If you don't haveaccess to an implementation of the standard string type, you almost certainly have access to some string-likeclass. Use it. Just about anything is preferable to raw char*s.

I use data structures from the standard library whenever I can. Such data structures are drawn from the StandardTemplate Library (the "STL" ? see Item 35). The STL includes bitsets, vectors, lists, queues, stacks, maps, sets,and more, and you should prefer these standardized data structures to the ad hoc equivalents you might otherwisebe tempted to write. Your compilers may not have the STL bundled in, but don't let that keep you from using it.Thanks to Silicon Graphics, you can download a free copy that works with many compilers from the °SGI STLweb site.

If you currently use a library of algorithms and data structures and are happy with it, there's no need to switch tothe STL just because it's "standard." However, if you have a choice between using an STL component or writingyour own code from scratch, you should lean toward using the STL. Remember code reuse? STL (and the rest ofthe standard library) has lots of code that is very much worth reusing.

Conventions and Terminology

Any time I mention inheritance in this book, I mean public inheritance (see Item E35). If I don't mean publicinheritance, I'll say so explicitly. When drawing inheritance hierarchies, I depict base-derived relationships bydrawing arrows from derived classes to base classes. For example, here is a hierarchy from Item 31:

This notation is the reverse of the convention I employed in the first (but not the second) edition of EffectiveC++. I'm now convinced that most C++ practitioners draw inheritance arrows from derived to base classes, andI am happy to follow suit. Within such diagrams, abstract classes (e.g., GameObject) are shaded and concreteclasses (e.g., SpaceShip) are unshaded.

Inheritance gives rise to pointers and references with two different types, a static type and a dynamic type. Thestatic type of a pointer or reference is its declared type. The dynamic type is determined by the type of object itactually refers to. Here are some examples based on the classes above:

Page 208: Effective C++ and More Effective C++

GameObject *pgo = // static type of pgo is

new SpaceShip; // GameObject*, dynamic

// type is SpaceShip*

Asteroid *pa = new Asteroid; // static type of pa is

// Asteroid*. So is its

// dynamic type

pgo = pa; // static type of pgo is

// still (and always)

// GameObject*. Its

// dynamic type is now

// Asteroid*

GameObject& rgo = *pa; // static type of rgo is

// GameObject, dynamic

// type is Asteroid

These examples also demonstrate a naming convention I like. pgo is a pointer-to-GameObject; pa is apointer-to-Asteroid; rgo is a reference-to-GameObject. I often concoct pointer and reference names in thisfashion.

Two of my favorite parameter names are lhs and rhs, abbreviations for "left-hand side" and "right-hand side,"respectively. To understand the rationale behind these names, consider a class for representing rationalnumbers:

class Rational { ... };

If I wanted a function to compare pairs of Rational objects, I'd declare it like this: bool operator==(const Rational& lhs, const Rational& rhs);

That would let me write this kind of code: Rational r1, r2;

...

if (r1 == r2) ...

Within the call to operator==, r1 appears on the left-hand side of the "==" and is bound to lhs, while r2 appearson the right-hand side of the "==" and is bound to rhs.

Other abbreviations I employ include ctor for "constructor," dtor for "destructor," and RTTI for C++'s supportfor runtime type identification (of which dynamic_cast is the most commonly used component).

When you allocate memory and fail to free it, you have a memory leak. Memory leaks arise in both C and C++,but in C++, memory leaks leak more than just memory. That's because C++ automatically calls constructorswhen objects are created, and constructors may themselves allocate resources. For example, consider this code:

class Widget { ... }; // some class ? it doesn't

// matter what it is

Widget *pw = new Widget; // dynamically allocate a

// Widget object

... // assume pw is never

// deleted

This code leaks memory, because the Widget pointed to by pw is never deleted. However, if the Widgetconstructor allocates additional resources that are to be released when the Widget is destroyed (such as filedescriptors, semaphores, window handles, database locks, etc.), those resources are lost just as surely as thememory is. To emphasize that memory leaks in C++ often leak other resources, too, I usually speak of resourceleaks in this book rather than memory leaks.

You won't see many inline functions in this book. That's not because I dislike inlining. Far from it, I believe thatinline functions are an important feature of C++. However, the criteria for determining whether a function shouldbe inlined can be complex, subtle, and platform-dependent (see Item E33). As a result, I avoid inlining unless

Page 209: Effective C++ and More Effective C++

there is a point about inlining I wish to make. When you see a non-inline function in More Effective C++, thatdoesn't mean I think it would be a bad idea to declare the function inline, it just means the decision to inline thatfunction is independent of the material I'm examining at that point in the book.

A few C++ features have been deprecated by the °standardization committee. Such features are slated foreventual removal from the language, because newer features have been added that do what the deprecatedfeatures do, but do it better. In this book, I identify deprecated constructs and explain what features replace them.You should try to avoid deprecated features where you can, but there's no reason to be overly concerned abouttheir use. In the interest of preserving backward compatibility for their customers, compiler vendors are likely tosupport deprecated features for many years.

A client is somebody (a programmer) or something (a class or function, typically) that uses the code you write.For example, if you write a Date class (for representing birthdays, deadlines, when the Second Coming occurs,etc.), anybody using that class is your client. Furthermore, any sections of code that use the Date class are yourclients as well. Clients are important. In fact, clients are the name of the game! If nobody uses the software youwrite, why write it? You will find I worry a lot about making things easier for clients, often at the expense ofmaking things more difficult for you, because good software is "clientcentric" ? it revolves around clients. If thisstrikes you as unreasonably philanthropic, view it instead through a lens of self-interest. Do you ever use theclasses or functions you write? If so, you're your own client, so making things easier for clients in general alsomakes them easier for you.

When discussing class or function templates and the classes or functions generated from them, I reserve the rightto be sloppy about the difference between the templates and their instantiations. For example, if Array is a classtemplate taking a type parameter T, I may refer to a particular instantiation of the template as an Array, eventhough Array<T> is really the name of the class. Similarly, if swap is a function template taking a type parameterT, I may refer to an instantiation as swap instead of swap<T>. In cases where this kind of shorthand might beunclear, I include template parameters when referring to template instantiations.

Reporting Bugs, Making Suggestions, Getting Book Updates

I have tried to make this book as accurate, readable, and useful as possible, but I know there is room forimprovement. If you find an error of any kind ? technical, grammatical, typographical, whatever ? please tell meabout it. I will try to correct the mistake in future printings of the book, and if you are the first person to report it,I will gladly add your name to the book's acknowledgments. If you have other suggestions for improvement, Iwelcome those, too.

I continue to collect guidelines for effective programming in C++. If you have ideas for new guidelines, I'd bedelighted if you'd share them with me. Send your guidelines, your comments, your criticisms, and your bugreports to: Scott Meyers c/o Editor-in-Chief, Corporate and Professional Publishing Addison-Wesley Publishing Company1 Jacob Way Reading, MA 01867 U. S. A.

Alternatively, you may send electronic mail to [email protected].

I maintain a list of changes to this book since its first printing, including bug-fixes, clarifications, and technicalupdates. This list, along with other book-related information, is available from the °Web site for this book. It isalso available via anonymous FTP from °ftp.awl.com in the directory cp/mec++. If you would like a copy of thelist of changes to this book, but you lack access to the Internet, please send a request to one of the addressesabove, and I will see that the list is sent to you.

Enough preliminaries. On with the show! Back to Acknowledgments

Continue to Basics

Page 210: Effective C++ and More Effective C++

Back to Introduction Continue to Item 1: Distinguish between pointers and references

Basics

Ah, the basics. Pointers, references, casts, arrays, constructors ? you can't get much more basic than that. All butthe simplest C++ programs use most of these features, and many programs use them all.

In spite of our familiarity with these parts of the language, sometimes they can still surprise us. This isespecially true for programmers making the transition from C to C++, because the concepts behind references,dynamic casts, default constructors, and other non-C features are usually a little murky.

This chapter describes the differences between pointers and references and offers guidance on when to use each.It introduces the new C++ syntax for casts and explains why the new casts are superior to the C-style casts theyreplace. It examines the C notion of arrays and the C++ notion of polymorphism, and it describes why mixing thetwo is an idea whose time will never come. Finally, it considers the pros and cons of default constructors andsuggests ways to work around language restrictions that encourage you to have one when none makes sense.

By heeding the advice in the items that follow, you'll make progress toward a worthy goal: producing softwarethat expresses your design intentions clearly and correctly.

Back to Introduction Continue to Item 1: Distinguish between pointers and references

Page 211: Effective C++ and More Effective C++

Back to Basics Continue to Item 2: Prefer C++-style casts

Item 1: Distinguish between pointers and references.

Pointers and references look different enough (pointers use the "*" and "->" operators, references use "."), butthey seem to do similar things. Both pointers and references let you refer to other objects indirectly. How, then,do you decide when to use one and not the other?

First, recognize that there is no such thing as a null reference. A reference must always refer to some object. Asa result, if you have a variable whose purpose is to refer to another object, but it is possible that there might notbe an object to refer to, you should make the variable a pointer, because then you can set it to null. On the otherhand, if the variable must always refer to an object, i.e., if your design does not allow for the possibility that thevariable is null, you should probably make the variable a reference.

"But wait," you wonder, "what about underhandedness like this?" char *pc = 0; // set pointer to null

char& rc = *pc; // make reference refer to

// dereferenced null pointer

Well, this is evil, pure and simple. The results are undefined (compilers can generate output to do anything theylike), and people who write this kind of code should be shunned until they agree to cease and desist. If you haveto worry about things like this in your software, you're probably best off avoiding references entirely. Either thator finding a better class of programmers to work with. We'll henceforth ignore the possibility that a referencecan be "null."

Because a reference must refer to an object, C++ requires that references be initialized: string& rs; // error! References must

// be initialized

string s("xyzzy");

string& rs = s; // okay, rs refers to s

Pointers are subject to no such restriction: string *ps; // uninitialized pointer:

// valid but risky

The fact that there is no such thing as a null reference implies that it can be more efficient to use references thanto use pointers. That's because there's no need to test the validity of a reference before using it:

void printDouble(const double& rd)

{

cout << rd; // no need to test rd; it

} // must refer to a double

Pointers, on the other hand, should generally be tested against null: void printDouble(const double *pd)

{

if (pd) { // check for null pointer

cout << *pd;

}

}

Another important difference between pointers and references is that pointers may be reassigned to refer todifferent objects. A reference, however, always refers to the object with which it is initialized:

string s1("Nancy");

string s2("Clancy");

string& rs = s1; // rs refers to s1

string *ps = &s1; // ps points to s1

Page 212: Effective C++ and More Effective C++

rs = s2; // rs still refers to s1,

// but s1's value is now

// "Clancy"

ps = &s2; // ps now points to s2;

// s1 is unchanged

In general, you should use a pointer whenever you need to take into account the possibility that there's nothing torefer to (in which case you can set the pointer to null) or whenever you need to be able to refer to differentthings at different times (in which case you can change where the pointer points). You should use a referencewhenever you know there will always be an object to refer to and you also know that once you're referring tothat object, you'll never want to refer to anything else.

There is one other situation in which you should use a reference, and that's when you're implementing certainoperators. The most common example is operator[]. This operator typically needs to return something that canbe used as the target of an assignment:

vector<int> v(10); // create an int vector of size 10;

// vector is a template in the

// standard C++ library (see Item 35)

v[5] = 10; // the target of this assignment is

// the return value of operator[]

If operator[] returned a pointer, this last statement would have to be written this way: *v[5] = 10;

But this makes it look like v is a vector of pointers, which it's not. For this reason, you'll almost always wantoperator[] to return a reference. (For an interesting exception to this rule, see Item 30.)

References, then, are the feature of choice when you know you have something to refer to, when you'll neverwant to refer to anything else, and when implementing operators whose syntactic requirements make the use ofpointers undesirable. In all other cases, stick with pointers.

Back to Basics Continue to Item 2: Prefer C++-style casts

Page 213: Effective C++ and More Effective C++

Back to Item 1: Distinguish between pointers and references Continue to Item 3: Never treat arrays polymorphically

Item 2: Prefer C++-style casts.

Consider the lowly cast. Nearly as much a programming pariah as the goto, it nonetheless endures, becausewhen worse comes to worst and push comes to shove, casts can be necessary. Casts are especially necessarywhen worse comes to worst and push comes to shove.

Still, C-style casts are not all they might be. For one thing, they're rather crude beasts, letting you cast prettymuch any type to pretty much any other type. It would be nice to be able to specify more precisely the purpose ofeach cast. There is a great difference, for example, between a cast that changes a pointer-to-const-object into apointer-to-non-const-object (i.e., a cast that changes only the constness of an object) and a cast that changes apointer-to-base-class-object into a pointer-to-derived-class-object (i.e., a cast that completely changes anobject's type). Traditional C-style casts make no such distinctions. (This is hardly a surprise. C-style casts weredesigned for C, not C++.)

A second problem with casts is that they are hard to find. Syntactically, casts consist of little more than a pair ofparentheses and an identifier, and parentheses and identifiers are used everywhere in C++. This makes it toughto answer even the most basic cast-related questions, questions like, "Are any casts used in this program?"That's because human readers are likely to overlook casts, and tools like grep cannot distinguish them fromnon-cast constructs that are syntactically similar.

C++ addresses the shortcomings of C-style casts by introducing four new cast operators, static_cast, const_cast,dynamic_cast, and reinterpret_cast. For most purposes, all you need to know about these operators is that whatyou are accustomed to writing like this,

(type) expression

you should now generally write like this: static_cast<type>(expression)

For example, suppose you'd like to cast an int to a double to force an expression involving ints to yield afloating point value. Using C-style casts, you could do it like this:

int firstNumber, secondNumber;

...

double result = ((double)firstNumber)/secondNumber;

With the new casts, you'd write it this way: double result = static_cast<double>(firstNumber)/secondNumber;

Now there's a cast that's easy to see, both for humans and for programs.

static_cast has basically the same power and meaning as the general-purpose C-style cast. It also has the samekind of restrictions. For example, you can't cast a struct into an int or a double into a pointer using static_castany more than you can with a C-style cast. Furthermore, static_cast can't remove constness from an expression,because another new cast, const_cast, is designed specifically to do that.

The other new C++ casts are used for more restricted purposes. const_cast is used to cast away the constness orvolatileness of an expression. By using a const_cast, you emphasize (to both humans and compilers) that the onlything you want to change through the cast is the constness or volatileness of something. This meaning is enforcedby compilers. If you try to employ const_cast for anything other than modifying the constness or volatileness ofan expression, your cast will be rejected. Here are some examples:

class Widget { ... };

class SpecialWidget: public Widget { ... };

void update(SpecialWidget *psw);

Page 214: Effective C++ and More Effective C++

SpecialWidget sw; // sw is a non-const object,

const SpecialWidget& csw = sw; // but csw is a reference to

// it as a const object

update(&csw); // error! can't pass a const

// SpecialWidget* to a function

// taking a SpecialWidget*

update(const_cast<SpecialWidget*>(&csw));

// fine, the constness of &csw is

// explicitly cast away (and

// csw ? and sw ? may now be

// changed inside update)

update((SpecialWidget*)&csw);

// same as above, but using a

// harder-to-recognize C-style cast

Widget *pw = new SpecialWidget;

update(pw); // error! pw's type is Widget*, but

// update takes a SpecialWidget*

update(const_cast<SpecialWidget*>(pw));

// error! const_cast can be used only

// to affect constness or volatileness,

// never to cast down the inheritance

// hierarch

By far the most common use of const_cast is to cast away the constness of an object.

The second specialized type of cast, dynamic_cast, is used to perform safe casts down or across an inheritancehierarchy. That is, you use dynamic_cast to cast pointers or references to base class objects into pointers orreferences to derived or sibling base class objects in such a way that you can determine whether the castssucceeded.1 Failed casts are indicated by a null pointer (when casting pointers) or an exception (when castingreferences):

Widget *pw;

...

update(dynamic_cast<SpecialWidget*>(pw));

// fine, passes to update a pointer

// to the SpecialWidget pw points to

// if pw really points to one,

// otherwise passes the null pointer

void updateViaRef(SpecialWidget& rsw);

updateViaRef(dynamic_cast<SpecialWidget&>(*pw));

// fine, passes to updateViaRef the

// SpecialWidget pw points to if pw

// really points to one, otherwise

// throws an exception

dynamic_casts are restricted to helping you navigate inheritance hierarchies. They cannot be applied to typeslacking virtual functions (see also Item 24), nor can they cast away constness:

int firstNumber, secondNumber;

...

double result = dynamic_cast<double>(firstNumber)/secondNumber;

// error! no inheritance is involved

const SpecialWidget sw;

Page 215: Effective C++ and More Effective C++

...

update(dynamic_cast<SpecialWidget*>(&sw));

// error! dynamic_cast can't cast

// away constness

If you want to perform a cast on a type where inheritance is not involved, you probably want a static_cast. Tocast constness away, you always want a const_cast.

The last of the four new casting forms is reinterpret_cast. This operator is used to perform type conversionswhose result is nearly always implementation-defined. As a result, reinterpret_casts are rarely portable.

The most common use of reinterpret_cast is to cast between function pointer types. For example, suppose youhave an array of pointers to functions of a particular type:

typedef void (*FuncPtr)(); // a FuncPtr is a pointer

// to a function taking no

// args and returning void

FuncPtr funcPtrArray[10]; // funcPtrArray is an array

// of 10 FuncPtrs

Let us suppose you wish (for some unfathomable reason) to place a pointer to the following function intofuncPtrArray:

int doSomething();

You can't do what you want without a cast, because doSomething has the wrong type for funcPtrArray. Thefunctions in funcPtrArray return void, but doSomething returns an int:

funcPtrArray[0] = &doSomething; // error! type mismatch

A reinterpret_cast lets you force compilers to see things your way: funcPtrArray[0] = // this compiles

reinterpret_cast<FuncPtr>(&doSomething);

Casting function pointers is not portable (C++ offers no guarantee that all function pointers are represented thesame way), and in some cases such casts yield incorrect results (see Item 31), so you should avoid castingfunction pointers unless your back's to the wall and a knife's at your throat. A sharp knife. A very sharp knife.

If your compilers lack support for the new casting forms, you can use traditional casts in place of static_cast,const_cast, and reinterpret_cast. Furthermore, you can use macros to approximate the new syntax:

#define static_cast(TYPE,EXPR) ((TYPE)(EXPR))

#define const_cast(TYPE,EXPR) ((TYPE)(EXPR))

#define reinterpret_cast(TYPE,EXPR) ((TYPE)(EXPR))

You'd use the approximations like this: double result = static_cast(double, firstNumber)/secondNumber;

update(const_cast(SpecialWidget*, &sw));

funcPtrArray[0] = reinterpret_cast(FuncPtr, &doSomething);

These approximations won't be as safe as the real things, of course, but they will simplify the process ofupgrading your code when your compilers support the new casts.

There is no easy way to emulate the behavior of a dynamic_cast, but many libraries provide functions to performsafe inheritance-based casts for you. If you lack such functions and you must perform this type of cast, you canfall back on C-style casts for those, too, but then you forego the ability to tell if the casts fail. Needless to say,

Page 216: Effective C++ and More Effective C++

you can define a macro to look like dynamic_cast, just as you can for the other casts: #define dynamic_cast(TYPE,EXPR) (TYPE)(EXPR)

Remember that this approximation is not performing a true dynamic_cast; there is no way to tell if the cast fails.

I know, I know, the new casts are ugly and hard to type. If you find them too unpleasant to look at, take solace inthe knowledge that C-style casts continue to be valid. However, what the new casts lack in beauty they make upfor in precision of meaning and easy recognizability. Programs that use the new casts are easier to parse (bothfor humans and for tools), and they allow compilers to diagnose casting errors that would otherwise goundetected. These are powerful arguments for abandoning C-style casts, and there may also be a third: perhapsmaking casts ugly and hard to type is a good thing.

Back to Item 1: Distinguish between pointers and references Continue to Item 3: Never treat arrays polymorphically

1 A second, unrelated use of dynamic_cast is to find the beginning of the memory occupied by an object. Weexplore that capability in Item 27. Return

Page 217: Effective C++ and More Effective C++

Back to Item 2: Prefer C++-style casts Continue to Item 4: Avoid gratuitous default constructors

Item 3: Never treat arrays polymorphically.

One of the most important features of inheritance is that you can manipulate derived class objects throughpointers and references to base class objects. Such pointers and references are said to behave polymorphically? as if they had multiple types. C++ also allows you to manipulate arrays of derived class objects through baseclass pointers and references. This is no feature at all, because it almost never works the way you want it to.

For example, suppose you have a class BST (for binary search tree objects) and a second class, BalancedBST,that inherits from BST:

class BST { ... };

class BalancedBST: public BST { ... };

In a real program such classes would be templates, but that's unimportant here, and adding all the templatesyntax just makes things harder to read. For this discussion, we'll assume BST and BalancedBST objects containonly ints.

Consider a function to print out the contents of each BST in an array of BSTs:

void printBSTArray(ostream& s,

const BST array[],

int numElements)

{

for (int i = 0; i < numElements; ++i) {

s << array[i]; // this assumes an

} // operator<< is defined

} // for BST objects

This will work fine when you pass it an array of BST objects: BST BSTArray[10];

...

printBSTArray(cout, BSTArray, 10); // works fine

Consider, however, what happens when you pass printBSTArray an array of BalancedBST objects: BalancedBST bBSTArray[10];

...

printBSTArray(cout, bBSTArray, 10); // works fine?

Your compilers will accept this function call without complaint, but look again at the loop for which they mustgenerate code:

for (int i = 0; i < numElements; ++i) {

s << array[i];

}

Now, array[i] is really just shorthand for an expression involving pointer arithmetic: it stands for *(array+i).We know that array is a pointer to the beginning of the array, but how far away from the memory locationpointed to by array is the memory location pointed to by array+i? The distance between them is i*sizeof(anobject in the array), because there are i objects between array[0] and array[i]. In order for compilers to emitcode that walks through the array correctly, they must be able to determine the size of the objects in the array.This is easy for them to do. The parameter array is declared to be of type array-of-BST, so each element of thearray must be a BST, and the distance between array and array+i must be i*sizeof(BST).

At least that's how your compilers look at it. But if you've passed an array of BalancedBST objects toprintBSTArray, your compilers are probably wrong. In that case, they'd assume each object in the array is the

Page 218: Effective C++ and More Effective C++

size of a BST, but each object would actually be the size of a BalancedBST. Derived classes usually have moredata members than their base classes, so derived class objects are usually larger than base class objects. Wethus expect a BalancedBST object to be larger than a BST object. If it is, the pointer arithmetic generated forprintBSTArray will be wrong for arrays of BalancedBST objects, and there's no telling what will happen whenprintBSTArray is invoked on a BalancedBST array. Whatever does happen, it's a good bet it won't be pleasant.

The problem pops up in a different guise if you try to delete an array of derived class objects through a baseclass pointer. Here's one way you might innocently attempt to do it:

// delete an array, but first log a message about its

// deletion

void deleteArray(ostream& logStream, BST array[])

{

logStream << "Deleting array at address "

<< static_cast<void*>(array) << '\n';

delete [] array;

}

BalancedBST *balTreeArray = // create a BalancedBST

new BalancedBST[50]; // array

...

deleteArray(cout, balTreeArray); // log its deletion

You can't see it, but there's pointer arithmetic going on here, too. When an array is deleted, a destructor for eachelement of the array must be called (see Item 8). When compilers see the statement

delete [] array;

they must generate code that does something like this: // destruct the objects in *array in the inverse order

// in which they were constructed

for (int i = the number of elements in the array - 1;

i >= 0;

--i)

{

array[i].BST::~BST(); // call array[i]'s

} // destructor

Just as this kind of loop failed to work when you wrote it, it will fail to work when your compilers write it, too.The °language specification says the result of deleting an array of derived class objects through a base classpointer is undefined, but we know what that really means: executing the code is almost certain to lead to grief.Polymorphism and pointer arithmetic simply don't mix. Array operations almost always involve pointerarithmetic, so arrays and polymorphism don't mix.

Note that you're unlikely to make the mistake of treating an array polymorphically if you avoid having a concreteclass (like BalancedBST) inherit from another concrete class (such as BST). As Item 33 explains, designingyour software so that concrete classes never inherit from one another has many benefits. I encourage you to turnto Item 33 and read all about them.

Back to Item 2: Prefer C++-style casts Continue to Item 4: Avoid gratuitous default constructors

Page 219: Effective C++ and More Effective C++

Back to Item 3: Never treat arrays polymorphically Continue to Operators

Item 4: Avoid gratuitous default constructors.

A default constructor (i.e., a constructor that can be called with no arguments) is the C++ way of saying you canget something for nothing. Constructors initialize objects, so default constructors initialize objects without anyinformation from the place where the object is being created. Sometimes this makes perfect sense. Objects thatact like numbers, for example, may reasonably be initialized to zero or to undefined values. Objects that act likepointers ( Item 28) may reasonably be initialized to null or to undefined values. Data structures like linked lists,hash tables, maps, and the like may reasonably be initialized to empty containers.

Not all objects fall into this category. For many objects, there is no reasonable way to perform a completeinitialization in the absence of outside information. For example, an object representing an entry in an addressbook makes no sense unless the name of the thing being entered is provided. In some companies, all equipmentmust be tagged with a corporate ID number, and creating an object to model a piece of equipment in suchcompanies is nonsensical unless the appropriate ID number is provided.

In a perfect world, classes in which objects could reasonably be created from nothing would contain defaultconstructors and classes in which information was required for object construction would not. Alas, ours is notthe best of all possible worlds, so we must take additional concerns into account. In particular, if a class lacks adefault constructor, there are restrictions on how you can use that class.

Consider a class for company equipment in which the corporate ID number of the equipment is a mandatoryconstructor argument:

class EquipmentPiece {

public:

EquipmentPiece(int IDNumber);

...

};

Because EquipmentPiece lacks a default constructor, its use may be problematic in three contexts. The first isthe creation of arrays. There is, in general, no way to specify constructor arguments for objects in arrays, so it isnot usually possible to create arrays of EquipmentPiece objects:

EquipmentPiece bestPieces[10]; // error! No way to call

// EquipmentPiece ctors

EquipmentPiece *bestPieces =

new EquipmentPiece[10]; // error! same problem

There are three ways to get around this restriction. A solution for non-heap arrays is to provide the necessaryarguments at the point where the array is defined:

int ID1, ID2, ID3, ..., ID10; // variables to hold

// equipment ID numbers

...

EquipmentPiece bestPieces[] = { // fine, ctor arguments

EquipmentPiece(ID1), // are provided

EquipmentPiece(ID2),

EquipmentPiece(ID3),

...,

EquipmentPiece(ID10)

};

Unfortunately, there is no way to extend this strategy to heap arrays.

A more general approach is to use an array of pointers instead of an array of objects: typedef EquipmentPiece* PEP; // a PEP is a pointer to

// an EquipmentPiece

Page 220: Effective C++ and More Effective C++

PEP bestPieces[10]; // fine, no ctors called

PEP *bestPieces = new PEP[10]; // also fine

Each pointer in the array can then be made to point to a different EquipmentPiece object: for (int i = 0; i < 10; ++i)

bestPieces[i] = new EquipmentPiece( ID Number );

There are two disadvantages to this approach. First, you have to remember to delete all the objects pointed toby the array. If you forget, you have a resource leak. Second, the total amount of memory you need increases,because you need the space for the pointers as well as the space for the EquipmentPiece objects.

You can avoid the space penalty if you allocate the raw memory for the array, then use "placement new" (see Item 8) to construct the EquipmentPiece objects in the memory:

// allocate enough raw memory for an array of 10

// EquipmentPiece objects; see Item 8 for details on

// the operator new[] function

void *rawMemory =

operator new[](10*sizeof(EquipmentPiece));

// make bestPieces point to it so it can be treated as an

// EquipmentPiece array

EquipmentPiece *bestPieces =

static_cast<EquipmentPiece*>(rawMemory);

// construct the EquipmentPiece objects in the memory

// using "placement new" (see Item 8)

for (int i = 0; i < 10; ++i)

new (&bestPieces[i]) EquipmentPiece( ID Number );

Notice that you still have to provide a constructor argument for each EquipmentPiece object. This technique (aswell as the array-of-pointers idea) allows you to create arrays of objects when a class lacks a defaultconstructor; it doesn't show you how to bypass required constructor arguments. There is no way to do that. Ifthere were, it would defeat the purpose of constructors, which is to guarantee that objects are initialized.

The downside to using placement new, aside from the fact that most programmers are unfamiliar with it (whichwill make maintenance more difficult), is that you must manually call destructors on the objects in the arraywhen you want them to go out of existence, then you must manually deallocate the raw memory by callingoperator delete[] (again, see Item 8):

// destruct the objects in bestPieces in the inverse

// order in which they were constructed

for (int i = 9; i >= 0; --i)

bestPieces[i].~EquipmentPiece();

// deallocate the raw memory

operator delete[](rawMemory);

If you forget this requirement and use the normal array-deletion syntax, your program will behaveunpredictably. That's because the result of deleting a pointer that didn't come from the new operator isundefined:

delete [] bestPieces; // undefined! bestPieces

// didn't come from the new

// operator

For more information on the new operator, placement new and how they interact with constructors anddestructors, see Item 8.

The second problem with classes lacking default constructors is that they are ineligible for use with manytemplate-based container classes. That's because it's a common requirement for such templates that the type used

Page 221: Effective C++ and More Effective C++

to instantiate the template provide a default constructor. This requirement almost always grows out of the factthat inside the template, an array of the template parameter type is being created. For example, a template for anArray class might look something like this:

template<class T>

class Array {

public:

Array(int size);

...

private:

T *data;

};

template<class T>

Array<T>::Array(int size)

{

data = new T[size]; // calls T::T() for each

... // element of the array

}

In most cases, careful template design can eliminate the need for a default constructor. For example, thestandard vector template (which generates classes that act like extensible arrays) has no requirement that its typeparameter have a default constructor. Unfortunately, many templates are designed in a manner that is anything butcareful. That being the case, classes without default constructors will be incompatible with many templates. AsC++ programmers learn more about template design, this problem should recede in significance. How long itwill take for that to happen, however, is anyone's guess.

The final consideration in the to-provide-a-default-constructor-or-not-to-provide-a-default-constructor dilemmahas to do with virtual base classes (see Item E43). Virtual base classes lacking default constructors are a pain towork with. That's because the arguments for virtual base class constructors must be provided by the mostderived class of the object being constructed. As a result, a virtual base class lacking a default constructorrequires that all classes derived from that class ? no matter how far removed ? must know about, understand themeaning of, and provide for the virtual base class's constructors' arguments. Authors of derived classes neitherexpect nor appreciate this requirement.

Because of the restrictions imposed on classes lacking default constructors, some people believe all classesshould have them, even if a default constructor doesn't have enough information to fully initialize objects of thatclass. For example, adherents to this philosophy might modify EquipmentPiece as follows:

class EquipmentPiece {

public:

EquipmentPiece(int IDNumber = UNSPECIFIED);

...

private:

static const int UNSPECIFIED; // magic ID number value

// meaning no ID was

}; // specified

This allows EquipmentPiece objects to be created like this: EquipmentPiece e; // now okay

Such a transformation almost always complicates the other member functions of the class, because there is nolonger any guarantee that the fields of an EquipmentPiece object have been meaningfully initialized. Assuming itmakes no sense to have an EquipmentPiece without an ID field, most member functions must check to see if theID is present. If it's not, they'll have to figure out how to stumble on anyway. Often it's not clear how to do that,and many implementations choose a solution that offers nothing but expediency: they throw an exception or theycall a function that terminates the program. When that happens, it's difficult to argue that the overall quality of thesoftware has been improved by including a default constructor in a class where none was warranted.

Inclusion of meaningless default constructors affects the efficiency of classes, too. If member functions have to

Page 222: Effective C++ and More Effective C++

test to see if fields have truly been initialized, clients of those functions have to pay for the time those tests take.Furthermore, they have to pay for the code that goes into those tests, because that makes executables andlibraries bigger. They also have to pay for the code that handles the cases where the tests fail. All those costsare avoided if a class's constructors ensure that all fields of an object are correctly initialized. Often defaultconstructors can't offer that kind of assurance, so it's best to avoid them in classes where they make no sense.That places some limits on how such classes can be used, yes, but it also guarantees that when you do use suchclasses, you can expect that the objects they generate are fully initialized and are efficiently implemented.

Back to Item 3: Never treat arrays polymorphically Continue to Operators

Page 223: Effective C++ and More Effective C++

Back to Item 4: Avoid gratuitous default constructors Continue to Item 5: Be wary of user-defined conversion functions

Operators

Overloadable operators ? you gotta love 'em! They allow you to give your types the same syntax as C++'sbuilt-in types, yet they let you put a measure of power into the functions behind the operators that's unheard offor the built-ins. Of course, the fact that you can make symbols like "+" and "==" do anything you want alsomeans you can use overloaded operators to produce programs best described as impenetrable. Adept C++programmers know how to harness the power of operator overloading without descending into theincomprehensible.

Regrettably, it is easy to make the descent. Single-argument constructors and implicit type conversion operatorsare particularly troublesome, because they can be invoked without there being any source code showing thecalls. This can lead to program behavior that is difficult to understand. A different problem arises when youoverload operators like && and ||, because the shift from built-in operator to user-defined function yields asubtle change in semantics that's easy to overlook. Finally, many operators are related to one another in standardways, but the ability to overload operators makes it possible to violate the accepted relationships.

In the items that follow, I focus on explaining when and how overloaded operators are called, how they behave,how they should relate to one another, and how you can seize control of these aspects of overloaded operators.With the information in this chapter under your belt, you'll be overloading (or not overloading) operators like apro.

Back to Item 4: Avoid gratuitous default constructors Continue to Item 5:Be wary of user-defined conversion functions

Page 224: Effective C++ and More Effective C++

Back to Operators Continue to Item 6: Distinguish between prefix and postfix forms of increment and decrement operators

Item 5: Be wary of user-defined conversion functions.

C++ allows compilers to perform implicit conversions between types. In honor of its C heritage, for example,the language allows silent conversions from char to int and from short to double. This is why you can pass ashort to a function that expects a double and still have the call succeed. The more frightening conversions in C ?those that may lose information ? are also present in C++, including conversion of int to short and double to (ofall things) char.

You can't do anything about such conversions, because they're hard-coded into the language. When you add yourown types, however, you have more control, because you can choose whether to provide the functions compilersare allowed to use for implicit type conversions.

Two kinds of functions allow compilers to perform such conversions: single-argument constructors andimplicit type conversion operators. A single-argument constructor is a constructor that may be called with onlyone argument. Such a constructor may declare a single parameter or it may declare multiple parameters, witheach parameter after the first having a default value. Here are two examples:

class Name { // for names of things

public:

Name(const string& s); // converts string to

// Name

...

};

class Rational { // for rational numbers

public:

Rational(int numerator = 0, // converts int to

int denominator = 1); // Rational

...

};

An implicit type conversion operator is simply a member function with a strange-looking name: the wordoperator followed by a type specification. You aren't allowed to specify a type for the function's return value,because the type of the return value is basically just the name of the function. For example, to allow Rationalobjects to be implicitly converted to doubles (which might be useful for mixed-mode arithmetic involvingRational objects), you might define class Rational like this:

class Rational {

public:

...

operator double() const; // converts Rational to

}; // double

This function would be automatically invoked in contexts like this: Rational r(1, 2); // r has the value 1/2

double d = 0.5 * r; // converts r to a double,

// then does multiplication

Perhaps all this is review. That's fine, because what I really want to explain is why you usually don't want toprovide type conversion functions of any ilk.

The fundamental problem is that such functions often end up being called when you neither want nor expect themto be. The result can be incorrect and unintuitive program behavior that is maddeningly difficult to diagnose.

Let us deal first with implicit type conversion operators, as they are the easiest case to handle. Suppose youhave a class for rational numbers similar to the one above, and you'd like to print Rational objects as if theywere a built-in type. That is, you'd like to be able to do this:

Rational r(1, 2);

Page 225: Effective C++ and More Effective C++

cout << r; // should print "1/2"

Further suppose you forgot to write an operator<< for Rational objects. You would probably expect that theattempt to print r would fail, because there is no appropriate operator<< to call. You would be mistaken. Yourcompilers, faced with a call to a function called operator<< that takes a Rational, would find that no suchfunction existed, but they would then try to find an acceptable sequence of implicit type conversions they couldapply to make the call succeed. The rules defining which sequences of conversions are acceptable arecomplicated, but in this case your compilers would discover they could make the call succeed by implicitlyconverting r to a double by calling Rational::operator double. The result of the code above would be to print ras a floating point number, not as a rational number. This is hardly a disaster, but it demonstrates thedisadvantage of implicit type conversion operators: their presence can lead to the wrong function being called(i.e., one other than the one intended).

The solution is to replace the operators with equivalent functions that don't have the syntactically magic names.For example, to allow conversion of a Rational object to a double, replace operator double with a functioncalled something like asDouble:

class Rational {

public:

...

double asDouble() const; // converts Rational

}; // to double

Such a member function must be called explicitly: Rational r(1, 2);

cout << r; // error! No operator<<

// for Rationals

cout << r.asDouble(); // fine, prints r as a

// double

In most cases, the inconvenience of having to call conversion functions explicitly is more than compensated forby the fact that unintended functions can no longer be silently invoked. In general, the more experience C++programmers have, the more likely they are to eschew type conversion operators. The members of °thecommittee working on the standard C++ library (see Item E49 and Item 35), for example, are among the mostexperienced in the business, and perhaps that's why the string type they added to the library contains no implicitconversion from a string object to a C-style char*. Instead, there's an explicit member function, c_str, thatperforms that conversion. Coincidence? I think not.

Implicit conversions via single-argument constructors are more difficult to eliminate. Furthermore, the problemsthese functions cause are in many cases worse than those arising from implicit type conversion operators.

As an example, consider a class template for array objects. These arrays allow clients to specify upper andlower index bounds:

template<class T>

class Array {

public:

Array(int lowBound, int highBound);

Array(int size);

T& operator[](int index);

...

};

The first constructor in the class allows clients to specify a range of array indices, for example, from 10 to 20.As a two-argument constructor, this function is ineligible for use as a type-conversion function. The secondconstructor, which allows clients to define Array objects by specifying only the number of elements in the array(in a manner similar to that used with built-in arrays), is different. It can be used as a type conversion function,

Page 226: Effective C++ and More Effective C++

and that can lead to endless anguish.

For example, consider a template specialization for comparing Array<int> objects and some code that uses suchobjects:

bool operator==( const Array<int>& lhs,

const Array<int>& rhs);

Array<int> a(10);

Array<int> b(10);

...

for (int i = 0; i < 10; ++i)

if (a == b[i]) { // oops! "a" should be "a[i]"

do something for when

a[i] and b[i] are equal;

}

else {

do something for when they're not;

}

We intended to compare each element of a to the corresponding element in b, but we accidentally omitted thesubscripting syntax when we typed a. Certainly we expect this to elicit all manner of unpleasant commentaryfrom our compilers, but they will complain not at all. That's because they see a call to operator== witharguments of type Array<int> (for a) and int (for b[i]), and though there is no operator== function taking thosetypes, our compilers notice they can convert the int into an Array<int> object by calling the Array<int>constructor that takes a single int as an argument. This they proceed to do, thus generating code for a program wenever meant to write, one that looks like this:

for (int i = 0; i < 10; ++i)

if (a == static_cast< Array<int> >(b[i])) ...

Each iteration through the loop thus compares the contents of a with the contents of a temporary array of sizeb[i] (whose contents are presumably undefined). Not only is this unlikely to behave in a satisfactory manner, it isalso tremendously inefficient, because each time through the loop we both create and destroy a temporaryArray<int> object (see Item 19).

The drawbacks to implicit type conversion operators can be avoided by simply failing to declare the operators,but single-argument constructors cannot be so easily waved away. After all, you may really want to offersingle-argument constructors to your clients. At the same time, you may wish to prevent compilers from callingsuch constructors indiscriminately. Fortunately, there is a way to have it all. In fact, there are two ways: the easyway and the way you'll have to use if your compilers don't yet support the easy way.

The easy way is to avail yourself of one of the newest C++ features, the explicit keyword. This feature wasintroduced specifically to address the problem of implicit type conversion, and its use is about asstraightforward as can be. Constructors can be declared explicit, and if they are, compilers are prohibited frominvoking them for purposes of implicit type conversion. Explicit conversions are still legal, however:

template<class T>

class Array {

public:

...

explicit Array(int size); // note use of "explicit"

...

};

Array<int> a(10); // okay, explicit ctors can

// be used as usual for

// object construction

Array<int> b(10); // also okay

Page 227: Effective C++ and More Effective C++

if (a == b[i]) ... // error! no way to

// implicitly convert

// int to Array<int>

if (a == Array<int>(b[i])) ... // okay, the conversion

// from int to Array<int> is

// explicit (but the logic of

// the code is suspect)

if (a == static_cast< Array<int> >(b[i])) ...

// equally okay, equally

// suspect

if (a == (Array<int>)b[i]) ... // C-style casts are also

// okay, but the logic of

// the code is still suspect

In the example using static_cast (see Item 2), the space separating the two ">" characters is no accident. If thestatement were written like this,

if (a == static_cast<Array<int>>(b[i])) ...

it would have a different meaning. That's because C++ compilers parse ">>" as a single token. Without a spacebetween the ">" characters, the statement would generate a syntax error.

If your compilers don't yet support explicit, you'll have to fall back on home-grown methods for preventing theuse of single-argument constructors as implicit type conversion functions. Such methods are obvious only afteryou've seen them.

I mentioned earlier that there are complicated rules governing which sequences of implicit type conversions arelegitimate and which are not. One of those rules is that no sequence of conversions is allowed to contain morethan one user-defined conversion (i.e., a call to a single-argument constructor or an implicit type conversionoperator). By constructing your classes properly, you can take advantage of this rule so that the objectconstructions you want to allow are legal, but the implicit conversions you don't want to allow are illegal.

Consider the Array template again. You need a way to allow an integer specifying the size of the array to beused as a constructor argument, but you must at the same time prevent the implicit conversion of an integer into atemporary Array object. You accomplish this by first creating a new class, ArraySize. Objects of this type haveonly one purpose: they represent the size of an array that's about to be created. You then modify Array'ssingle-argument constructor to take an ArraySize object instead of an int. The code looks like this:

template<class T>

class Array {

public:

class ArraySize { // this class is new

public:

ArraySize(int numElements): theSize(numElements) {}

int size() const { return theSize; }

private:

int theSize;

};

Array(int lowBound, int highBound);

Array(ArraySize size); // note new declaration

...

};

Here you've nested ArraySize inside Array to emphasize the fact that it's always used in conjunction with that

Page 228: Effective C++ and More Effective C++

class. You've also made ArraySize public in Array so that anybody can use it. Good.

Consider what happens when an Array object is defined via the class's single-argument constructor: Array<int> a(10);

Your compilers are asked to call a constructor in the Array<int> class that takes an int, but there is no suchconstructor. Compilers realize they can convert the int argument into a temporary ArraySize object, and thatArraySize object is just what the Array<int> constructor needs, so compilers perform the conversion with theirusual gusto. This allows the function call (and the attendant object construction) to succeed.

The fact that you can still construct Array objects with an int argument is reassuring, but it does you little goodunless the type conversions you want to avoid are prevented. They are. Consider this code again:

bool operator==(const Array<int>& lhs,

const Array<int>& rhs);

Array<int> a(10);

Array<int> b(10);

...

for (int i = 0; i < 10; ++i)

if (a == b[i]) ... // oops! "a" should be "a[i]";

// this is now an error

Compilers need an object of type Array<int> on the right-hand side of the "==" in order to call operator== forArray<int> objects, but there is no single-argument constructor taking an int argument. Furthermore, compilerscannot consider converting the int into a temporary ArraySize object and then creating the necessary Array<int>object from this temporary, because that would call for two user-defined conversions, one from int to ArraySizeand one from ArraySize to Array<int>. Such a conversion sequence is verboten, so compilers must issue anerror for the code attempting to perform the comparison.

The use of the ArraySize class in this example might look like a special-purpose hack, but it's actually a specificinstance of a more general technique. Classes like ArraySize are often called proxy classes, because each objectof such a class stands for (is a proxy for) some other object. An ArraySize object is really just a stand-in for theinteger used to specify the size of the Array being created. Proxy objects can give you control over aspects ofyour software's behavior ? in this case implicit type conversions ? that is otherwise beyond your grasp, so it'swell worth your while to learn how to use them. How, you might wonder, can you acquire such learning? Oneway is to turn to Item 30; it's devoted to proxy classes.

Before you turn to proxy classes, however, reflect a bit on the lessons of this Item. Granting compilers license toperform implicit type conversions usually leads to more harm than good, so don't provide conversion functionsunless you're sure you want them.

Back to Operators Continue to Item 6: Distinguish between prefix and postfix forms of increment and decrement operators

Page 229: Effective C++ and More Effective C++

Back to Item 5: Be wary of user-defined conversion functions Continue to Item 7: Never overload ?

Item 6: Distinguish between prefix and postfix forms of increment and decrement operators.

Long, long ago (the late '80s) in a language far, far away (C++ at that time), there was no way to distinguishbetween prefix and postfix invocations of the ++ and -- operators. Programmers being programmers, theykvetched about this omission, and C++ was extended to allow overloading both forms of increment anddecrement operators.

There was a syntactic problem, however, and that was that overloaded functions are differentiated on the basisof the parameter types they take, but neither prefix nor postfix increment or decrement takes an argument. Tosurmount this linguistic pothole, it was decreed that postfix forms take an int argument, and compilers silentlypass 0 as that int when those functions are called:

class UPInt { // "unlimited precision int"

public:

UPInt& operator++(); // prefix ++

const UPInt operator++(int); // postfix ++

UPInt& operator--(); // prefix --

const UPInt operator--(int); // postfix --

UPInt& operator+=(int); // a += operator for UPInts

// and ints

...

};

UPInt i;

++i; // calls i.operator++();

i++; // calls i.operator++(0);

--i; // calls i.operator--();

i--; // calls i.operator--(0);

This convention is a little on the odd side, but you'll get used to it. More important to get used to, however, isthis: the prefix and postfix forms of these operators return different types. In particular, prefix forms return areference, postfix forms return a const object. We'll focus here on the prefix and postfix ++ operators, but thestory for the -- operators is analogous.

From your days as a C programmer, you may recall that the prefix form of the increment operator is sometimescalled "increment and fetch," while the postfix form is often known as "fetch and increment." These two phrasesare important to remember, because they all but act as formal specifications for how prefix and postfixincrement should be implemented:

// prefix form: increment and fetch

UPInt& UPInt::operator++()

{

*this += 1; // increment

return *this; // fetch

}

// postfix form: fetch and increment

const UPInt UPInt::operator++(int)

{

UPInt oldValue = *this; // fetch

++(*this); // increment

return oldValue; // return what was

} // fetched

Page 230: Effective C++ and More Effective C++

Note how the postfix operator makes no use of its parameter. This is typical. The only purpose of the parameteris to distinguish prefix from postfix function invocation. Many compilers issue warnings (see Item E48) if youfail to use named parameters in the body of the function to which they apply, and this can be annoying. To avoidsuch warnings, a common strategy is to omit names for parameters you don't plan to use; that's what's been doneabove.

It's clear why postfix increment must return an object (it's returning an old value), but why a const object?Imagine that it did not. Then the following would be legal:

UPInt i;

i++++; // apply postfix increment

// twice

This is the same as i.operator++(0).operator++(0);

and it should be clear that the second invocation of operator++ is being applied to the object returned from thefirst invocation.

There are two reasons to abhor this. First, it's inconsistent with the behavior of the built-in types. A good rule tofollow when designing classes is when in doubt, do as the ints do, and the ints most certainly do not allowdouble application of postfix increment:

int i;

i++++; // error!

The second reason is that double application of postfix increment almost never does what clients expect it to.As noted above, the second application of operator++ in a double increment changes the value of the objectreturned from the first invocation, not the value of the original object. Hence, if

i++++;

were legal, i would be incremented only once. This is counterintuitive and confusing (for both ints and UPInts),so it's best prohibited.

C++ prohibits it for ints, but you must prohibit it yourself for classes you write. The easiest way to do this is tomake the return type of postfix increment a const object. Then when compilers see

i++++; // same as

i.operator++(0).operator++(0);

they recognize that the const object returned from the first call to operator++ is being used to call operator++again. operator++, however, is a non-const member function, so const objects ? such as those returned frompostfix operator++ ? can't call it.2 If you've ever wondered if it makes sense to have functions return constobjects, now you know: sometimes it does, and postfix increment and decrement are examples. (For anotherexample, turn to Item E21.)

If you're the kind who worries about efficiency, you probably broke into a sweat when you first saw the postfixincrement function. That function has to create a temporary object for its return value (see Item 19), and theimplementation above also creates an explicit temporary object (oldValue) that has to be constructed anddestructed. The prefix increment function has no such temporaries. This leads to the possibly startling conclusionthat, for efficiency reasons alone, clients of UPInt should prefer prefix increment to postfix increment unless theyreally need the behavior of postfix increment. Let us be explicit about this. When dealing with user-definedtypes, prefix increment should be used whenever possible, because it's inherently more efficient.

Let us make one more observation about the prefix and postfix increment operators. Except for their returnvalues, they do the same thing: they increment a value. That is, they're supposed to do the same thing. How can

Page 231: Effective C++ and More Effective C++

you be sure the behavior of postfix increment is consistent with that of prefix increment? What guarantee do youhave that their implementations won't diverge over time, possibly as a result of different programmersmaintaining and enhancing them? Unless you've followed the design principle embodied by the code above, youhave no such guarantee. That principle is that postfix increment and decrement should be implemented in termsof their prefix counterparts. You then need only maintain the prefix versions, because the postfix versions willautomatically behave in a consistent fashion.

As you can see, mastering prefix and postfix increment and decrement is easy. Once you know their properreturn types and that the postfix operators should be implemented in terms of the prefix operators, there's verylittle more to learn.

Back to Item 5: Be wary of user-defined conversion functions Continue to Item 7: Never overload ?

2 Alas, it is not uncommon for compilers to fail to enforce this restriction. Before you write programs that relyon it, test your compilers to make sure they behave correctly. Return

Page 232: Effective C++ and More Effective C++

Back to Item 6: Distinguish between prefix and postfix forms of increment and decrement operators Continue to Item 8: Understand the different meanings of new and delete

Item 7: Never overload &&, ||, or ,.

Like C, C++ employs short-circuit evaluation of boolean expressions. This means that once the truth orfalsehood of an expression has been determined, evaluation of the expression ceases, even if some parts of theexpression haven't yet been examined. For example, in this case,

char *p;

...

if ((p != 0) && (strlen(p) > 10)) ...

there is no need to worry about invoking strlen on p if it's a null pointer, because if the test of p against 0 fails,strlen will never be called. Similarly, given

int rangeCheck(int index)

{

if ((index < lowerBound) || (index > upperBound)) ...

...

}

index will never be compared to upperBound if it's less than lowerBound.

This is the behavior that has been drummed into C and C++ programmers since time immemorial, so this is whatthey expect. Furthermore, they write programs whose correct behavior depends on short-circuit evaluation. Inthe first code fragment above, for example, it is important that strlen not be invoked if p is a null pointer,because the °standard for C++ states (as does the standard for C) that the result of invoking strlen on a nullpointer is undefined.

C++ allows you to customize the behavior of the && and || operators for user-defined types. You do it byoverloading the functions operator&& and operator||, and you can do this at the global scope or on a per-classbasis. If you decide to take advantage of this opportunity, however, you must be aware that you are changing therules of the game quite radically, because you are replacing short-circuit semantics with function call semantics.That is, if you overload operator&&, what looks to you like this,

if (expression1 && expression2) ...

looks to compilers like one of these: if (expression1.operator&&(expression2)) ...

// when operator&& is a

// member function

if (operator&&(expression1, expression2)) ...

// when operator&& is a

// global function

This may not seem like that big a deal, but function call semantics differ from short-circuit semantics in twocrucial ways. First, when a function call is made, all parameters must be evaluated, so when calling thefunctions operator&& and operator||, both parameters are evaluated. There is, in other words, no short circuit.Second, the language specification leaves undefined the order of evaluation of parameters to a function call, sothere is no way of knowing whether expression1 or expression2 will be evaluated first. This stands in starkcontrast to short-circuit evaluation, which always evaluates its arguments in left-to-right order.

As a result, if you overload && or ||, there is no way to offer programmers the behavior they both expect andhave come to depend on. So don't overload && or ||.

The situation with the comma operator is similar, but before we delve into that, I'll pause and let you catch the

Page 233: Effective C++ and More Effective C++

breath you lost when you gasped, "The comma operator? There's a comma operator?" There is indeed.

The comma operator is used to form expressions, and you're most likely to run across it in the update part of afor loop. The following function, for example, is based on one in the second edition of Kernighan's and Ritchie'sclassic °The C Programming Language (Prentice-Hall, 1988):

// reverse string s in place

void reverse(char s[])

{

for (int i = 0, j = strlen(s)-1;

i < j;

++i, --j) // aha! the comma operator!

{

int c = s[i];

s[i] = s[j];

s[j] = c;

}

}

Here, i is incremented and j is decremented in the final part of the for loop. It is convenient to use the commaoperator here, because only an expression is valid in the final part of a for loop; separate statements to changethe values of i and j would be illegal.

Just as there are rules in C++ defining how && and || behave for built-in types, there are rules defining how thecomma operator behaves for such types. An expression containing a comma is evaluated by first evaluating thepart of the expression to the left of the comma, then evaluating the expression to the right of the comma; the resultof the overall comma expression is the value of the expression on the right. So in the final part of the loop above,compilers first evaluate ++i, then --j, and the result of the comma expression is the value returned from --j.

Perhaps you're wondering why you need to know this. You need to know because you need to mimic thisbehavior if you're going to take it upon yourself to write your own comma operator. Unfortunately, you can'tperform the requisite mimicry.

If you write operator, as a non-member function, you'll never be able to guarantee that the left-hand expression isevaluated before the right-hand expression, because both expressions will be passed as arguments in a functioncall (to operator,). But you have no control over the order in which a function's arguments are evaluated. So thenon-member approach is definitely out.

That leaves only the possibility of writing operator, as a member function. Even here you can't rely on theleft-hand operand to the comma operator being evaluated first, because compilers are not constrained to dothings that way. Hence, you can't overload the comma operator and also guarantee it will behave the way it'ssupposed to. It therefore seems imprudent to overload it at all.

You may be wondering if there's an end to this overloading madness. After all, if you can overload the commaoperator, what can't you overload? As it turns out, there are limits. You can't overload the following operators:

. .* :: ?:

new delete sizeof typeid

static_cast dynamic_cast const_cast reinterpret_cast

You can overload these:

operator new operator delete

operator new[] operator delete[]

+ - * / % ^ & | ~

! = < > += -= *= /= %=

^= &= |= << >> >>= <<= == !=

<= >= && || ++ -- , ->* ->

() []

Page 234: Effective C++ and More Effective C++

(For information on the new and delete operators, as well as operator new, operator delete, operator new[],and operator delete[], see Item 8.)

Of course, just because you can overload these operators is no reason to run off and do it. The purpose ofoperator overloading is to make programs easier to read, write, and understand, not to dazzle others with yourknowledge that comma is an operator. If you don't have a good reason for overloading an operator, don'toverload it. In the case of &&, ||, and ,, it's difficult to have a good reason, because no matter how hard you try,you can't make them behave the way they're supposed to.

Back to Item 6: Distinguish between prefix and postfix forms of increment and decrement operators Continue to Item 8: Understand the different meanings of new and delete

Page 235: Effective C++ and More Effective C++

Back to Item 7: Never overload ? Continue to Exceptions

Item 8: Understand the different meanings of new and delete.

It occasionally seems as if people went out of their way to make C++ terminology difficult to understand. Casein point: the difference between the new operator and operator new.

When you write code like this, string *ps = new string("Memory Management");

the new you are using is the new operator. This operator is built into the language and, like sizeof, you can'tchange its meaning: it always does the same thing. What it does is twofold. First, it allocates enough memory tohold an object of the type requested. In the example above, it allocates enough memory to hold a string object.Second, it calls a constructor to initialize an object in the memory that was allocated. The new operator alwaysdoes those two things; you can't change its behavior in any way.

What you can change is how the memory for an object is allocated. The new operator calls a function to performthe requisite memory allocation, and you can rewrite or overload that function to change its behavior. The nameof the function the new operator calls to allocate memory is operator new. Honest.

The operator new function is usually declared like this: void * operator new(size_t size);

The return type is void*, because this function returns a pointer to raw, uninitialized memory. (If you like, youcan write a version of operator new that initializes the memory to some value before returning a pointer to it, butthis is not commonly done.) The size_t parameter specifies how much memory to allocate. You can overloadoperator new by adding additional parameters, but the first parameter must always be of type size_t. (Forinformation on writing operator new, consult Items E8-E10.)

You'll probably never want to call operator new directly, but on the off chance you do, you'll call it just like anyother function:

void *rawMemory = operator new(sizeof(string));

Here operator new will return a pointer to a chunk of memory large enough to hold a string object.

Like malloc, operator new's only responsibility is to allocate memory. It knows nothing about constructors. Alloperator new understands is memory allocation. It is the job of the new operator to take the raw memory thatoperator new returns and transform it into an object. When your compilers see a statement like

string *ps = new string("Memory Management");

they must generate code that more or less corresponds to this (see Items E8 and E10, as well as the sidebar tomy article on counting objects, for a more detailed treatment of this point):

void *memory = // get raw memory

operator new(sizeof(string)); // for a string

// object

call string::string("Memory Management") // initialize the

on *memory; // object in the

// memory

string *ps = // make ps point to

static_cast<string*>(memory); // the new object

Page 236: Effective C++ and More Effective C++

Notice that the second step above involves calling a constructor, something you, a mere programmer, areprohibited from doing. Your compilers are unconstrained by mortal limits, however, and they can do whateverthey like. That's why you must use the new operator if you want to conjure up a heap-based object: you can'tdirectly call the constructor necessary to initialize the object (including such crucial components as its vtbl ?see Item 24).

Placement new

There are times when you really want to call a constructor directly. Invoking a constructor on an existing objectmakes no sense, because constructors initialize objects, and an object can only be initialized ? given its firstvalue ? once. But occasionally you have some raw memory that's already been allocated, and you need toconstruct an object in the memory you have. A special version of operator new called placement new allowsyou to do it.

As an example of how placement new might be used, consider this: class Widget {

public:

Widget(int widgetSize);

...

};

Widget * constructWidgetInBuffer(void *buffer,

int widgetSize)

{

return new (buffer) Widget(widgetSize);

}

This function returns a pointer to a Widget object that's constructed within the buffer passed to the function.Such a function might be useful for applications using shared memory or memory-mapped I/O, because objectsin such applications must be placed at specific addresses or in memory allocated by special routines. (For adifferent example of how placement new can be used, see Item 4.)

Inside constructWidgetInBuffer, the expression being returned is new (buffer) Widget(widgetSize)

This looks a little strange at first, but it's just a use of the new operator in which an additional argument (buffer)is being specified for the implicit call that the new operator makes to operator new. The operator new thuscalled must, in addition to the mandatory size_t argument, accept a void* parameter that points to the memory theobject being constructed is to occupy. That operator new is placement new, and it looks like this:

void * operator new(size_t, void *location)

{

return location;

}

This is probably simpler than you expected, but this is all placement new needs to do. After all, the purpose ofoperator new is to find memory for an object and return a pointer to that memory. In the case of placement new,the caller already knows what the pointer to the memory should be, because the caller knows where the object issupposed to be placed. All placement new has to do, then, is return the pointer that's passed into it. (The unused(but mandatory) size_t parameter has no name to keep compilers from complaining about its not being used; see Item 6.) Placement new is part of the standard C++ library (see Item E49). To use placement new, all you haveto do is #include <new> (or, if your compilers don't yet support the new-style header names (again, see ItemE49), <new.h>).

If we step back from placement new for a moment, we'll see that the relationship between the new operator andoperator new, though you want to create an object on the heap, use the new operator. It both allocates memoryand calls a constructor for the object. If you only want to allocate memory, call operator new; no constructorwill be called. If you want to customize the memory allocation that takes place when heap objects are created,

Page 237: Effective C++ and More Effective C++

write your own version of operator new and use the new operator; it will automatically invoke your customversion of operator new. If you want to construct an object in memory you've already got a pointer to, useplacement new.

(For additional insights into variants of new and delete, see Item E7 and my article on counting objects.)

Deletion and Memory Deallocation

To avoid resource leaks, every dynamic allocation must be matched by an equal and opposite deallocation. Thefunction operator delete is to the built-in delete operator as operator new is to the new operator. When you saysomething like this,

string *ps;

...

delete ps; // use the delete operator

your compilers must generate code both to destruct the object ps points to and to deallocate the memoryoccupied by that object.

The memory deallocation is performed by the operator delete function, which is usually declared like this: void operator delete(void *memoryToBeDeallocated);

Hence, delete ps;

causes compilers to generate code that approximately corresponds to this:

ps->~string(); // call the object's dtor

operator delete(ps); // deallocate the memory

// the object occupied

One implication of this is that if you want to deal only with raw, uninitialized memory, you should bypass thenew and delete operators entirely. Instead, you should call operator new to get the memory and operator deleteto return it to the system:

void *buffer = // allocate enough

operator new(50*sizeof(char)); // memory to hold 50

// chars; call no ctors

...

operator delete(buffer); // deallocate the memory;

// call no dtors

This is the C++ equivalent of calling malloc and free.

If you use placement new to create an object in some memory, you should avoid using the delete operator on thatmemory. That's because the delete operator calls operator delete to deallocate the memory, but the memorycontaining the object wasn't allocated by operator new in the first place; placement new just returned the pointerthat was passed to it. Who knows where that pointer came from? Instead, you should undo the effect of theconstructor by explicitly calling the object's destructor:

// functions for allocating and deallocating memory in

// shared memory

void * mallocShared(size_t size);

void freeShared(void *memory);

Page 238: Effective C++ and More Effective C++

void *sharedMemory = mallocShared(sizeof(Widget));

Widget *pw = // as above,

constructWidgetInBuffer(sharedMemory, 10); // placement

// new is used

...

delete pw; // undefined! sharedMemory came from

// mallocShared, not operator new

pw->~Widget(); // fine, destructs the Widget pointed to

// by pw, but doesn't deallocate the

// memory containing the Widget

freeShared(pw); // fine, deallocates the memory pointed

// to by pw, but calls no destructor

As this example demonstrates, if the raw memory passed to placement new was itself dynamically allocated(through some unconventional means), you must still deallocate that memory if you wish to avoid a memory leak.(See the sidebar to my article on counting objects for information on "placement delete".)

Arrays

So far so good, but there's farther to go. Everything we've examined so far concerns itself with only one objectat a time. What about array allocation? What happens here?

string *ps = new string[10]; // allocate an array of

// objects

The new being used is still the new operator, but because an array is being created, the new operator behavesslightly differently from the case of single-object creation. For one thing, memory is no longer allocated byoperator new. Instead, it's allocated by the array-allocation equivalent, a function called operator new[] (oftenreferred to as "array new.") Like operator new, operator new[] can be overloaded. This allows you to seizecontrol of memory allocation for arrays in the same way you can control memory allocation for single objects(but see Item E8 for some caveats on this).

(operator new[] is a relatively recent addition to C++, so your compilers may not support it yet. If they don't, theglobal version of operator new will be used to allocate memory for every array, regardless of the type ofobjects in the array. Customizing array-memory allocation under such compilers is daunting, because it requiresthat you rewrite the global operator new. This is not a task to be undertaken lightly. By default, the globaloperator new handles all dynamic memory allocation in a program, so any change in its behavior has a dramaticand pervasive effect. Furthermore, there is only one global operator new with the "normal" signature (i.e., takingthe single size_t parameter ? see Item E9), so if you decide to claim it as your own, you instantly render yoursoftware incompatible with any library that makes the same decision. (See also Item 27.) As a result of theseconsiderations, custom memory management for arrays is not usually a reasonable design decision for compilerslacking support for operator new[].)

The second way in which the new operator behaves differently for arrays than for objects is in the number ofconstructor calls it makes. For arrays, a constructor must be called for each object in the array:

string *ps = // call operator new[] to allocate

new string[10]; // memory for 10 string objects,

// then call the default string

// ctor for each array element

Page 239: Effective C++ and More Effective C++

Similarly, when the delete operator is used on an array, it calls a destructor for each array element and thencalls operator delete[] to deallocate the memory:

delete [] ps; // call the string dtor for each

// array element, then call

// operator delete[] to

// deallocate the array's memory

Just as you can replace or overload operator delete, you can replace or overload operator delete[]. There aresome restrictions on how they can be overloaded, however; consult a good C++ text for details. (For ideas ongood C++ texts, see the recommendations beginning on page 285.)

So there you have it. The new and delete operators are built-in and beyond your control, but the memoryallocation and deallocation functions they call are not. When you think about customizing the behavior of thenew and delete operators, remember that you can't really do it. You can modify how they do what they do, butwhat they do is fixed by the language.

Back to Item 7: Never overload ? Continue to Exceptions

Page 240: Effective C++ and More Effective C++

Back to Item 8: Understand the different meanings of new and delete Continue to Item 9: Use destructors to prevent resource leaks

Exceptions

The addition of exceptions to C++ changes things. Profoundly. Radically. Possibly uncomfortably. The use ofraw, unadorned pointers, for example, becomes risky. Opportunities for resource leaks increase in number. Itbecomes more difficult to write constructors and destructors that behave the way we want them to. Special caremust be taken to prevent program execution from abruptly halting. Executables and libraries typically increase insize and decrease in speed.

And these are just the things we know. There is much the C++ community does not know about writing programsusing exceptions, including, for the most part, how to do it correctly. There is as yet no agreement on a body oftechniques that, when applied routinely, leads to software that behaves predictably and reliably when exceptionsare thrown. (For insight into some of the issues involved, see the article by Tom Cargill. For information onrecent progress in dealing with these issues, see the articles by Jack Reeves and Herb Sutter.)

We do know this much: programs that behave well in the presence of exceptions do so because they were designed to, not because they happen to. Exception-safe programs are not created by accident. The chances of aprogram behaving well in the presence of exceptions when it was not designed for exceptions are about the sameas the chances of a program behaving well in the presence of multiple threads of control when it was notdesigned for multi-threaded execution: about zero.

That being the case, why use exceptions? Error codes have sufficed for C programmers ever since C wasinvented, so why mess with exceptions, especially if they're as problematic as I say? The answer is simple:exceptions cannot be ignored. If a function signals an exceptional condition by setting a status variable orreturning an error code, there is no way to guarantee the function's caller will check the variable or examine thecode. As a result, execution may continue long past the point where the condition was encountered. If thefunction signals the condition by throwing an exception, however, and that exception is not caught, programexecution immediately ceases.

This is behavior that C programmers can approach only by using setjmp and longjmp. But longjmp exhibits aserious deficiency when used with C++: it fails to call destructors for local objects when it adjusts the stack.Most C++ programs depend on such destructors being called, so setjmp and longjmp make a poor substitute fortrue exceptions. If you need a way of signaling exceptional conditions that cannot be ignored, and if you mustensure that local destructors are called when searching the stack for code that can handle exceptional conditions,you need C++ exceptions. It's as simple as that.

Because we have much to learn about programming with exceptions, the Items that follow comprise anincomplete guide to writing exception-safe software. Nevertheless, they introduce important considerations foranyone using exceptions in C++. By heeding the guidance in the material below (and in the magazine articles onthis CD), you'll improve the correctness, robustness, and efficiency of the software you write, and you'llsidestep many problems that commonly arise when working with exceptions.

Back to Item 8: Understand the different meanings of new and delete Continue to Item 9: Use destructors to prevent resource leaks

Page 241: Effective C++ and More Effective C++

Back to Exceptions Continue to Item 10: Prevent resource leaks in constructors

Item 9: Use destructors to prevent resource leaks.

Say good-bye to pointers. Admit it: you never really liked them that much anyway.

Okay, you don't have to say good-bye to all pointers, but you do need to say sayonara to pointers that are used tomanipulate local resources. Suppose, for example, you're writing software at the Shelter for Adorable LittleAnimals, an organization that finds homes for puppies and kittens. Each day the shelter creates a file containinginformation on the adoptions it arranged that day, and your job is to write a program to read these files and dothe appropriate processing for each adoption.

A reasonable approach to this task is to define an abstract base class, ALA ("Adorable Little Animal"), plusconcrete derived classes for puppies and kittens. A virtual function, processAdoption, handles the necessaryspecies-specific processing:

class ALA {

public:

virtual void processAdoption() = 0;

...

};

class Puppy: public ALA {

public:

virtual void processAdoption();

...

};

class Kitten: public ALA {

public:

virtual void processAdoption();

...

};

You'll need a function that can read information from a file and produce either a Puppy object or a Kittenobject, depending on the information in the file. This is a perfect job for a virtual constructor, a kind of functiondescribed in Item 25. For our purposes here, the function's declaration is all we need:

// read animal information from s, then return a pointer

// to a newly allocated object of the appropriate type

ALA * readALA(istream& s);

The heart of your program is likely to be a function that looks something like this: void processAdoptions(istream& dataSource)

{

while (dataSource) { // while there's data

ALA *pa = readALA(dataSource); // get next animal

pa->processAdoption(); // process adoption

delete pa; // delete object that

} // readALA returned

Page 242: Effective C++ and More Effective C++

}

This function loops through the information in dataSource, processing each entry as it goes. The only mildlytricky thing is the need to remember to delete pa at the end of each iteration. This is necessary because readALAcreates a new heap object each time it's called. Without the call to delete, the loop would contain a resourceleak.

Now consider what would happen if pa->processAdoption threw an exception. processAdoptions fails to catchexceptions, so the exception would propagate to processAdoptions's caller. In doing so, all statements inprocessAdoptions after the call to pa->processAdoption would be skipped, and that means pa would never bedeleted. As a result, anytime pa->processAdoption throws an exception, processAdoptions contains a resourceleak.

Plugging the leak is easy enough, void processAdoptions(istream& dataSource)

{

while (dataSource) {

ALA *pa = readALA(dataSource);

try {

pa->processAdoption();

}

catch (...) { // catch all exceptions

delete pa; // avoid resource leak when an

// exception is thrown

throw; // propagate exception to caller

}

delete pa; // avoid resource leak when no

} // exception is thrown

}

but then you have to litter your code with try and catch blocks. More importantly, you are forced to duplicatecleanup code that is common to both normal and exceptional paths of control. In this case, the call to delete mustbe duplicated. Like all replicated code, this is annoying to write and difficult to maintain, but it also feels wrong. Regardless of whether we leave processAdoptions by a normal return or by throwing an exception, we need todelete pa, so why should we have to say that in more than one place?

We don't have to if we can somehow move the cleanup code that must always be executed into the destructor foran object local to processAdoptions. That's because local objects are always destroyed when leaving a function,regardless of how that function is exited. (The only exception to this rule is when you call longjmp, and thisshortcoming of longjmp is the primary reason why C++ has support for exceptions in the first place.) Our realconcern, then, is moving the delete from processAdoptions into a destructor for an object local toprocessAdoptions.

The solution is to replace the pointer pa with an object that acts like a pointer. That way, when the pointer-likeobject is (automatically) destroyed, we can have its destructor call delete. Objects that act like pointers, but domore, are called smart pointers, and, as Item 28 explains, you can make pointer-like objects very smart indeed.In this case, we don't need a particularly brainy pointer, we just need a pointer-like object that knows enough todelete what it points to when the pointer-like object goes out of scope.

It's not difficult to write a class for such objects, but we don't need to. The standard C++ library (see Item E49)contains a class template called auto_ptr that does just what we want. Each auto_ptr class takes a pointer to aheap object in its constructor and deletes that object in its destructor. Boiled down to these essential functions,

Page 243: Effective C++ and More Effective C++

auto_ptr looks like this: template<class T>

class auto_ptr {

public:

auto_ptr(T *p = 0): ptr(p) {} // save ptr to object

~auto_ptr() { delete ptr; } // delete ptr to object

private:

T *ptr; // raw ptr to object

};

The standard version of auto_ptr is much fancier, and this stripped-down implementation isn't suitable for realuse3 (we must add at least the copy constructor, assignment operator, and pointer-emulating functions discussedin Item 28), but the concept behind it should be clear: use auto_ptr objects instead of raw pointers, and youwon't have to worry about heap objects not being deleted, not even when exceptions are thrown. (Because theauto_ptr destructor uses the single-object form of delete, auto_ptr is not suitable for use with pointers to arraysof objects. If you'd like an auto_ptr-like template for arrays, you'll have to write your own. In such cases,however, it's often a better design decision to use a vector instead of an array, anyway.)

Using an auto_ptr object instead of a raw pointer, processAdoptions looks like this: void processAdoptions(istream& dataSource)

{

while (dataSource) {

auto_ptr<ALA> pa(readALA(dataSource));

pa->processAdoption();

}

}

This version of processAdoptions differs from the original in only two ways. First, pa is declared to be anauto_ptr<ALA> object, not a raw ALA* pointer. Second, there is no delete statement at the end of the loop.That's it. Everything else is identical, because, except for destruction, auto_ptr objects act just like normalpointers. Easy, huh?

The idea behind auto_ptr ? using an object to store a resource that needs to be automatically released and relyingon that object's destructor to release it ? applies to more than just pointer-based resources. Consider a functionin a GUI application that needs to create a window to display some information:

// this function may leak resources if an exception

// is thrown

void displayInfo(const Information& info)

{

WINDOW_HANDLE w(createWindow());

display info in window corresponding to w;

destroyWindow(w);

}

Many window systems have C-like interfaces that use functions like createWindow and destroyWindow toacquire and release window resources. If an exception is thrown during the process of displaying info in w, thewindow for which w is a handle will be lost just as surely as any other dynamically allocated resource.

The solution is the same as it was before. Create a class whose constructor and destructor acquire and releasethe resource:

// class for acquiring and releasing a window handle

class WindowHandle {

public:

WindowHandle(WINDOW_HANDLE handle): w(handle) {}

~WindowHandle() { destroyWindow(w); }

Page 244: Effective C++ and More Effective C++

operator WINDOW_HANDLE() { return w; } // see below

private:

WINDOW_HANDLE w;

// The following functions are declared private to prevent

// multiple copies of a WINDOW_HANDLE from being created.

// See Item 28 for a discussion of a more flexible approach.

WindowHandle(const WindowHandle&);

WindowHandle& operator=(const WindowHandle&);

};

This looks just like the auto_ptr template, except that assignment and copying are explicitly prohibited (see ItemE27), and there is an implicit conversion operator that can be used to turn a WindowHandle into aWINDOW_HANDLE. This capability is essential to the practical application of a WindowHandle object,because it means you can use a WindowHandle just about anywhere you would normally use a rawWINDOW_HANDLE. (See Item 5, however, for why you should generally be leery of implicit type conversionoperators.)

Given the WindowHandle class, we can rewrite displayInfo as follows: // this function avoids leaking resources if an

// exception is thrown

void displayInfo(const Information& info)

{

WindowHandle w(createWindow());

display info in window corresponding to w;

}

Even if an exception is thrown within displayInfo, the window created by createWindow will always bedestroyed.

By adhering to the rule that resources should be encapsulated inside objects, you can usually avoid resourceleaks in the presence of exceptions. But what happens if an exception is thrown while you're in the process ofacquiring a resource, e.g., while you're in the constructor of a resource-acquiring class? What happens if anexception is thrown during the automatic destruction of such resources? Don't constructors and destructors callfor special techniques? They do, and you can read about them in Items 10 and 11.

Back to Exceptions Continue to Item 10: Prevent resource leaks in constructors

3 A complete version of an almost-standard auto_ptr appears on pages 291-294. Return

Page 245: Effective C++ and More Effective C++

Back to Item 9: Use destructors to prevent resource leaks Continue to Item 11: Prevent exceptions from leaving destructors

Item 10: Prevent resource leaks in constructors.

Imagine you're developing software for a multimedia address book. Such an address book might hold, inaddition to the usual textual information of a person's name, address, and phone numbers, a picture of the personand the sound of their voice (possibly giving the proper pronunciation of their name).

To implement the book, you might come up with a design like this: class Image { // for image data

public:

Image(const string& imageDataFileName);

...

};

class AudioClip { // for audio data

public:

AudioClip(const string& audioDataFileName);

...

};

class PhoneNumber { ... }; // for holding phone numbers

class BookEntry { // for each entry in the

public: // address book

BookEntry(const string& name,

const string& address = "",

const string& imageFileName = "",

const string& audioClipFileName = "");

~BookEntry();

// phone numbers are added via this function

void addPhoneNumber(const PhoneNumber& number);

...

private:

string theName; // person's name

string theAddress; // their address

list<PhoneNumber> thePhones; // their phone numbers

Image *theImage; // their image

AudioClip *theAudioClip; // an audio clip from them

};

Each BookEntry must have name data, so you require that as a constructor argument (see Item 3), but the otherfields ? the person's address and the names of files containing image and audio data ? are optional. Note the useof the list class to hold the person's phone numbers. This is one of several container classes that are part of thestandard C++ library (see Item E49 and Item 35).

A straightforward way to write the BookEntry constructor and destructor is as follows: BookEntry::BookEntry(const string& name,

const string& address,

const string& imageFileName,

Const string& audioClipFileName)

: theName(name), theAddress(address),

theImage(0), theAudioClip(0)

{

if (imageFileName != "") {

theImage = new Image(imageFileName);

}

Page 246: Effective C++ and More Effective C++

if (audioClipFileName != "") {

theAudioClip = new AudioClip(audioClipFileName);

}

}

BookEntry::~BookEntry()

{

delete theImage;

delete theAudioClip;

}

The constructor initializes the pointers theImage and theAudioClip to null, then makes them point to real objectsif the corresponding arguments are non-empty strings. The destructor deletes both pointers, thus ensuring that aBookEntry object doesn't give rise to a resource leak. Because C++ guarantees it's safe to delete null pointers,BookEntry's destructor need not check to see if the pointers actually point to something before deleting them.

Everything looks fine here, and under normal conditions everything is fine, but under abnormal conditions ?under exceptional conditions ? things are not fine at all.

Consider what will happen if an exception is thrown during execution of this part of the BookEntry constructor: if (audioClipFileName != "") {

theAudioClip = new AudioClip(audioClipFileName);

}

An exception might arise because operator new (see Item 8) is unable to allocate enough memory for anAudioClip object. One might also arise because the AudioClip constructor itself throws an exception.Regardless of the cause of the exception, if one is thrown within the BookEntry constructor, it will bepropagated to the site where the BookEntry object is being created.

Now, if an exception is thrown during creation of the object theAudioClip is supposed to point to (thustransferring control out of the BookEntry constructor), who deletes the object that theImage already points to?The obvious answer is that BookEntry's destructor does, but the obvious answer is wrong. BookEntry'sdestructor will never be called. Never.

C++ destroys only fully constructed objects, and an object isn't fully constructed until its constructor has run tocompletion. So if a BookEntry object b is created as a local object,

void testBookEntryClass()

{

BookEntry b("Addison-Wesley Publishing Company",

"One Jacob Way, Reading, MA 01867");

...

}

and an exception is thrown during construction of b, b's destructor will not be called. Furthermore, if you try totake matters into your own hands by allocating b on the heap and then calling delete if an exception is thrown,

void testBookEntryClass()

{

BookEntry *pb = 0;

try {

pb = new BookEntry("Addison-Wesley Publishing Company",

"One Jacob Way, Reading, MA 01867");

...

}

catch (...) { // catch all exceptions

delete pb; // delete pb when an

Page 247: Effective C++ and More Effective C++

// exception is thrown

throw; // propagate exception to

} // caller

delete pb; // delete pb normally

}

you'll find that the Image object allocated inside BookEntry's constructor is still lost, because no assignment ismade to pb unless the new operation succeeds. If BookEntry's constructor throws an exception, pb will be thenull pointer, so deleting it in the catch block does nothing except make you feel better about yourself. Using thesmart pointer class auto_ptr<BookEntry> (see Item 9) instead of a raw BookEntry* won't do you any goodeither, because the assignment to pb still won't be made unless the new operation succeeds.

There is a reason why C++ refuses to call destructors for objects that haven't been fully constructed, and it's notsimply to make your life more difficult. It's because it would, in many cases, be a nonsensical thing ? possibly aharmful thing ? to do. If a destructor were invoked on an object that wasn't fully constructed, how would thedestructor know what to do? The only way it could know would be if bits had been added to each objectindicating how much of the constructor had been executed. Then the destructor could check the bits and (maybe)figure out what actions to take. Such bookkeeping would slow down constructors, and it would make each objectlarger, too. C++ avoids this overhead, but the price you pay is that partially constructed objects aren'tautomatically destroyed. (For an example of a similar trade-off involving efficiency and program behavior, turnto Item E13.)

Because C++ won't clean up after objects that throw exceptions during construction, you must design yourconstructors so that they clean up after themselves. Often, this involves simply catching all possible exceptions,executing some cleanup code, then rethrowing the exception so it continues to propagate. This strategy can beincorporated into the BookEntry constructor like this:

BookEntry::BookEntry(const string& name,

const string& address,

const string& imageFileName,

const string& audioClipFileName)

: theName(name), theAddress(address),

theImage(0), theAudioClip(0)

{

try { // this try block is new

if (imageFileName != "") {

theImage = new Image(imageFileName);

}

if (audioClipFileName != "") {

theAudioClip = new AudioClip(audioClipFileName);

}

}

catch (...) { // catch any exception

delete theImage; // perform necessary

delete theAudioClip; // cleanup actions

throw; // propagate the exception

}

}

There is no need to worry about BookEntry's non-pointer data members. Data members are automaticallyinitialized before a class's constructor is called, so if a BookEntry constructor body begins executing, theobject's theName, theAddress, and thePhones data members have already been fully constructed. As fullyconstructed objects, these data members will be automatically destroyed when the BookEntry object containing

Page 248: Effective C++ and More Effective C++

them is, and there is no need for you to intervene. Of course, if these objects' constructors call functions thatmight throw exceptions, those constructors have to worry about catching the exceptions and performing anynecessary cleanup before allowing them to propagate.

You may have noticed that the statements in BookEntry's catch block are almost the same as those in BookEntry'sdestructor. Code duplication here is no more tolerable than it is anywhere else, so the best way to structurethings is to move the common code into a private helper function and have both the constructor and the destructorcall it:

class BookEntry {

public:

... // as before

private:

...

void cleanup(); // common cleanup statements

};

void BookEntry::cleanup()

{

delete theImage;

delete theAudioClip;

}

BookEntry::BookEntry(const string& name,

const string& address,

const string& imageFileName,

const string& audioClipFileName)

: theName(name), theAddress(address),

theImage(0), theAudioClip(0)

{

try {

... // as before

}

catch (...) {

cleanup(); // release resources

throw; // propagate exception

}

}

BookEntry::~BookEntry()

{

cleanup();

}

This is nice, but it doesn't put the topic to rest. Let us suppose we design our BookEntry class slightlydifferently so that theImage and theAudioClip are constant pointers:

class BookEntry {

public:

... // as above

private:

...

Image * const theImage; // pointers are now

AudioClip * const theAudioClip; // const

};

Such pointers must be initialized via the member initialization lists of BookEntry's constructors, because thereis no other way to give const pointers a value (see Item E12). A common temptation is to initialize theImage andtheAudioClip like this,

// an implementation that may leak resources if an

// exception is thrown

Page 249: Effective C++ and More Effective C++

BookEntry::BookEntry(const string& name,

const string& address,

const string& imageFileName,

const string& audioClipFileName)

: theName(name), theAddress(address),

theImage(imageFileName != ""

? new Image(imageFileName)

: 0),

theAudioClip(audioClipFileName != ""

? new AudioClip(audioClipFileName)

: 0)

{}

but this leads to the problem we originally wanted to eliminate: if an exception is thrown during initialization oftheAudioClip, the object pointed to by theImage is never destroyed. Furthermore, we can't solve the problem byadding try and catch blocks to the constructor, because try and catch are statements, and member initializationlists allow only expressions. (That's why we had to use the ?: syntax instead of the if-then-else syntax in theinitialization of theImage and theAudioClip.)

Nevertheless, the only way to perform cleanup chores before exceptions propagate out of a constructor is tocatch those exceptions, so if we can't put try and catch in a member initialization list, we'll have to put themsomewhere else. One possibility is inside private member functions that return pointers with which theImageand theAudioClip should be initialized:

class BookEntry {

public:

... // as above

private:

... // data members as above

Image * initImage(const string& imageFileName);

AudioClip * initAudioClip(const string&

audioClipFileName);

};

BookEntry::BookEntry(const string& name,

const string& address,

const string& imageFileName,

const string& audioClipFileName)

: theName(name), theAddress(address),

theImage(initImage(imageFileName)),

theAudioClip(initAudioClip(audioClipFileName))

{}

// theImage is initialized first, so there is no need to

// worry about a resource leak if this initialization

// fails. This function therefore handles no exceptions

Image * BookEntry::initImage(const string& imageFileName)

{

if (imageFileName != "") return new Image(imageFileName);

else return 0;

}

// theAudioClip is initialized second, so it must make

// sure theImage's resources are released if an exception

// is thrown during initialization of theAudioClip. That's

// why this function uses try...catch.

AudioClip * BookEntry::initAudioClip(const string&

audioClipFileName)

{

try {

if (audioClipFileName != "") {

return new AudioClip(audioClipFileName);

Page 250: Effective C++ and More Effective C++

}

else return 0;

}

catch (...) {

delete theImage;

throw;

}

}

This is perfectly kosher, and it even solves the problem we've been laboring to overcome. The drawback is thatcode that conceptually belongs in a constructor is now dispersed across several functions, and that's amaintenance headache.

A better solution is to adopt the advice of Item 9 and treat the objects pointed to by theImage and theAudioClipas resources to be managed by local objects. This solution takes advantage of the facts that both theImage andtheAudioClip are pointers to dynamically allocated objects and that those objects should be deleted when thepointers themselves go away. This is precisely the set of conditions for which the auto_ptr classes (see Item 9)were designed. We can therefore change the raw pointer types of theImage and theAudioClip to their auto_ptrequivalents:

class BookEntry {

public:

... // as above

private:

...

const auto_ptr<Image> theImage; // these are now

const auto_ptr<AudioClip> theAudioClip; // auto_ptr objects

};

Doing this makes BookEntry's constructor leak-safe in the presence of exceptions, and it lets us initializetheImage and theAudioClip using the member initialization list:

BookEntry::BookEntry(const string& name,

const string& address,

const string& imageFileName,

const string& audioClipFileName)

: theName(name), theAddress(address),

theImage(imageFileName != ""

? new Image(imageFileName)

: 0),

theAudioClip(audioClipFileName != ""

? new AudioClip(audioClipFileName)

: 0)

{}

In this design, if an exception is thrown during initialization of theAudioClip, theImage is already a fullyconstructed object, so it will automatically be destroyed, just like theName, theAddress, and thePhones.Furthermore, because theImage and theAudioClip are now objects, they'll be destroyed automatically when theBookEntry object containing them is. Hence there's no need to manually delete what they point to. Thatsimplifies BookEntry's destructor considerably:

BookEntry::~BookEntry()

{} // nothing to do!

This means you could eliminate BookEntry's destructor entirely.

It all adds up to this: if you replace pointer class members with their corresponding auto_ptr objects, you fortifyyour constructors against resource leaks in the presence of exceptions, you eliminate the need to manuallydeallocate resources in destructors, and you allow const member pointers to be handled in the same graceful

Page 251: Effective C++ and More Effective C++

fashion as non-const pointers.

Dealing with the possibility of exceptions during construction can be tricky, but auto_ptr (and auto_ptr-likeclasses) can eliminate most of the drudgery. Their use leaves behind code that's not only easy to understand, it'srobust in the face of exceptions, too.

Back to Item 9: Use destructors to prevent resource leaks Continue to Item 11: Prevent exceptions from leaving destructors

Page 252: Effective C++ and More Effective C++

Back to Item 10: Prevent resource leaks in constructors Continue to Item 12: Understand how throwing an exception differs from passing a parameter or calling a virtual function

Item 11: Prevent exceptions from leaving destructors.

There are two situations in which a destructor is called. The first is when an object is destroyed under "normal"conditions, e.g., when it goes out of scope or is explicitly deleted. The second is when an object is destroyed bythe exception-handling mechanism during the stack-unwinding part of exception propagation.

That being the case, an exception may or may not be active when a destructor is invoked. Regrettably, there is noway to distinguish between these conditions from inside a destructor.4 As a result, you must write yourdestructors under the conservative assumption that an exception is active, because if control leaves a destructordue to an exception while another exception is active, C++ calls the terminate function. That function does justwhat its name suggests: it terminates execution of your program. Furthermore, it terminates it immediately; noteven local objects are destroyed.

As an example, consider a Session class for monitoring on-line computer sessions, i.e., things that happen fromthe time you log in through the time you log out. Each Session object notes the date and time of its creation anddestruction:

class Session {

public:

Session();

~Session();

...

private:

static void logCreation(Session *objAddr);

static void logDestruction(Session *objAddr);

};

The functions logCreation and logDestruction are used to record object creations and destructions, respectively.We might therefore expect that we could code Session's destructor like this:

Session::~Session()

{

logDestruction(this);

}

This looks fine, but consider what would happen if logDestruction throws an exception. The exception wouldnot be caught in Session's destructor, so it would be propagated to the caller of that destructor. But if thedestructor was itself being called because some other exception had been thrown, the terminate function wouldautomatically be invoked, and that would stop your program dead in its tracks.

In many cases, this is not what you'll want to have happen. It may be unfortunate that the Session object'sdestruction can't be logged, it might even be a major inconvenience, but is it really so horrific a prospect that theprogram can't continue running? If not, you'll have to prevent the exception thrown by logDestruction frompropagating out of Session's destructor. The only way to do that is by using try and catch blocks. A naive attemptmight look like this,

Session::~Session()

{

try {

logDestruction(this);

}

catch (...) {

cerr << "Unable to log destruction of Session object "

<< "at address "

<< this

<< ".\n";

}

}

Page 253: Effective C++ and More Effective C++

but this is probably no safer than our original code. If one of the calls to operator<< in the catch block results inan exception being thrown, we're back where we started, with an exception leaving the Session destructor.

We could always put a try block inside the catch block, but that seems a bit extreme. Instead, we'll just forgetabout logging Session destructions if logDestruction throws an exception:

Session::~Session()

{

try {

logDestruction(this);

}

catch (...) { }

}

The catch block appears to do nothing, but appearances can be deceiving. That block prevents exceptionsthrown from logDestruction from propagating beyond Session's destructor. That's all it needs to do. We can nowrest easy knowing that if a Session object is destroyed as part of stack unwinding, terminate will not be called.

There is a second reason why it's bad practice to allow exceptions to propagate out of destructors. If anexception is thrown from a destructor and is not caught there, that destructor won't run to completion. (It willstop at the point where the exception is thrown.) If the destructor doesn't run to completion, it won't doeverything it's supposed to do. For example, consider a modified version of the Session class where the creationof a session starts a database transaction and the termination of a session ends that transaction:

Session::Session() // to keep things simple,

{ // this ctor handles no

// exceptions

logCreation(this);

startTransaction(); // start DB transaction

}

Session::~Session()

{

logDestruction(this);

endTransaction(); // end DB transaction

}

Here, if logDestruction throws an exception, the transaction started in the Session constructor will never beended. In this case, we might be able to reorder the function calls in Session's destructor to eliminate theproblem, but if endTransaction might throw an exception, we've no choice but to revert to try and catch blocks.

We thus find ourselves with two good reasons for keeping exceptions from propagating out of destructors. First,it prevents terminate from being called during the stack-unwinding part of exception propagation. Second, ithelps ensure that destructors always accomplish everything they are supposed to accomplish. Each argument isconvincing in its own right, but together, the case is ironclad. (If you're still not convinced, turn to Herb Sutter'sarticle; in particular, to the section entitled, "Destructors That Throw and Why They're Evil.)

Back to Item 10: Prevent resource leaks in constructors Continue to Item 12: Understand how throwing an exception differs from passing a parameter or calling a virtual function

4 Now there is. In July 1995, the °ISO/ANSI standardization committee for C++ added a function,uncaught_exception, that returns true if an exception is active and has not yet been caught. Return

Page 254: Effective C++ and More Effective C++

Back to Item 11: Prevent exceptions from leaving destructors Continue to Item 13: Catch exceptions by reference

Item 12: Understand how throwing an exception differs from passing a parameter or calling a virtual function.

The syntax for declaring function parameters is almost the same as that for catch clauses:

class Widget { ... }; // some class; it makes no

// difference what it is

void f1(Widget w); // all these functions

void f2(Widget& w); // take parameters of

void f3(const Widget& w); // type Widget, Widget&, or

void f4(Widget *pw); // Widget*

void f5(const Widget *pw);

catch (Widget w) ... // all these catch clauses

catch (Widget& w) ... // catch exceptions of

catch (const Widget& w) ... // type Widget, Widget&, or

catch (Widget *pw) ... // Widget*

catch (const Widget *pw) ...

You might therefore assume that passing an exception from a throw site to a catch clause is basically the sameas passing an argument from a function call site to the function's parameter. There are some similarities, to besure, but there are significant differences, too.

Let us begin with a similarity. You can pass both function parameters and exceptions by value, by reference, orby pointer. What happens when you pass parameters and exceptions, however, is quite different. This differencegrows out of the fact that when you call a function, control eventually returns to the call site (unless the functionfails to return), but when you throw an exception, control does not return to the throw site.

Consider a function that both passes a Widget as a parameter and throws a Widget as an exception: // function to read the value of a Widget from a stream

istream operator>>(istream& s, Widget& w);

void passAndThrowWidget()

{

Widget localWidget;

cin >> localWidget; // pass localWidget to operator>>

throw localWidget; // throw localWidget as an exception

}

When localWidget is passed to operator>>, no copying is performed. Instead, the reference w insideoperator>> is bound to localWidget, and anything done to w is really done to localWidget. It's a different storywhen localWidget is thrown as an exception. Regardless of whether the exception is caught by value or byreference (it can't be caught by pointer ? that would be a type mismatch), a copy of localWidget will be made,and it is the copy that is passed to the catch clause. This must be the case, because localWidget will go out ofscope once control leaves passAndThrowWidget, and when localWidget goes out of scope, its destructor willbe called. If localWidget itself were passed to a catch clause, the clause would receive a destructed Widget, anex-Widget, a former Widget, the carcass of what once was but is no longer a Widget. That would not be useful,and that's why C++ specifies that an object thrown as an exception is always copied.

This copying occurs even if the object being thrown is not in danger of being destroyed. For example, ifpassAndThrowWidget declares localWidget to be static,

void passAndThrowWidget()

{

Page 255: Effective C++ and More Effective C++

static Widget localWidget; // this is now static; it

// will exist until the

// end of the program

cin >> localWidget; // this works as before

throw localWidget; // a copy of localWidget is

} // still made and thrown

a copy of localWidget would still be made when the exception was thrown. This means that even if theexception is caught by reference, it is not possible for the catch block to modify localWidget; it can only modifya copy of localWidget. This mandatory copying of exception objects helps explain another difference betweenparameter passing and throwing an exception: the latter is typically much slower than the former (see Item 15).

When an object is copied for use as an exception, the copying is performed by the object's copy constructor.This copy constructor is the one in the class corresponding to the object's static type, not its dynamic type. Forexample, consider this slightly modified version of passAndThrowWidget:

class Widget { ... };

class SpecialWidget: public Widget { ... };

void passAndThrowWidget()

{

SpecialWidget localSpecialWidget;

...

Widget& rw = localSpecialWidget; // rw refers to a

// SpecialWidget

throw rw; // this throws an

// exception of type

} // Widget!

Here a Widget exception is thrown, even though rw refers to a SpecialWidget. That's because rw's static type isWidget, not SpecialWidget. That rw actually refers to a SpecialWidget is of no concern to your compilers; allthey care about is rw's static type. This behavior may not be what you want, but it's consistent with all othercases in which C++ copies objects. Copying is always based on an object's static type (but see Item 25 for atechnique that lets you make copies on the basis of an object's dynamic type).

The fact that exceptions are copies of other objects has an impact on how you propagate exceptions from a catchblock. Consider these two catch blocks, which at first glance appear to do the same thing:

catch (Widget& w) // catch Widget exceptions

{

... // handle the exception

throw; // rethrow the exception so it

} // continues to propagate

catch (Widget& w) // catch Widget exceptions

{

... // handle the exception

throw w; // propagate a copy of the

Page 256: Effective C++ and More Effective C++

} // caught exception

The only difference between these blocks is that the first one rethrows the current exception, while the secondone throws a new copy of the current exception. Setting aside the performance cost of the additional copyoperation, is there a difference between these approaches?

There is. The first block rethrows the current exception, regardless of its type. In particular, if the exceptionoriginally thrown was of type SpecialWidget, the first block would propagate a SpecialWidget exception, eventhough w's static type is Widget. This is because no copy is made when the exception is rethrown. The secondcatch block throws a new exception, which will always be of type Widget, because that's w's static type. Ingeneral, you'll want to use the

throw;

syntax to rethrow the current exception, because there's no chance that that will change the type of the exceptionbeing propagated. Furthermore, it's more efficient, because there's no need to generate a new exception object.

(Incidentally, the copy made for an exception is a temporary object. As Item 19 explains, this gives compilersthe right to optimize it out of existence. I wouldn't expect your compilers to work that hard, however. Exceptionsare supposed to be rare, so it makes little sense for compiler vendors to pour a lot of energy into theiroptimization.)

Let us examine the three kinds of catch clauses that could catch the Widget exception thrown bypassAndThrowWidget. They are:

catch (Widget w) ... // catch exception by value

catch (Widget& w) ... // catch exception by

// reference

catch (const Widget& w) ... // catch exception by

// reference-to-const

Right away we notice another difference between parameter passing and exception propagation. A thrownobject (which, as explained above, is always a temporary) may be caught by simple reference; it need not becaught by reference-to-const. Passing a temporary object to a non-const reference parameter is not allowed forfunction calls (see Item 19), but it is for exceptions.

Let us overlook this difference, however, and return to our examination of copying exception objects. We knowthat when we pass a function argument by value, we make a copy of the passed object (see Item E22), and westore that copy in a function parameter. The same thing happens when we pass an exception by value. Thus,when we declare a catch clause like this,

catch (Widget w) ... // catch by value

we expect to pay for the creation of two copies of the thrown object, one to create the temporary that allexceptions generate, the second to copy that temporary into w. Similarly, when we catch an exception byreference,

catch (Widget& w) ... // catch by reference

catch (const Widget& w) ... // also catch by reference

we still expect to pay for the creation of a copy of the exception: the copy that is the temporary. In contrast,when we pass function parameters by reference, no copying takes place. When throwing an exception, then, we

Page 257: Effective C++ and More Effective C++

expect to construct (and later destruct) one more copy of the thrown object than if we passed the same object toa function.

We have not yet discussed throwing exceptions by pointer, but throw by pointer is equivalent to pass by pointer.Either way, a copy of the pointer is passed. About all you need to remember is not to throw a pointer to a localobject, because that local object will be destroyed when the exception leaves the local object's scope. The catchclause would then be initialized with a pointer to an object that had already been destroyed. This is the behaviorthe mandatory copying rule is designed to avoid.

The way in which objects are moved from call or throw sites to parameters or catch clauses is one way in whichargument passing differs from exception propagation. A second difference lies in what constitutes a type matchbetween caller or thrower and callee or catcher. Consider the sqrt function from the standard math library:

double sqrt(double); // from <cmath> or <math.h>

We can determine the square root of an integer like this: int i;

double sqrtOfi = sqrt(i);

There is nothing surprising here. The language allows implicit conversion from int to double, so in the call tosqrt, i is silently converted to a double, and the result of sqrt corresponds to that double. (See Item 5 for a fullerdiscussion of implicit type conversions.) In general, such conversions are not applied when matching exceptionsto catch clauses. In this code,

void f(int value)

{

try {

if (someFunction()) { // if someFunction() returns

throw value; // true, throw an int

...

}

}

catch (double d) { // handle exceptions of

... // type double here

}

...

}

the int exception thrown inside the try block will never be caught by the catch clause that takes a double. Thatclause catches only exceptions that are exactly of type double; no type conversions are applied. As a result, ifthe int exception is to be caught, it will have to be by some other (dynamically enclosing) catch clause taking anint or an int& (possibly modified by const or volatile).

Two kinds of conversions are applied when matching exceptions to catch clauses. The first is inheritance-basedconversions. A catch clause for base class exceptions is allowed to handle exceptions of derived class types,too. For example, consider the diagnostics portion of the hierarchy of exceptions defined by the standard C++library (see Item E49):

Page 258: Effective C++ and More Effective C++
Page 259: Effective C++ and More Effective C++

A catch clause for runtime_errors can catch exceptions of type range_error and overflow_error, too, and a catchclause accepting an object of the root class exception can catch any kind of exception derived from thishierarchy.

This inheritance-based exception-conversion rule applies to values, references, and pointers in the usualfashion:

catch (runtime_error) ... // can catch errors of type

Page 260: Effective C++ and More Effective C++

catch (runtime_error&) ... // runtime_error,

catch (const runtime_error&) ... // range_error, or

// overflow_error

catch (runtime_error*) ... // can catch errors of type

catch (const runtime_error*) ... // runtime_error*,

// range_error*, or

// overflow_error*

The second type of allowed conversion is from a typed to an untyped pointer, so a catch clause taking a constvoid* pointer will catch an exception of any pointer type:

catch (const void*) ... // catches any exception

// that's a pointer

The final difference between passing a parameter and propagating an exception is that catch clauses are alwaystried in the order of their appearance. Hence, it is possible for an exception of a derived class type to behandled by a catch clause for one of its base class types ? even when a catch clause for the derived class isassociated with the same try block! For example,

try {

...

}

catch (logic_error& ex) { // this block will catch

... // all logic_error

} // exceptions, even those

// of derived types

catch (invalid_argument& ex) { // this block can never be

... // executed, because all

} // invalid_argument

// exceptions will be caught

// by the clause above

Contrast this behavior with what happens when you call a virtual function. When you call a virtual function, thefunction invoked is the one in the class closest to the dynamic type of the object invoking the function. You mightsay that virtual functions employ a "best fit" algorithm, while exception handling follows a "first fit" strategy.Compilers may warn you if a catch clause for a derived class comes after one for a base class (some issue anerror, because such code used to be illegal in C++), but your best course of action is preemptive: never put acatch clause for a base class before a catch clause for a derived class. The code above, for example, should bereordered like this:

try {

...

}

catch (invalid_argument& ex) { // handle invalid_argument

... // exceptions here

}

catch (logic_error& ex) { // handle all other

... // logic_errors here

}

There are thus three primary ways in which passing an object to a function or using that object to invoke avirtual function differs from throwing the object as an exception. First, exception objects are always copied;when caught by value, they are copied twice. Objects passed to function parameters need not be copied at all.Second, objects thrown as exceptions are subject to fewer forms of type conversion than are objects passed tofunctions. Finally, catch clauses are examined in the order in which they appear in the source code, and the firstone that can succeed is selected for execution. When an object is used to invoke a virtual function, the functionselected is the one that provides the best match for the type of the object, even if it's not the first one listed in the

Page 261: Effective C++ and More Effective C++

source code. Back to Item 11: Prevent exceptions from leaving destructors

Continue to Item 13: Catch exceptions by reference

Page 262: Effective C++ and More Effective C++

Back to Item 12: Understand how throwing an exception differs from passing a parameter or calling a virtual function Continue to Item 14: Use exception specifications judiciously

Item 13: Catch exceptions by reference.

When you write a catch clause, you must specify how exception objects are to be passed to that clause. Youhave three choices, just as when specifying how parameters should be passed to functions: by pointer, by value,or by reference.

Let us consider first catch by pointer. In theory, this should be the least inefficient way to implement theinvariably slow process of moving an exception from throw site to catch clause (see Item 15). That's becausethrow by pointer is the only way of moving exception information without copying an object (see Item 12). Forexample:

class exception { ... }; // from the standard C++

// library exception

// hierarchy (see Item 12)

void someFunction()

{

static exception ex; // exception object

...

throw &ex; // throw a pointer to ex

...

}

void doSomething()

{

try {

someFunction(); // may throw an exception*

}

catch (exception *ex) { // catches the exception*;

... // no object is copied

}

}

This looks neat and tidy, but it's not quite as well-kept as it appears. For this to work, programmers must defineexception objects in a way that guarantees the objects exist after control leaves the functions throwing pointersto them. Global and static objects work fine, but it's easy for programmers to forget the constraint. If they do,they typically end up writing code like this:

void someFunction()

{

exception ex; // local exception object;

// will be destroyed when

// this function's scope is

... // exited

throw &ex; // throw a pointer to an

... // object that's about to

} // be destroyed

This is worse than useless, because the catch clause handling this exception receives a pointer to an object thatno longer exists.

An alternative is to throw a pointer to a new heap object:

Page 263: Effective C++ and More Effective C++

void someFunction()

{

...

throw new exception; // throw a pointer to a new heap-

... // based object (and hope that

} // operator new ? see Item 8 ?

// doesn't itself throw an

// exception!)

This avoids the I-just-caught-a-pointer-to-a-destroyed-object problem, but now authors of catch clausesconfront a nasty question: should they delete the pointer they receive? If the exception object was allocated onthe heap, they must, otherwise they suffer a resource leak. If the exception object wasn't allocated on the heap,they mustn't, otherwise they suffer undefined program behavior. What to do?

It's impossible to know. Some clients might pass the address of a global or static object, others might pass theaddress of an exception on the heap. Catch by pointer thus gives rise to the Hamlet conundrum: to delete or notto delete? It's a question with no good answer. You're best off ducking it.

Furthermore, catch-by-pointer runs contrary to the convention established by the language itself. The fourstandard exceptions ? bad_alloc (thrown when operator new (see Item 8) can't satisfy a memory request),bad_cast (thrown when a dynamic_cast to a reference fails; see Item 2), bad_typeid (thrown when dynamic_castis applied to a null pointer), and bad_exception (available for unexpected exceptions; see Item 14) ? are allobjects, not pointers to objects, so you have to catch them by value or by reference, anyway.

Catch-by-value eliminates questions about exception deletion and works with the standard exception types.However, it requires that exception objects be copied twice each time they're thrown (see Item 12). It also givesrise to the specter of the slicing problem, whereby derived class exception objects caught as base classexceptions have their derivedness "sliced off." Such "sliced" objects are base class objects: they lack derivedclass data members, and when virtual functions are called on them, they resolve to virtual functions of the baseclass. (Exactly the same thing happens when an object is passed to a function by value ? see Item E22.) Forexample, consider an application employing an exception class hierarchy that extends the standard one:

class exception { // as above, this is a

public: // standard exception class

virtual const char * what() throw();

// returns a brief descrip.

... // of the exception (see

// Item 14 for info about

}; // the "throw()" at the

// end of the declaration)

class runtime_error: // also from the standard

public exception { ... }; // C++ exception hierarchy

class Validation_error: // this is a class added by

public runtime_error { // a client

public:

virtual const char * what() throw();

// this is a redefinition

... // of the function declared

}; // in class exception above

void someFunction() // may throw a validation

{ // exception

...

if (a validation test fails) {

throw Validation_error();

}

Page 264: Effective C++ and More Effective C++

...

}

void doSomething()

{

try {

someFunction(); // may throw a validation

} // exception

catch (exception ex) { // catches all exceptions

// in or derived from

// the standard hierarchy

cerr << ex.what(); // calls exception::what(),

... // never

} // Validation_error::what()

}

The version of what that is called is that of the base class, even though the thrown exception is of typeValidation_error and Validation_error redefines that virtual function. This kind of slicing behavior is almostnever what you want.

That leaves only catch-by-reference. Catch-by-reference suffers from none of the problems we have discussed.Unlike catch-by-pointer, the question of object deletion fails to arise, and there is no difficulty in catching thestandard exception types. Unlike catch-by-value, there is no slicing problem, and exception objects are copiedonly once.

If we rewrite the last example using catch-by-reference, it looks like this:

void someFunction() // nothing changes in this

{ // function

...

if (a validation test fails) {

throw Validation_error();

}

...

}

void doSomething()

{

try {

someFunction(); // no change here

}

catch (exception& ex) { // here we catch by reference

// instead of by value

cerr << ex.what(); // now calls

// Validation_error::what(),

... // not exception::what()

}

}

There is no change at the throw site, and the only change in the catch clause is the addition of an ampersand.

Page 265: Effective C++ and More Effective C++

This tiny modification makes a big difference, however, because virtual functions in the catch block now workas we expect: functions in Validation_error are invoked if they redefine those in exception.

What a happy confluence of events! If you catch by reference, you sidestep questions about object deletion thatleave you damned if you do and damned if you don't; you avoid slicing exception objects; you retain the abilityto catch standard exceptions; and you limit the number of times exception objects need to be copied. So what areyou waiting for? Catch exceptions by reference!

Back to Item 12: Understand how throwing an exception differs from passing a parameter or calling a virtual function Continue to Item 14: Use exception specifications judiciously

Page 266: Effective C++ and More Effective C++

Back to Item 13: Catch exceptions by reference Continue to Item 15: Understand the costs of exception handling

Item 14: Use exception specifications judiciously.

There's no denying it: exception specifications have appeal. They make code easier to understand, because theyexplicitly state what exceptions a function may throw. But they're more than just fancy comments. Compilers aresometimes able to detect inconsistent exception specifications during compilation. Furthermore, if a functionthrows an exception not listed in its exception specification, that fault is detected at runtime, and the specialfunction unexpected is automatically invoked. Both as a documentation aid and as an enforcement mechanism forconstraints on exception usage, then, exception specifications seem attractive.

As is often the case, however, beauty is only skin deep. The default behavior for unexpected is to call terminate,and the default behavior for terminate is to call abort, so the default behavior for a program with a violatedexception specification is to halt. Local variables in active stack frames are not destroyed, because abort shutsdown program execution without performing such cleanup. A violated exception specification is therefore acataclysmic thing, something that should almost never happen.

Unfortunately, it's easy to write functions that make this terrible thing occur. Compilers only partially checkexception usage for consistency with exception specifications. What they do not check for ? what the °languagestandard prohibits them from rejecting (though they may issue a warning) ? is a call to a function that mightviolate the exception specification of the function making the call.

Consider a declaration for a function f1 that has no exception specification. Such a function may throw any kindof exception:

extern void f1(); // might throw anything

Now consider a function f2 that claims, through its exception specification, it will throw only exceptions of typeint:

void f2() throw(int);

It is perfectly legal C++ for f2 to call f1, even though f1 might throw an exception that would violate f2'sexception specification:

void f2() throw(int)

{

...

f1(); // legal even though f1 might throw

// something besides an int

...

}

This kind of flexibility is essential if new code with exception specifications is to be integrated with older codelacking such specifications.

Because your compilers are content to let you call functions whose exception specifications are inconsistentwith those of the routine containing the calls, and because such calls might result in your program's executionbeing terminated, it's important to write your software in such a way that these kinds of inconsistencies areminimized. A good way to start is to avoid putting exception specifications on templates that take typearguments. Consider this template, which certainly looks as if it couldn't throw any exceptions:

// a poorly designed template wrt exception specifications

template<class T>

bool operator==(const T& lhs, const T& rhs) throw()

{

return &lhs == &rhs;

}

This template defines an operator== function for all types. For any pair of objects of the same type, it returns

Page 267: Effective C++ and More Effective C++

true if the objects have the same address, otherwise it returns false.

This template contains an exception specification stating that the functions generated from the template willthrow no exceptions. But that's not necessarily true, because it's possible that operator& (the address-of operator? see Item E45) has been overloaded for some types. If it has, operator& may throw an exception when calledfrom inside operator==. If it does, our exception specification is violated, and off to unexpected we go.

This is a specific example of a more general problem, namely, that there is no way to know anything about theexceptions thrown by a template's type parameters. We can almost never provide a meaningful exceptionspecification for a template, because templates almost invariably use their type parameter in some way. Theconclusion? Templates and exception specifications don't mix.

A second technique you can use to avoid calls to unexpected is to omit exception specifications on functionsmaking calls to functions that themselves lack exception specifications. This is simple common sense, but thereis one case that is easy to forget. That's when allowing users to register callback functions:

// Function pointer type for a window system callback

// when a window system event occurs

typedef void (*CallBackPtr)(int eventXLocation,

int eventYLocation,

void *dataToPassBack);

// Window system class for holding onto callback

// functions registered by window system clients

class CallBack {

public:

CallBack(CallBackPtr fPtr, void *dataToPassBack)

: func(fPtr), data(dataToPassBack) {}

void makeCallBack(int eventXLocation,

int eventYLocation) const throw();

private:

CallBackPtr func; // function to call when

// callback is made

void *data; // data to pass to callback

}; // function

// To implement the callback, we call the registered func-

// tion with event's coordinates and the registered data

void CallBack::makeCallBack(int eventXLocation,

int eventYLocation) const throw()

{

func(eventXLocation, eventYLocation, data);

}

Here the call to func in makeCallBack runs the risk of a violated exception specification, because there is noway of knowing what exceptions func might throw.

This problem can be eliminated by tightening the exception specification in the CallBackPtr typedef:5

typedef void (*CallBackPtr)(int eventXLocation,

int eventYLocation,

void *dataToPassBack) throw();

Given this typedef, it is now an error to register a callback function that fails to guarantee it throws nothing:

// a callback function without an exception specification

void callBackFcn1(int eventXLocation, int eventYLocation,

void *dataToPassBack);

Page 268: Effective C++ and More Effective C++

void *callBackData;

...

CallBack c1(callBackFcn1, callBackData);

// error! callBackFcn1

// might throw an exception

// a callback function with an exception specification

void callBackFcn2(int eventXLocation,

int eventYLocation,

void *dataToPassBack) throw();

CallBack c2(callBackFcn2, callBackData);

// okay, callBackFcn2 has a

// conforming ex. spec.

This checking of exception specifications when passing function pointers is a relatively recent addition to thelanguage, so don't be surprised if your compilers don't yet support it. If they don't, it's up to you to ensure youdon't make this kind of mistake.

A third technique you can use to avoid calls to unexpected is to handle exceptions "the system" may throw. Ofthese exceptions, the most common is bad_alloc, which is thrown by operator new and operator new[] when amemory allocation fails (see Item 8). If you use the new operator (again, see Item 8) in any function, you must beprepared for the possibility that the function will encounter a bad_alloc exception.

Now, an ounce of prevention may be better than a pound of cure, but sometimes prevention is hard and cure iseasy. That is, sometimes it's easier to cope with unexpected exceptions directly than to prevent them from arisingin the first place. If, for example, you're writing software that uses exception specifications rigorously, butyou're forced to call functions in libraries that don't use exception specifications, it's impractical to preventunexpected exceptions from arising, because that would require changing the code in the libraries.

If preventing unexpected exceptions isn't practical, you can exploit the fact that C++ allows you to replaceunexpected exceptions with exceptions of a different type. For example, suppose you'd like all unexpectedexceptions to be replaced by UnexpectedException objects. You can set it up like this,

class UnexpectedException {}; // all unexpected exception

// objects will be replaced

// by objects of this type

void convertUnexpected() // function to call if

{ // an unexpected exception

throw UnexpectedException(); // is thrown

}

and make it happen by replacing the default unexpected function with convertUnexpected: set_unexpected(convertUnexpected);

Once you've done this, any unexpected exception results in convertUnexpected being called. The unexpectedexception is then replaced by a new exception of type UnexpectedException. Provided the exceptionspecification that was violated includes UnexpectedException, exception propagation will then continue as if theexception specification had always been satisfied. (If the exception specification does not includeUnexpectedException, terminate will be called, just as if you had never replaced unexpected.)

Another way to translate unexpected exceptions into a well known type is to rely on the fact that if the

Page 269: Effective C++ and More Effective C++

unexpected function's replacement rethrows the current exception, that exception will be replaced by a newexception of the standard type bad_exception. Here's how you'd arrange for that to happen:

void convertUnexpected() // function to call if

{ // an unexpected exception

throw; // is thrown; just rethrow

} // the current exception

set_unexpected(convertUnexpected);

// install convertUnexpected

// as the unexpected

// replacement

If you do this and you include bad_exception (or its base class, the standard class exception) in all yourexception specifications, you'll never have to worry about your program halting if an unexpected exception isencountered. Instead, any wayward exception will be replaced by a bad_exception, and that exception will bepropagated in the stead of the original one.

By now you understand that exception specifications can be a lot of trouble. Compilers perform only partialchecks for their consistent usage, they're problematic in templates, they're easy to violate inadvertently, and, bydefault, they lead to abrupt program termination when they're violated. Exception specifications have anotherdrawback, too, and that's that they result in unexpected being invoked even when a higher-level caller isprepared to cope with the exception that's arisen. For example, consider this code, which is taken almostverbatim from Item 11:

class Session { // for modeling online

public: // sessions

~Session();

...

private:

static void logDestruction(Session *objAddr) throw();

};

Session::~Session()

{

try {

logDestruction(this);

}

catch (...) { }

}

The Session destructor calls logDestruction to record the fact that a Session object is being destroyed, but itexplicitly catches any exceptions that might be thrown by logDestruction. However, logDestruction comes withan exception specification asserting that it throws no exceptions. Now, suppose some function called bylogDestruction throws an exception that logDestruction fails to catch. This isn't supposed to happen, but aswe've seen, it isn't difficult to write code that leads to the violation of exception specifications. When thisunanticipated exception propagates through logDestruction, unexpected will be called, and, by default, that willresult in termination of the program. This is correct behavior, to be sure, but is it the behavior the author ofSession's destructor wanted? That author took pains to handle all possible exceptions, so it seems almost unfairto halt the program without giving Session's destructor's catch block a chance to work. If logDestruction had noexception specification, this I'm-willing-to-catch-it-if-you'll-just-give-me-a-chance scenario would never arise.(One way to prevent it is to replace unexpected as described above.)

It's important to keep a balanced view of exception specifications. They provide excellent documentation on thekinds of exceptions a function is expected to throw, and for situations in which violating an exceptionspecification is so dire as to justify immediate program termination, they offer that behavior by default. At thesame time, they are only partly checked by compilers and they are easy to violate inadvertently. Furthermore,they can prevent high-level exception handlers from dealing with unexpected exceptions, even when they knowhow to. That being the case, exception specifications are a tool to be applied judiciously. Before adding them to

Page 270: Effective C++ and More Effective C++

your functions, consider whether the behavior they impart to your software is really the behavior you want. Back to Item 13: Catch exceptions by reference

Continue to Item 15: Understand the costs of exception handling

5 Alas, it can't, at least not portably. Though many compilers accept the code shown on this page, the °standardization committee has inexplicably decreed that "an exception specification shall not appear in atypedef." I don't know why. If you need a portable solution, you must ? it hurts me to write this ? makeCallBackPtr a macro, sigh. Return

Page 271: Effective C++ and More Effective C++

Back to Item 14: Use exception specifications judiciously Continue to Efficiency

Item 15: Understand the costs of exception handling.

To handle exceptions at runtime, programs must do a fair amount of bookkeeping. At each point duringexecution, they must be able to identify the objects that require destruction if an exception is thrown; they mustmake note of each entry to and exit from a try block; and for each try block, they must keep track of theassociated catch clauses and the types of exceptions those clauses can handle. This bookkeeping is not free. Norare the runtime comparisons necessary to ensure that exception specifications are satisfied. Nor is the workexpended to destroy the appropriate objects and find the correct catch clause when an exception is thrown. No,exception handling has costs, and you pay at least some of them even if you never use the keywords try, throw,or catch.

Let us begin with the things you pay for even if you never use any exception-handling features. You pay for thespace used by the data structures needed to keep track of which objects are fully constructed (see Item 10), andyou pay for the time needed to keep these data structures up to date. These costs are typically quite modest.Nevertheless, programs compiled without support for exceptions are typically both faster and smaller than theircounterparts compiled with support for exceptions.

In theory, you don't have a choice about these costs: exceptions are part of C++, compilers have to support them,and that's that. You can't even expect compiler vendors to eliminate the costs if you use no exception-handlingfeatures, because programs are typically composed of multiple independently generated object files, and justbecause one object file doesn't do anything with exceptions doesn't mean others don't. Furthermore, even if noneof the object files linked to form an executable use exceptions, what about the libraries they're linked with? If any part of a program uses exceptions, the rest of the program must support them, too. Otherwise it may not bepossible to provide correct exception-handling behavior at runtime.

That's the theory. In practice, most vendors who support exception handling allow you to control whethersupport for exceptions is included in the code they generate. If you know that no part of your program uses try,throw, or catch, and you also know that no library with which you'll link uses try, throw, or catch, you might aswell compile without exception-handling support and save yourself the size and speed penalty you'd otherwiseprobably be assessed for a feature you're not using. As time goes on and libraries employing exceptions becomemore common, this strategy will become less tenable, but given the current state of C++ software development,compiling without support for exceptions is a reasonable performance optimization if you have already decidednot to use exceptions. It may also be an attractive optimization for libraries that eschew exceptions, providedthey can guarantee that exceptions thrown from client code never propagate into the library. This is a difficultguarantee to make, as it precludes client redefinitions of library-declared virtual functions; it also rules outclient-defined callback functions.

A second cost of exception-handling arises from try blocks, and you pay it whenever you use one, i.e., wheneveryou decide you want to be able to catch exceptions. Different compilers implement try blocks in different ways,so the cost varies from compiler to compiler. As a rough estimate, expect your overall code size to increase by5-10% and your runtime to go up by a similar amount if you use try blocks. This assumes no exceptions arethrown; what we're discussing here is just the cost of having try blocks in your programs. To minimize this cost,you should avoid unnecessary try blocks.

Compilers tend to generate code for exception specifications much as they do for try blocks, so an exceptionspecification generally incurs about the same cost as a try block. Excuse me? You say you thought exceptionspecifications were just specifications, you didn't think they generated code? Well, now you have something newto think about.

Which brings us to the heart of the matter, the cost of throwing an exception. In truth, this shouldn't be much of aconcern, because exceptions should be rare. After all, they indicate the occurrence of events that are exceptional. The 80-20 rule (see Item 16) tells us that such events should almost never have much impact on a program'soverall performance. Nevertheless, I know you're curious about just how big a hit you'll take if you throw anexception, and the answer is it's probably a big one. Compared to a normal function return, returning from afunction by throwing an exception may be as much as three orders of magnitude slower. That's quite a hit. Butyou'll take it only if you throw an exception, and that should be almost never. If, however, you've been thinking

Page 272: Effective C++ and More Effective C++

of using exceptions to indicate relatively common conditions like the completion of a data structure traversal orthe termination of a loop, now would be an excellent time to think again.

But wait. How can I know this stuff? If support for exceptions is a relatively recent addition to most compilers(it is), and if different compilers implement their support in different ways (they do), how can I say that aprogram's size will generally grow by about 5-10%, its speed will decrease by a similar amount, and it may runorders of magnitude slower if lots of exceptions are thrown? The answer is frightening: a little rumor and ahandful of benchmarks (see Item 23). The fact is that most people ? including most compiler vendors ? havelittle experience with exceptions, so though we know there are costs associated with them, it is difficult topredict those costs accurately.

The prudent course of action is to be aware of the costs described in this item, but not to take the numbers veryseriously. Whatever the cost of exception handling, you don't want to pay any more than you have to. Tominimize your exception-related costs, compile without support for exceptions when that is feasible; limit youruse of try blocks and exception specifications to those locations where you honestly need them; and throwexceptions only under conditions that are truly exceptional. If you still have performance problems, profile yoursoftware (see Item 16) to determine if exception support is a contributing factor. If it is, consider switching todifferent compilers, ones that provide more efficient implementations of C++'s exception-handling features.

Back to Item 14: Use exception specifications judiciously Continue to Efficiency

Page 273: Effective C++ and More Effective C++

Back to Item 15:Understand the costs of exception handling Continue to Item 16: Remember the 80-20 rule

Efficiency

I harbor a suspicion that someone has performed secret °Pavlovian experiments on C++ software developers.How else can one explain the fact that when the word "efficiency" is mentioned, scores of programmers start todrool?

In fact, efficiency is no laughing matter. Programs that are too big or too slow fail to find acceptance, no matterhow compelling their merits. This is perhaps as it should be. Software is supposed to help us do things better,and it's difficult to argue that slower is better, that demanding 32 megabytes of memory is better than requiring amere 16, that chewing up 100 megabytes of disk space is better than swallowing only 50. Furthermore, thoughsome programs take longer and use more memory because they perform more ambitious computations, too manyprograms can blame their sorry pace and bloated footprint on nothing more than bad design and slipshodprogramming.

Writing efficient programs in C++ starts with the recognition that C++ may well have nothing to do with anyperformance problems you've been having. If you want to write an efficient C++ program, you must first be ableto write an efficient program. Too many developers overlook this simple truth. Yes, loops may be unrolled byhand and multiplications may be replaced by shift operations, but such micro-tuning leads nowhere if thehigher-level algorithms you employ are inherently inefficient. Do you use quadratic algorithms when linear onesare available? Do you compute the same value over and over? Do you squander opportunities to reduce theaverage cost of expensive operations? If so, you can hardly be surprised if your programs are described likesecond-rate tourist attractions: worth a look, but only if you've got some extra time.

The material in this chapter attacks the topic of efficiency from two angles. The first is language-independent,focusing on things you can do in any programming language. C++ provides a particularly appealing implementation medium for these ideas, because its strong support for encapsulation makes it possible toreplace inefficient class implementations with better algorithms and data structures that support the sameinterface.

The second focus is on C++ itself. High-performance algorithms and data structures are great, but sloppyimplementation practices can reduce their effectiveness considerably. The most insidious mistake is both simpleto make and hard to recognize: creating and destroying too many objects. Superfluous object constructions anddestructions act like a hemorrhage on your program's performance, with precious clock-ticks bleeding awayeach time an unnecessary object is created and destroyed. This problem is so pervasive in C++ programs, Idevote four separate items to describing where these objects come from and how you can eliminate them withoutcompromising the correctness of your code.

Programs don't get big and slow only by creating too many objects. Other potholes on the road to highperformance include library selection and implementations of language features. In the items that follow, Iaddress these issues, too.

After reading the material in this chapter, you'll be familiar with several principles that can improve theperformance of virtually any program you write, you'll know exactly how to prevent unnecessary objects fromcreeping into your software, and you'll have a keener awareness of how your compilers behave when generatingexecutables.

It's been said that forewarned is forearmed. If so, think of the information that follows as preparation for battle. Back to Item 15: Understand the costs of exception handling

Continue to Item 16: Remember the 80-20 rule

Page 274: Effective C++ and More Effective C++

Back to Efficiency Continue to Item 17: Consider using lazy evaluation

Item 16: Remember the 80-20 rule.

The 80-20 rule states that 80 percent of a program's resources are used by about 20 percent of the code: 80percent of the runtime is spent in approximately 20 percent of the code; 80 percent of the memory is used bysome 20 percent of the code; 80 percent of the disk accesses are performed for about 20 percent of the code; 80percent of the maintenance effort is devoted to around 20 percent of the code. The rule has been repeatedlyverified through examinations of countless machines, operating systems, and applications. The 80-20 rule ismore than just a catchy phrase; it's a guideline about system performance that has both wide applicability and asolid empirical basis.

When considering the 80-20 rule, it's important not to get too hung up on numbers. Some people favor the morestringent 90-10 rule, and there's experimental evidence to back that, too. Whatever the precise numbers, thefundamental point is this: the overall performance of your software is almost always determined by a small partof its constituent code.

As a programmer striving to maximize your software's performance, the 80-20 rule both simplifies andcomplicates your life. On one hand, the 80-20 rule implies that most of the time you can produce code whoseperformance is, frankly, rather mediocre, because 80 percent of the time its efficiency doesn't affect the overallperformance of the system you're working on. That may not do much for your ego, but it should reduce yourstress level a little. On the other hand, the rule implies that if your software has a performance problem, you'vegot a tough job ahead of you, because you not only have to locate the small pockets of code that are causing theproblem, you have to find ways to increase their performance dramatically. Of these tasks, the more troublesomeis generally locating the bottlenecks. There are two fundamentally different ways to approach the matter: the waymost people do it and the right way.

The way most people locate bottlenecks is to guess. Using experience, intuition, tarot cards and Ouija boards,rumors or worse, developer after developer solemnly proclaims that a program's efficiency problems can betraced to network delays, improperly tuned memory allocators, compilers that don't optimize aggressivelyenough, or some bonehead manager's refusal to permit assembly language for crucial inner loops. Suchassessments are generally delivered with a condescending sneer, and usually both the sneerers and theirprognostications are flat-out wrong.

Most programmers have lousy intuition about the performance characteristics of their programs, becauseprogram performance characteristics tend to be highly unintuitive. As a result, untold effort is poured intoimproving the efficiency of parts of programs that will never have a noticeable effect on their overall behavior.For example, fancy algorithms and data structures that minimize computation may be added to a program, but it'sall for naught if the program is I/O-bound. Souped-up I/O libraries (see Item 23) may be substituted for the onesshipped with compilers, but there's not much point if the programs using them are CPU-bound.

That being the case, what do you do if you're faced with a slow program or one that uses too much memory? The80-20 rule means that improving random parts of the program is unlikely to help very much. The fact thatprograms tend to have unintuitive performance characteristics means that trying to guess the causes ofperformance bottlenecks is unlikely to be much better than just improving random parts of your program. What,then, will work?

What will work is to empirically identify the 20 percent of your program that is causing you heartache, and theway to identify that horrid 20 percent is to use a program profiler. Not just any profiler will do, however. Youwant one that directly measures the resources you are interested in. For example, if your program is too slow,you want a profiler that tells you how much time is being spent in different parts of the program. That way youcan focus on those places where a significant improvement in local efficiency will also yield a significantimprovement in overall efficiency.

Profilers that tell you how many times each statement is executed or how many times each function is called areof limited utility. From a performance point of view, you do not care how many times a statement is executed ora function is called. It is, after all, rather rare to encounter a user of a program or a client of a library whocomplains that too many statements are being executed or too many functions are being called. If your software

Page 275: Effective C++ and More Effective C++

is fast enough, nobody cares how many statements are executed, and if it's too slow, nobody cares how few. Allthey care about is that they hate to wait, and if your program is making them do it, they hate you, too.

Still, knowing how often statements are executed or functions are called can sometimes yield insight into whatyour software is doing. If, for example, you think you're creating about a hundred objects of a particular type, itwould certainly be worthwhile to discover that you're calling constructors in that class thousands of times.Furthermore, statement and function call counts can indirectly help you understand facets of your software'sbehavior you can't directly measure. If you have no direct way of measuring dynamic memory usage, forexample, it may be helpful to know at least how often memory allocation and deallocation functions (e.g.,operators new, new[], delete, and delete[] ? see Item 8) are called.

Of course, even the best of profilers is hostage to the data it's given to process. If you profile your programwhile it's processing unrepresentative input data, you're in no position to complain if the profiler leads you tofine-tune parts of your software ? the parts making up some 80 percent of it ? that have no bearing on its usualperformance. Remember that a profiler can only tell you how a program behaved on a particular run (or set ofruns), so if you profile a program using input data that is unrepresentative, you're going to get back a profile thatis equally unrepresentative. That, in turn, is likely to lead to you to optimize your software's behavior foruncommon uses, and the overall impact on common uses may even be negative.

The best way to guard against these kinds of pathological results is to profile your software using as many datasets as possible. Moreover, you must ensure that each data set is representative of how the software is used byits clients (or at least its most important clients). It is usually easy to acquire representative data sets, becausemany clients are happy to let you use their data when profiling. After all, you'll then be tuning your software tomeet their needs, and that can only be good for both of you.

Back to Efficiency Continue to Item 17: Consider using lazy evaluation

Page 276: Effective C++ and More Effective C++

Back to Item 16: Remember the 80-20 rule Continue to Item 18: Amortize the cost of expected computations

Item 17: Consider using lazy evaluation.

From the perspective of efficiency, the best computations are those you never perform at all. That's fine, but ifyou don't need to do something, why would you put code in your program to do it in the first place? And if youdo need to do something, how can you possibly avoid executing the code that does it?

The key is to be lazy.

Remember when you were a child and your parents told you to clean your room? If you were anything like me,you'd say "Okay," then promptly go back to what you were doing. You would not clean your room. In fact,cleaning your room would be the last thing on your mind ? until you heard your parents coming down the hall toconfirm that your room had, in fact, been cleaned. Then you'd sprint to your room and get to work as fast as youpossibly could. If you were lucky, your parents would never check, and you'd avoid all the work cleaning yourroom normally entails.

It turns out that the same delay tactics that work for a five year old work for a C++ programmer. In ComputerScience, however, we dignify such procrastination with the name lazy evaluation. When you employ lazyevaluation, you write your classes in such a way that they defer computations until the results of thosecomputations are required. If the results are never required, the computations are never performed, and neitheryour software's clients nor your parents are any the wiser.

Perhaps you're wondering exactly what I'm talking about. Perhaps an example would help. Well, lazy evaluationis applicable in an enormous variety of application areas, so I'll describe four.

Reference Counting

Consider this code:

class String { ... }; // a string class (the standard

// string type may be implemented

// as described below, but it

// doesn't have to be)

String s1 = "Hello";

String s2 = s1; // call String copy ctor

A common implementation for the String copy constructor would result in s1 and s2 each having its own copy of"Hello" after s2 is initialized with s1. Such a copy constructor would incur a relatively large expense, because itwould have to make a copy of s1's value to give to s2, and that would typically entail allocating heap memoryvia the new operator (see Item 8) and calling strcpy to copy the data in s1 into the memory allocated by s2. Thisis eager evaluation: making a copy of s1 and putting it into s2 just because the String copy constructor wascalled. At this point, however, there has been no real need for s2 to have a copy of the value, because s2 hasn'tbeen used yet.

The lazy approach is a lot less work. Instead of giving s2 a copy of s1's value, we have s2 share s1's value. Allwe have to do is a little bookkeeping so we know who's sharing what, and in return we save the cost of a call tonew and the expense of copying anything. The fact that s1 and s2 are sharing a data structure is transparent toclients, and it certainly makes no difference in statements like the following, because they only read values, theydon't write them:

cout << s1; // read s1's value

cout << s1 + s2; // read s1's and s2's values

In fact, the only time the sharing of values makes a difference is when one or the other string is modified; then

Page 277: Effective C++ and More Effective C++

it's important that only one string be changed, not both. In this statement, s2.convertToUpperCase();

it's crucial that only s2's value be changed, not s1's also.

To handle statements like this, we have to implement String's convertToUpperCase function so that it makes acopy of s2's value and makes that value private to s2 before modifying it. Inside convertToUpperCase, we canbe lazy no longer: we have to make a copy of s2's (shared) value for s2's private use. On the other hand, if s2 isnever modified, we never have to make a private copy of its value. It can continue to share a value as long as itexists. If we're lucky, s2 will never be modified, in which case we'll never have to expend the effort to give it itsown value.

The details on making this kind of value sharing work (including all the code) are provided in Item 29, but theidea is lazy evaluation: don't bother to make a copy of something until you really need one. Instead, be lazy ? usesomeone else's copy as long as you can get away with it. In some application areas, you can often get away withit forever.

Distinguishing Reads from Writes

Pursuing the example of reference-counting strings a bit further, we come upon a second way in which lazyevaluation can help us. Consider this code:

String s = "Homer's Iliad"; // Assume s is a

// reference-counted string

...

cout << s[3]; // call operator[] to read s[3]

s[3] = 'x'; // call operator[] to write s[3]

The first call to operator[] is to read part of a string, but the second call is to perform a write. We'd like to beable to distinguish the read call from the write, because reading a reference-counted string is cheap, but writingto such a string may require splitting off a new copy of the string's value prior to the write.

This puts us in a difficult implementation position. To achieve what we want, we need to do different thingsinside operator[] (depending on whether it's being called to perform a read or a write). How can we determinewhether operator[] has been called in a read or a write context? The brutal truth is that we can't. By using lazyevaluation and proxy classes as described in Item 30, however, we can defer the decision on whether to takeread actions or write actions until we can determine which is correct.

Lazy Fetching

As a third example of lazy evaluation, imagine you've got a program that uses large objects containing manyconstituent fields. Such objects must persist across program runs, so they're stored in a database. Each objecthas a unique object identifier that can be used to retrieve the object from the database:

class LargeObject { // large persistent objects

public:

LargeObject(ObjectID id); // restore object from disk

const string& field1() const; // value of field 1

int field2() const; // value of field 2

double field3() const; // ...

const string& field4() const;

const string& field5() const;

...

};

Page 278: Effective C++ and More Effective C++

Now consider the cost of restoring a LargeObject from disk: void restoreAndProcessObject(ObjectID id)

{

LargeObject object(id); // restore object

...

}

Because LargeObject instances are big, getting all the data for such an object might be a costly databaseoperation, especially if the data must be retrieved from a remote database and pushed across a network. In somecases, the cost of reading all that data would be unnecessary. For example, consider this kind of application:

void restoreAndProcessObject(ObjectID id)

{

LargeObject object(id);

if (object.field2() == 0) {

cout << "Object " << id << ": null field2.\n";

}

}

Here only the value of field2 is required, so any effort spent setting up the other fields is wasted.

The lazy approach to this problem is to read no data from disk when a LargeObject object is created. Instead,only the "shell" of an object is created, and data is retrieved from the database only when that particular data isneeded inside the object. Here's one way to implement this kind of "demand-paged" object initialization:

class LargeObject {

public:

LargeObject(ObjectID id);

const string& field1() const;

int field2() const;

double field3() const;

const string& field4() const;

...

private:

ObjectID oid;

mutable string *field1Value; // see below for a

mutable int *field2Value; // discussion of "mutable"

mutable double *field3Value;

mutable string *field4Value;

...

};

LargeObject::LargeObject(ObjectID id)

: oid(id), field1Value(0), field2Value(0), field3Value(0), ...

{}

const string& LargeObject::field1() const

{

if (field1Value == 0) {

read the data for field 1 from the database and make

field1Value point to it;

}

return *field1Value;

}

Page 279: Effective C++ and More Effective C++

Each field in the object is represented as a pointer to the necessary data, and the LargeObject constructorinitializes each pointer to null. Such null pointers signify fields that have not yet been read from the database.Each LargeObject member function must check the state of a field's pointer before accessing the data it points to.If the pointer is null, the corresponding data must be read from the database before performing any operations onthat data.

When implementing lazy fetching, you must confront the problem that null pointers may need to be initialized topoint to real data from inside any member function, including const member functions like field1. However,compilers get cranky when you try to modify data members inside const member functions, so you've got to finda way to say, "It's okay, I know what I'm doing." The best way to say that is to declare the pointer fields mutable,which means they can be modified inside any member function, even inside const member functions (see ItemE21). That's why the fields inside LargeObject above are declared mutable.

The mutable keyword is a relatively recent addition to C++, so it's possible your vendors don't yet support it. Ifnot, you'll need to find another way to convince your compilers to let you modify data members inside constmember functions. One workable strategy is the "fake this" approach, whereby you create a pointer-to-non-constthat points to the same object as this does. When you want to modify a data member, you access it through the"fake this" pointer:

class LargeObject {

public:

const string& field1() const; // unchanged

...

private:

string *field1Value; // not declared mutable

... // so that older

}; // compilers will accept it

const string& LargeObject::field1() const

{

// declare a pointer, fakeThis, that points where this

// does, but where the constness of the object has been

// cast away

LargeObject * const fakeThis =

const_cast<LargeObject* const>(this);

if (field1Value == 0) {

fakeThis->field1Value = // this assignment is OK,

the appropriate data // because what fakeThis

from the database; // points to isn't const

}

return *field1Value;

}

This function employs a const_cast (see Item 2) to cast away the constness of *this. If your compilers don'tsupport const_cast, you can use an old C-style cast:

// Use of old-style cast to help emulate mutable

const string& LargeObject::field1() const

{

LargeObject * const fakeThis = (LargeObject* const)this;

... // as above

}

Look again at the pointers inside LargeObject. Let's face it, it's tedious and error-prone to have to initialize allthose pointers to null, then test each one before use. Fortunately, such drudgery can be automated through the useof smart pointers, which you can read about in Item 28. If you use smart pointers inside LargeObject, you'll alsofind you no longer need to declare the pointers mutable. Alas, it's only a temporary respite, because you'll wind

Page 280: Effective C++ and More Effective C++

up needing mutable once you sit down to implement the smart pointer classes. Think of it as conservation ofinconvenience.

Lazy Expression Evaluation

A final example of lazy evaluation comes from numerical applications. Consider this code: template<class T>

class Matrix { ... }; // for homogeneous matrices

Matrix<int> m1(1000, 1000); // a 1000 by 1000 matrix

Matrix<int> m2(1000, 1000); // ditto

...

Matrix<int> m3 = m1 + m2; // add m1 and m2

The usual implementation of operator+ would use eager evaluation; in this case it would compute and return thesum of m1 and m2. That's a fair amount of computation (1,000,000 additions), and of course there's the cost ofallocating the memory to hold all those values, too.

The lazy evaluation strategy says that's way too much work, so it doesn't do it. Instead, it sets up a data structureinside m3 that indicates that m3's value is the sum of m1 and m2. Such a data structure might consist of nothingmore than a pointer to each of m1 and m2, plus an enum indicating that the operation on them is addition.Clearly, it's going to be faster to set up this data structure than to add m1 and m2, and it's going to use a lot lessmemory, too.

Suppose that later in the program, before m3 has been used, this code is executed: Matrix<int> m4(1000, 1000);

... // give m4 some values

m3 = m4 * m1;

Now we can forget all about m3 being the sum of m1 and m2 (and thereby save the cost of the computation), andin its place we can start remembering that m3 is the product of m4 and m1. Needless to say, we don't perform themultiplication. Why bother? We're lazy, remember?

This example looks contrived, because no good programmer would write a program that computed the sum oftwo matrices and failed to use it, but it's not as contrived as it seems. No good programmer would deliberatelycompute a value that's not needed, but during maintenance, it's not uncommon for a programmer to modify thepaths through a program in such a way that a formerly useful computation becomes unnecessary. The likelihoodof that happening is reduced by defining objects immediately prior to use (see Item E32), but it's still a problemthat occurs from time to time.

Nevertheless, if that were the only time lazy evaluation paid off, it would hardly be worth the trouble. A morecommon scenario is that we need only part of a computation. For example, suppose we use m3 as follows afterinitializing it to the sum of m1 and m2:

cout << m3[4]; // print the 4th row of m3

Clearly we can be completely lazy no longer ? we've got to compute the values in the fourth row of m3. But let'snot be overly ambitious, either. There's no reason we have to compute any more than the fourth row of m3; theremainder of m3 can remain uncomputed until it's actually needed. With luck, it never will be.

How likely are we to be lucky? Experience in the domain of matrix computations suggests the odds are in ourfavor. In fact, lazy evaluation lies behind the wonder that is APL. APL was developed in the 1960s forinteractive use by people who needed to perform matrix-based calculations. Running on computers that had lesscomputational horsepower than the chips now found in high-end microwave ovens, APL was seemingly able to

Page 281: Effective C++ and More Effective C++

add, multiply, and even divide large matrices instantly! Its trick was lazy evaluation. The trick was usuallyeffective, because APL users typically added, multiplied, or divided matrices not because they needed the entireresulting matrix, but only because they needed a small part of it. APL employed lazy evaluation to defer itscomputations until it knew exactly what part of a result matrix was needed, then it computed only that part. Inpractice, this allowed users to perform computationally intensive tasks interactively in an environment wherethe underlying machine was hopelessly inadequate for an implementation employing eager evaluation. Machinesare faster today, but data sets are bigger and users less patient, so many contemporary matrix libraries continueto take advantage of lazy evaluation.

To be fair, laziness sometimes fails to pay off. If m3 is used in this way,

cout << m3; // print out all of m3

the jig is up and we've got to compute a complete value for m3. Similarly, if one of the matrices on which m3 isdependent is about to be modified, we have to take immediate action:

m3 = m1 + m2; // remember that m3 is the

// sum of m1 and m2

m1 = m4; // now m3 is the sum of m2

// and the OLD value of m1!

Here we've got to do something to ensure that the assignment to m1 doesn't change m3. Inside the Matrix<int>assignment operator, we might compute m3's value prior to changing m1 or we might make a copy of the oldvalue of m1 and make m3 dependent on that, but we have to do something to guarantee that m3 has the value it'ssupposed to have after m1 has been the target of an assignment. Other functions that might modify a matrix mustbe handled in a similar fashion.

Because of the need to store dependencies between values; to maintain data structures that can store values,dependencies, or a combination of the two; and to overload operators like assignment, copying, and addition,lazy evaluation in a numerical domain is a lot of work. On the other hand, it often ends up saving significantamounts of time and space during program runs, and in many applications, that's a payoff that easily justifies thesignificant effort lazy evaluation requires.

Summary

These four examples show that lazy evaluation can be useful in a variety of domains: to avoid unnecessarycopying of objects, to distinguish reads from writes using operator[], to avoid unnecessary reads fromdatabases, and to avoid unnecessary numerical computations. Nevertheless, it's not always a good idea. Just asprocrastinating on your clean-up chores won't save you any work if your parents always check up on you, lazyevaluation won't save your program any work if all your computations are necessary. Indeed, if all yourcomputations are essential, lazy evaluation may slow you down and increase your use of memory, because, inaddition to having to do all the computations you were hoping to avoid, you'll also have to manipulate the fancydata structures needed to make lazy evaluation possible in the first place. Lazy evaluation is only useful whenthere's a reasonable chance your software will be asked to perform computations that can be avoided.

There's nothing about lazy evaluation that's specific to C++. The technique can be applied in any programminglanguage, and several languages ? notably APL, some dialects of Lisp, and virtually all dataflow languages ?embrace the idea as a fundamental part of the language. Mainstream programming languages employ eagerevaluation, however, and C++ is mainstream. Yet C++ is particularly suitable as a vehicle for user-implementedlazy evaluation, because its support for encapsulation makes it possible to add lazy evaluation to a class withoutclients of that class knowing it's been done.

Look again at the code fragments used in the above examples, and you can verify that the class interfaces offerno hints about whether eager or lazy evaluation is used by the classes. That means it's possible to implement aclass using a straightforward eager evaluation strategy, but then, if your profiling investigations (see Item 16)show that class's implementation is a performance bottleneck, you can replace its implementation with onebased on lazy evaluation. (See also Item E34.) The only change your clients will see (after recompilation or

Page 282: Effective C++ and More Effective C++

relinking) is improved performance. That's the kind of software enhancement clients love, one that can make youdownright proud to be lazy.

Back to Item 16: Remember the 80-20 rule Continue to Item 18: Amortize the cost of expected computations

Page 283: Effective C++ and More Effective C++

Back to Item 17: Consider using lazy evaluation Continue to Item 19: Understand the origin of temporary objects

Item 18: Amortize the cost of expected computations.

In Item 17, I extolled the virtues of laziness, of putting things off as long as possible, and I explained howlaziness can improve the efficiency of your programs. In this item, I adopt a different stance. Here, laziness hasno place. I now encourage you to improve the performance of your software by having it do more than it's askedto do. The philosophy of this item might be called over-eager evaluation: doing things before you're asked to dothem.

Consider, for example, a template for classes representing large collections of numeric data: template<class NumericalType>

class DataCollection {

public:

NumericalType min() const;

NumericalType max() const;

NumericalType avg() const;

...

};

Assuming the min, max, and avg functions return the current minimum, maximum, and average values of thecollection, there are three ways in which these functions can be implemented. Using eager evaluation, we'dexamine all the data in the collection when min, max, or avg was called, and we'd return the appropriate value.Using lazy evaluation, we'd have the functions return data structures that could be used to determine theappropriate value whenever the functions' return values were actually used. Using over-eager evaluation, we'dkeep track of the running minimum, maximum, and average values of the collection, so when min, max, or avgwas called, we'd be able to return the correct value immediately ? no computation would be required. If min,max, and avg were called frequently, we'd be able to amortize the cost of keeping track of the collection'sminimum, maximum, and average values over all the calls to those functions, and the amortized cost per callwould be lower than with eager or lazy evaluation.

The idea behind over-eager evaluation is that if you expect a computation to be requested frequently, you canlower the average cost per request by designing your data structures to handle the requests especiallyefficiently.

One of the simplest ways to do this is by caching values that have already been computed and are likely to beneeded again. For example, suppose you're writing a program to provide information about employees, and oneof the pieces of information you expect to be requested frequently is an employee's cubicle number. Furthersuppose that employee information is stored in a database, but, for most applications, an employee's cubiclenumber is irrelevant, so the database is not optimized to find it. To avoid having your specialized applicationunduly stress the database with repeated lookups of employee cubicle numbers, you could write afindCubicleNumber function that caches the cubicle numbers it looks up. Subsequent requests for cubicle numbers that have already been retrieved can then be satisfied by consulting the cache instead of querying thedatabase.

Here's one way to implement findCubicleNumber; it uses a map object from the Standard Template Library (the"STL" ? see Item 35) as a local cache:

int findCubicleNumber(const string& employeeName)

{

// define a static map to hold (employee name, cubicle number)

// pairs. This map is the local cache.

typedef map<string, int> CubicleMap;

static CubicleMap cubes;

// try to find an entry for employeeName in the cache;

// the STL iterator "it" will then point to the found

// entry, if there is one (see Item 35 for details)

CubicleMap::iterator it = cubes.find(employeeName);

Page 284: Effective C++ and More Effective C++

// "it"'s value will be cubes.end() if no entry was

// found (this is standard STL behavior). If this is

// the case, consult the database for the cubicle

// number, then add it to the cache

if (it == cubes.end()) {

int cubicle =

the result of looking up employeeName's cubicle

number in the database;

cubes[employeeName] = cubicle; // add the pair

// (employeeName, cubicle)

// to the cache

return cubicle;

}

else {

// "it" points to the correct cache entry, which is a

// (employee name, cubicle number) pair. We want only

// the second component of this pair, and the member

// "second" will give it to us

return (*it).second;

}

}

Try not to get bogged down in the details of the STL code (which will be clearer after you've read Item 35).Instead, focus on the general strategy embodied by this function. That strategy is to use a local cache to replacecomparatively expensive database queries with comparatively inexpensive lookups in an in-memory datastructure. Provided we're correct in assuming that cubicle numbers will frequently be requested more than once,the use of a cache in findCubicleNumber should reduce the average cost of returning an employee's cubiclenumber.

One detail of the code requires explanation. The final statement returns (*it).second instead of the moreconventional it->second. Why? The answer has to do with the conventions followed by the STL. In brief, theiterator it is an object, not a pointer, so there is no guarantee that "->" can be applied to it.6 The STL doesrequire that "." and "*" be valid for iterators, however, so (*it).second, though syntactically clumsy, isguaranteed to work.)

Caching is one way to amortize the cost of anticipated computations. Prefetching is another. You can think ofprefetching as the computational equivalent of a discount for buying in bulk. Disk controllers, for example, readentire blocks or sectors of data when they read from disk, even if a program asks for only a small amount ofdata. That's because it's faster to read a big chunk once than to read two or three small chunks at different times.Furthermore, experience has shown that if data in one place is requested, it's quite common to want nearby data,too. This is the infamous locality of reference phenomenon, and systems designers rely on it to justify diskcaches, memory caches for both instructions and data, and instruction prefetches.

Excuse me? You say you don't worry about such low-level things as disk controllers or CPU caches? Noproblem. Prefetching can yield dividends for even one as high-level as you. Imagine, for example, you'd like toimplement a template for dynamic arrays, i.e., arrays that start with a size of one and automatically extendthemselves so that all nonnegative indices are valid:

template<class T> // template for dynamic

class DynArray { ... }; // array-of-T classes

DynArray<double> a; // at this point, only a[0]

// is a legitimate array

// element

a[22] = 3.5; // a is automatically

// extended: valid indices

// are now 0-22

a[32] = 0; // a extends itself again;

Page 285: Effective C++ and More Effective C++

// now a[0]-a[32] are valid

How does a DynArray object go about extending itself when it needs to? A straightforward strategy would be toallocate only as much additional memory as needed, something like this:

template<class T>

T& DynArray<T>::operator[](int index)

{

if (index < 0) {

throw an exception; // negative indices are

} // still invalid

if (index > the current maximum index value) {

call new to allocate enough additional memory so that

index is valid;

}

return the indexth element of the array;

}

This approach simply calls new each time it needs to increase the size of the array, but calls to new invokeoperator new (see Item 8), and calls to operator new (and operator delete) are usually expensive. That's becausethey typically result in calls to the underlying operating system, and system calls are generally slower than arein-process function calls. As a result, we'd like to make as few system calls as possible.

An over-eager evaluation strategy employs this reasoning: if we have to increase the size of the array now toaccommodate index i, the locality of reference principle suggests we'll probably have to increase it in the futureto accommodate some other index a bit larger than i. To avoid the cost of the memory allocation for the second(anticipated) expansion, we'll increase the size of the DynArray now by more than is required to make i valid,and we'll hope that future expansions occur within the range we have thereby provided for. For example, wecould write DynArray::operator[] like this:

template<class T>

T& DynArray<T>::operator[](int index)

{

if (index < 0) throw an exception;

if (index > the current maximum index value) {

int diff = index - the current maximum index value;

call new to allocate enough additional memory so that

index+diff is valid;

}

return the indexth element of the array;

}

This function allocates twice as much memory as needed each time the array must be extended. If we look againat the usage scenario we saw earlier, we note that the DynArray must allocate additional memory only once,even though its logical size is extended twice:

DynArray<double> a; // only a[0] is valid

a[22] = 3.5; // new is called to expand

// a's storage through

// index 44; a's logical

// size becomes 23

a[32] = 0; // a's logical size is

// changed to allow a[32],

// but new isn't called

Page 286: Effective C++ and More Effective C++

If a needs to be extended again, that extension, too, will be inexpensive, provided the new maximum index is nogreater than 44.

There is a common theme running through this Item, and that's that greater speed can often be purchased at a costof increased memory usage. Keeping track of running minima, maxima, and averages requires extra space, but itsaves time. Caching results necessitates greater memory usage but reduces the time needed to regenerate theresults once they've been cached. Prefetching demands a place to put the things that are prefetched, but it reducesthe time needed to access those things. The story is as old as Computer Science: you can often trade space fortime. (Not always, however. Using larger objects means fewer fit on a virtual memory or cache page. In rarecases, making objects bigger reduces the performance of your software, because your paging activity increases,your cache hit rate decreases, or both. How do you find out if you're suffering from such problems? You profile,profile, profile (see Item 16).)

The advice I proffer in this Item ? that you amortize the cost of anticipated computations through over-eagerstrategies like caching and prefetching ? is not contradictory to the advice on lazy evaluation I put forth in Item17. Lazy evaluation is a technique for improving the efficiency of programs when you must support operationswhose results are not always needed. Over-eager evaluation is a technique for improving the efficiency ofprograms when you must support operations whose results are almost always needed or whose results are oftenneeded more than once. Both are more difficult to implement than run-of-the-mill eager evaluation, but both canyield significant performance improvements in programs whose behavioral characteristics justify the extraprogramming effort.

Back to Item 17: Consider using lazy evaluation Continue to Item 19: Understand the origin of temporary objects

6 In July 1995, the °ISO/ANSI committee standardizing C++ added a requirement that STL iterators support the"->" operator, so it->second should now work. Some STL implementations fail to satisfy this requirement,however, so (*it).second is still the more portable construct. Return

Page 287: Effective C++ and More Effective C++

Back to Item 18: Amortize the cost of expected computations Continue to Item 20: Facilitate the return value optimization

Item 19: Understand the origin of temporary objects.

When programmers speak amongst themselves, they often refer to variables that are needed for only a shortwhile as "temporaries." For example, in this swap routine,

template<class T>

void swap(T& object1, T& object2)

{

T temp = object1;

object1 = object2;

object2 = temp;

}

it's common to call temp a "temporary." As far as C++ is concerned, however, temp is not a temporary at all.It's simply an object local to a function.

True temporary objects in C++ are invisible ? they don't appear in your source code. They arise whenever anon-heap object is created but not named. Such unnamed objects usually arise in one of two situations: whenimplicit type conversions are applied to make function calls succeed and when functions return objects. It'simportant to understand how and why these temporary objects are created and destroyed, because the attendantcosts of their construction and destruction can have a noticeable impact on the performance of your programs.

Consider first the case in which temporary objects are created to make function calls succeed. This happenswhen the type of object passed to a function is not the same as the type of the parameter to which it is beingbound. For example, consider a function that counts the number of occurrences of a character in a string:

// returns the number of occurrences of ch in str

size_t countChar(const string& str, char ch);

char buffer[MAX_STRING_LEN];

char c;

// read in a char and a string; use setw to avoid

// overflowing buffer when reading the string

cin >> c >> setw(MAX_STRING_LEN) >> buffer;

cout << "There are " << countChar(buffer, c)

<< " occurrences of the character " << c

<< " in " << buffer << endl;

Look at the call to countChar. The first argument passed is a char array, but the corresponding functionparameter is of type const string&. This call can succeed only if the type mismatch can be eliminated, and yourcompilers will be happy to eliminate it by creating a temporary object of type string. That temporary object isinitialized by calling the string constructor with buffer as its argument. The str parameter of countChar is thenbound to this temporary string object. When countChar returns, the temporary object is automatically destroyed.

Conversions such as these are convenient (though dangerous ? see Item 5), but from an efficiency point of view,the construction and destruction of a temporary string object is an unnecessary expense. There are two generalways to eliminate it. One is to redesign your code so conversions like these can't take place. That strategy isexamined in Item 5. An alternative tack is to modify your software so that the conversions are unnecessary. Item21 describes how you can do that.

These conversions occur only when passing objects by value or when passing to a reference-to-const parameter.They do not occur when passing an object to a reference-to-non-const parameter. Consider this function:

void uppercasify(string& str); // changes all chars in

// str to upper case

Page 288: Effective C++ and More Effective C++

In the character-counting example, a char array could be successfully passed to countChar, but here, trying tocall uppercasify with a char array fails:

char subtleBookPlug[] = "Effective C++";

uppercasify(subtleBookPlug); // error!

No temporary is created to make the call succeed. Why not?

Suppose a temporary were created. Then the temporary would be passed to uppercasify, which would modifythe temporary so its characters were in upper case. But the actual argument to the function call ? subtleBookPlug? would not be affected; only the temporary string object generated from subtleBookPlug would be changed.Surely this is not what the programmer intended. That programmer passed subtleBookPlug to uppercasify, andthat programmer expected subtleBookPlug to be modified. Implicit type conversion for references-to-non-constobjects, then, would allow temporary objects to be changed when programmers expected non-temporary objectsto be modified. That's why the language prohibits the generation of temporaries for non-const referenceparameters. Reference-to-const parameters don't suffer from this problem, because such parameters, by virtue ofbeing const, can't be changed.

The second set of circumstances under which temporary objects are created is when a function returns an object.For instance, operator+ must return an object that represents the sum of its operands (see Item E23). Given atype Number, for example, operator+ for that type would be declared like this:

const Number operator+(const Number& lhs,

const Number& rhs);

The return value of this function is a temporary, because it has no name: it's just the function's return value. Youmust pay to construct and destruct this object each time you call operator+. (For an explanation of why the returnvalue is const, see Item E21.)

As usual, you don't want to incur this cost. For this particular function, you can avoid paying by switching to asimilar function, operator+=; Item 22 tells you about this transformation. For most functions that return objects,however, switching to a different function is not an option and there is no way to avoid the construction anddestruction of the return value. At least, there's no way to avoid it conceptually. Between concept and reality,however, lies a murky zone called optimization, and sometimes you can write your object-returning functions ina way that allows your compilers to optimize temporary objects out of existence. Of these optimizations, themost common and useful is the return value optimization, which is the subject of Item 20.

The bottom line is that temporary objects can be costly, so you want to eliminate them whenever you can. Moreimportant than this, however, is to train yourself to look for places where temporary objects may be created.Anytime you see a reference-to-const parameter, the possibility exists that a temporary will be created to bind tothat parameter. Anytime you see a function returning an object, a temporary will be created (and laterdestroyed). Learn to look for such constructs, and your insight into the cost of "behind the scenes" compileractions will markedly improve.

Back to Item 18: Amortize the cost of expected computations Continue to Item 20: Facilitate the return value optimization

Page 289: Effective C++ and More Effective C++

Back to Item 19: Understand the origin of temporary objects Continue to Item 21: Overload to avoid implicit type conversions

Item 20: Facilitate the return value optimization.

A function that returns an object is frustrating to efficiency aficionados, because the by-value return, includingthe constructor and destructor calls it implies (see Item 19), cannot be eliminated. The problem is simple: afunction either has to return an object in order to offer correct behavior or it doesn't. If it does, there's no way toget rid of the object being returned. Period.

Consider the operator* function for rational numbers: class Rational {

public:

Rational(int numerator = 0, int denominator = 1);

...

int numerator() const;

int denominator() const;

};

// For an explanation of why the return value is const,

// see Item 6

const Rational operator*(const Rational& lhs,

const Rational& rhs);

Without even looking at the code for operator*, we know it must return an object, because it returns the productof two arbitrary numbers. These are arbitrary numbers. How can operator* possibly avoid creating a newobject to hold their product? It can't, so it must create a new object and return it. C++ programmers havenevertheless expended Herculean efforts in a search for the legendary elimination of the by-value return (seeItems E23 and E31).

Sometimes people return pointers, which leads to this syntactic travesty: // an unreasonable way to avoid returning an object

const Rational * operator*(const Rational& lhs,

const Rational& rhs);

Rational a = 10;

Rational b(1, 2);

Rational c = *(a * b); // Does this look "natural"

// to you?

It also raises a question. Should the caller delete the pointer returned by the function? The answer is usuallyyes, and that usually leads to resource leaks.

Other developers return references. That yields an acceptable syntax, // a dangerous (and incorrect) way to avoid returning

// an object

const Rational& operator*(const Rational& lhs,

const Rational& rhs);

Rational a = 10;

Rational b(1, 2);

Rational c = a * b; // looks perfectly reasonable

but such functions can't be implemented in a way that behaves correctly. A common attempt looks like this: // another dangerous (and incorrect) way to avoid

// returning an object

const Rational& operator*(const Rational& lhs,

const Rational& rhs)

Page 290: Effective C++ and More Effective C++

{

Rational result(lhs.numerator() * rhs.numerator(),

lhs.denominator() * rhs.denominator());

return result;

}

This function returns a reference to an object that no longer exists. In particular, it returns a reference to thelocal object result, but result is automatically destroyed when operator* is exited. Returning a reference to anobject that's been destroyed is hardly useful.

Trust me on this: some functions (operator* among them) just have to return objects. That's the way it is. Don'tfight it. You can't win.

That is, you can't win in your effort to eliminate by-value returns from functions that require them. But that's thewrong war to wage. From an efficiency point of view, you shouldn't care that a function returns an object, youshould only care about the cost of that object. What you need to do is channel your efforts into finding a way toreduce the cost of returned objects, not to eliminate the objects themselves (which we now recognize is a futilequest). If no cost is associated with such objects, who cares how many get created?

It is frequently possible to write functions that return objects in such a way that compilers can eliminate the costof the temporaries. The trick is to return constructor arguments instead of objects, and you can do it like this:

// an efficient and correct way to implement a

// function that returns an object

const Rational operator*(const Rational& lhs,

const Rational& rhs)

{

return Rational(lhs.numerator() * rhs.numerator(),

lhs.denominator() * rhs.denominator());

}

Look closely at the expression being returned. It looks like you're calling a Rational constructor, and in fact youare. You're creating a temporary Rational object through this expression,

Rational(lhs.numerator() * rhs.numerator(),

lhs.denominator() * rhs.denominator());

and it is this temporary object the function is copying for its return value.

This business of returning constructor arguments instead of local objects doesn't appear to have bought you a lot,because you still have to pay for the construction and destruction of the temporary created inside the function,and you still have to pay for the construction and destruction of the object the function returns. But you havegained something. The rules for C++ allow compilers to optimize temporary objects out of existence. As aresult, if you call operator* in a context like this,

Rational a = 10;

Rational b(1, 2);

Rational c = a * b; // operator* is called here

your compilers are allowed to eliminate both the temporary inside operator* and the temporary returned byoperator*. They can construct the object defined by the return expression inside the memory allotted for theobject c. If your compilers do this, the total cost of temporary objects as a result of your calling operator* iszero: no temporaries are created. Instead, you pay for only one constructor call ? the one to create c.Furthermore, you can't do any better than this, because c is a named object, and named objects can't beeliminated (see also Item 22).7 You can, however, eliminate the overhead of the call to operator* by declaringthat function inline (but first see Item E33):

// the most efficient way to write a function returning

// an object

inline const Rational operator*(const Rational& lhs,

Page 291: Effective C++ and More Effective C++

const Rational& rhs)

{

return Rational(lhs.numerator() * rhs.numerator(),

lhs.denominator() * rhs.denominator());

}

"Yeah, yeah," you mutter, "optimization, schmoptimization. Who cares what compilers can do? I want to knowwhat they do do. Does any of this nonsense work with real compilers?" It does. This particular optimization ?eliminating a local temporary by using a function's return location (and possibly replacing that with an object atthe function's call site) ? is both well-known and commonly implemented. It even has a name: the return valueoptimization. In fact, the existence of a name for this optimization may explain why it's so widely available.Programmers looking for a C++ compiler can ask vendors whether the return value optimization is implemented.If one vendor says yes and another says "The what?," the first vendor has a notable competitive advantage. Ah,capitalism. Sometimes you just gotta love it.

Back to Item 19: Understand the origin of temporary objects Continue to Item 21: Overload to avoid implicit type conversions

7 In July 1996, the °ISO/ANSI standardization committee declared that both named and unnamed objects may beoptimized away via the return value optimization, so both versions of operator* above may now yield the same(optimized) object code. Return

Page 292: Effective C++ and More Effective C++

Back to Item 20: Facilitate the return value optimization Continue to Item 22: Consider using op= instead of stand-alone op

Item 21: Overload to avoid implicit type conversions.

Here's some code that looks nothing if not eminently reasonable: class UPInt { // class for unlimited

public: // precision integers

UPInt();

UPInt(int value);

...

};

// For an explanation of why the return value is const,

// see Item E21

const UPInt operator+(const UPInt& lhs, const UPInt& rhs);

UPInt upi1, upi2;

...

UPInt upi3 = upi1 + upi2;

There are no surprises here. upi1 and upi2 are both UPInt objects, so adding them together just calls operator+for UPInts.

Now consider these statements: upi3 = upi1 + 10;

upi3 = 10 + upi2;

These statements also succeed. They do so through the creation of temporary objects to convert the integer 10into UPInts (see Item 19).

It is convenient to have compilers perform these kinds of conversions, but the temporary objects created to makethe conversions work are a cost we may not wish to bear. Just as most people want government benefits withouthaving to pay for them, most C++ programmers want implicit type conversions without incurring any cost fortemporaries. But without the computational equivalent of deficit spending, how can we do it?

We can take a step back and recognize that our goal isn't really type conversion, it's being able to make calls tooperator+ with a combination of UPInt and int arguments. Implicit type conversion happens to be a means to thatend, but let us not confuse means and ends. There is another way to make mixed-type calls to operator+ succeed,and that's to eliminate the need for type conversions in the first place. If we want to be able to add UPInt and intobjects, all we have to do is say so. We do it by declaring several functions, each with a different set ofparameter types:

const UPInt operator+(const UPInt& lhs, // add UPInt

const UPInt& rhs); // and UPInt

const UPInt operator+(const UPInt& lhs, // add UPInt

int rhs); // and int

const UPInt operator+(int lhs, // add int and

const UPInt& rhs); // UPInt

UPInt upi1, upi2;

...

UPInt upi3 = upi1 + upi2; // fine, no temporary for

// upi1 or upi2

Page 293: Effective C++ and More Effective C++

upi3 = upi1 + 10; // fine, no temporary for

// upi1 or 10

upi3 = 10 + upi2; // fine, no temporary for

// 10 or upi2

Once you start overloading to eliminate type conversions, you run the risk of getting swept up in the passion ofthe moment and declaring functions like this:

const UPInt operator+(int lhs, int rhs); // error!

The thinking here is reasonable enough. For the types UPInt and int, we want to overload on all possiblecombinations for operator+. Given the three overloadings above, the only one missing is operator+ taking twoint arguments, so we want to add it.

Reasonable or not, there are rules to this C++ game, and one of them is that every overloaded operator must takeat least one argument of a user-defined type. int isn't a user-defined type, so we can't overload an operator takingonly arguments of that type. (If this rule didn't exist, programmers would be able to change the meaning ofpredefined operations, and that would surely lead to chaos. For example, the attempted overloading of operator+above would change the meaning of addition on ints. Is that really something we want people to be able to do?)

Overloading to avoid temporaries isn't limited to operator functions. For example, in most programs, you'll wantto allow a string object everywhere a char* is acceptable, and vice versa. Similarly, if you're using a numericalclass like complex (see Item 35), you'll want types like int and double to be valid anywhere a numerical objectis. As a result, any function taking arguments of type string, char*, complex, etc., is a reasonable candidate foroverloading to eliminate type conversions.

Still, it's important to keep the 80-20 rule (see Item 16) in mind. There is no point in implementing a slew ofoverloaded functions unless you have good reason to believe that it will make a noticeable improvement in theoverall efficiency of the programs that use them.

Back to Item 20: Facilitate the return value optimization Continue to Item 22: Consider using op= instead of stand-alone op

Page 294: Effective C++ and More Effective C++

Back to Item 21: Overload to avoid implicit type conversions Continue to Item 23: Consider alternative libraries

Item 22: Consider using op= instead of stand-alone op.

Most programmers expect that if they can say things like these, x = x + y; x = x - y;

they can also say things like these: x += y; x -= y;

If x and y are of a user-defined type, there is no guarantee that this is so. As far as C++ is concerned, there is norelationship between operator+, operator=, and operator+=, so if you want all three operators to exist and tohave the expected relationship, you must implement that yourself. Ditto for the operators -, *, /, etc.

A good way to ensure that the natural relationship between the assignment version of an operator (e.g.,operator+=) and the stand-alone version (e.g., operator+) exists is to implement the latter in terms of the former(see also Item 6). This is easy to do:

class Rational {

public:

...

Rational& operator+=(const Rational& rhs);

Rational& operator-=(const Rational& rhs);

};

// operator+ implemented in terms of operator+=; see

// Item E21 for an explanation of why the return value is

// const and page 109 for a warning about implementation

const Rational operator+(const Rational& lhs,

const Rational& rhs)

{

return Rational(lhs) += rhs;

}

// operator- implemented in terms of operator -=

const Rational operator-(const Rational& lhs,

const Rational& rhs)

{

return Rational(lhs) -= rhs;

}

In this example, operators += and -= are implemented (elsewhere) from scratch, and operator+ and operator-call them to provide their own functionality. With this design, only the assignment versions of these operatorsneed to be maintained. Furthermore, assuming the assignment versions of the operators are in the class's publicinterface, there is never a need for the stand-alone operators to be friends of the class (see Item E19).

If you don't mind putting all stand-alone operators at global scope, you can use templates to eliminate the need towrite the stand-alone functions:

template<class T>

const T operator+(const T& lhs, const T& rhs)

{

return T(lhs) += rhs; // see discussion below

}

template<class T>

const T operator-(const T& lhs, const T& rhs)

{

return T(lhs) -= rhs; // see discussion below

}

Page 295: Effective C++ and More Effective C++

...

With these templates, as long as an assignment version of an operator is defined for some type T, thecorresponding stand-alone operator will automatically be generated if it's needed.

All this is well and good, but so far we have failed to consider the issue of efficiency, and efficiency is, afterall, the topic of this chapter. Three aspects of efficiency are worth noting here. The first is that, in general,assignment versions of operators are more efficient than stand-alone versions, because stand-alone versionsmust typically return a new object, and that costs us the construction and destruction of a temporary (see Items 19 and 20, as well as Item E23). Assignment versions of operators write to their left-hand argument, so there isno need to generate a temporary to hold the operator's return value.

The second point is that by offering assignment versions of operators as well as stand-alone versions, youallow clients of your classes to make the difficult trade-off between efficiency and convenience. That is, yourclients can decide whether to write their code like this,

Rational a, b, c, d, result;

...

result = a + b + c + d; // probably uses 3 temporary

// objects, one for each call

// to operator+

or like this:

result = a; // no temporary needed

result += b; // no temporary needed

result += c; // no temporary needed

result += d; // no temporary needed

The former is easier to write, debug, and maintain, and it offers acceptable performance about 80% of the time(see Item 16). The latter is more efficient, and, one supposes, more intuitive for assembly languageprogrammers. By offering both options, you let clients develop and debug code using the easier-to-readstand-alone operators while still reserving the right to replace them with the more efficient assignment versionsof the operators. Furthermore, by implementing the stand-alones in terms of the assignment versions, you ensurethat when clients switch from one to the other, the semantics of the operations remain constant.

The final efficiency observation concerns implementing the stand-alone operators. Look again at theimplementation for operator+:

template<class T>

const T operator+(const T& lhs, const T& rhs)

{ return T(lhs) += rhs; }

The expression T(lhs) is a call to T's copy constructor. It creates a temporary object whose value is the same asthat of lhs. This temporary is then used to invoke operator+= with rhs, and the result of that operation is returnedfrom operator+.8 This code seems unnecessarily cryptic. Wouldn't it be better to write it like this?

template<class T>

const T operator+(const T& lhs, const T& rhs)

{

T result(lhs); // copy lhs into result

return result += rhs; // add rhs to it and return

}

This template is almost equivalent to the one above, but there is a crucial difference. This second templatecontains a named object, result. The fact that this object is named means that the return value optimization (see Item 20) was, until relatively recently, unavailable for this implementation of operator+ (see the footnote onpage 104). The first implementation has always been eligible for the return value optimization, so the odds maybe better that the compilers you use will generate optimized code for it.

Page 296: Effective C++ and More Effective C++

Now, truth in advertising compels me to point out that the expression return T(lhs) += rhs;

is more complex than most compilers are willing to subject to the return value optimization. The firstimplementation above may thus cost you one temporary object within the function, just as you'd pay for using thenamed object result. However, the fact remains that unnamed objects have historically been easier to eliminatethan named objects, so when faced with a choice between a named object and a temporary object, you may bebetter off using the temporary. It should never cost you more than its named colleague, and, especially with oldercompilers, it may cost you less.

All this talk of named objects, unnamed objects, and compiler optimizations is interesting, but let us not forgetthe big picture. The big picture is that assignment versions of operators (such as operator+=) tend to be moreefficient than stand-alone versions of those operators (e.g. operator+). As a library designer, you should offerboth, and as an application developer, you should consider using assignment versions of operators instead ofstand-alone versions whenever performance is at a premium.

Back to Item 21: Overload to avoid implicit type conversions Continue to Item 23: Consider alternative libraries

8 At least that's what's supposed to happen. Alas, some compilers treat T(lhs) as a cast to remove lhs'sconstness, then add rhs to lhs and return a reference to the modified lhs! Test your compilers before relying onthe behavior described above. Return

Page 297: Effective C++ and More Effective C++

Back to Item 22: Consider using op= instead of stand-alone op Continue to Item 24: Understand the costs of virtual functions, multiple inheritance, virtual base classes, and RTTI

Item 23: Consider alternative libraries.

Library design is an exercise in compromise. The ideal library is small, fast, powerful, flexible, extensible,intuitive, universally available, well supported, free of use restrictions, and bug-free. It is also nonexistent.Libraries optimized for size and speed are typically not portable. Libraries with rich functionality are rarelyintuitive. Bug-free libraries are limited in scope. In the real world, you can't have everything; something alwayshas to give.

Different designers assign different priorities to these criteria. They thus sacrifice different things in theirdesigns. As a result, it is not uncommon for two libraries offering similar functionality to have quite differentperformance profiles.

As an example, consider the iostream and stdio libraries, both of which should be available to every C++programmer. The iostream library has several advantages over its C counterpart (see Item E2). It's type-safe, forexample, and it's extensible. In terms of efficiency, however, the iostream library generally suffers incomparison with stdio, because stdio usually results in executables that are both smaller and faster than thosearising from iostreams.

Consider first the speed issue. One way to get a feel for the difference in performance between iostreams andstdio is to run benchmark applications using both libraries. Now, it's important to bear in mind that benchmarkslie. Not only is it difficult to come up with a set of inputs that correspond to "typical" usage of a program orlibrary, it's also useless unless you have a reliable way of determining how "typical" you or your clients are.Nevertheless, benchmarks can provide some insight into the comparative performance of different approaches toa problem, so though it would be foolish to rely on them completely, it would also be foolish to ignore them.

Let's examine a simple-minded benchmark program that exercises only the most rudimentary I/O functionality.This program reads 30,000 floating point numbers from standard input and writes them to standard output in afixed format. The choice between the iostream and stdio libraries is made during compilation and is determinedby the preprocessor symbol STDIO. If this symbol is defined, the stdio library is used, otherwise the iostreamlibrary is employed.

#ifdef STDIO

#include <stdio.h>

#else

#include <iostream>

#include <iomanip>

using namespace std;

#endif

const int VALUES = 30000; // # of values to read/write

int main()

{

double d;

for (int n = 1; n <= VALUES; ++n) {

#ifdef STDIO

scanf("%lf", &d);

printf("%10.5f", d);

#else

cin >> d;

cout << setw(10) // set field width

<< setprecision(5) // set decimal places

<< setiosflags(ios::showpoint) // keep trailing 0s

<< setiosflags(ios::fixed) // use these settings

<< d;

#endif

if (n % 5 == 0) {

Page 298: Effective C++ and More Effective C++

#ifdef STDIO

printf("\n");

#else

cout << '\n';

#endif

}

}

return 0;

}

When this program is given the natural logarithms of the positive integers as input, it produces output like this: 0.00000 0.69315 1.09861 1.38629 1.60944

1.79176 1.94591 2.07944 2.19722 2.30259

2.39790 2.48491 2.56495 2.63906 2.70805

2.77259 2.83321 2.89037 2.94444 2.99573

3.04452 3.09104 3.13549 3.17805 3.21888

Such output demonstrates, if nothing else, that it's possible to produce fixed-format I/O using iostreams. Ofcourse,

cout << setw(10)

<< setprecision(5)

<< setiosflags(ios::showpoint)

<< setiosflags(ios::fixed)

<< d;

is nowhere near as easy to type as printf("%10.5f", d);

but operator<< is both type-safe and extensible, and printf is neither.

I have run this program on several combinations of machines, operating systems, and compilers, and in everycase the stdio version has been faster. Sometimes it's been only a little faster (about 20%), sometimes it's beensubstantially faster (nearly 200%), but I've never come across an iostream implementation that was as fast as thecorresponding stdio implementation. In addition, the size of this trivial program's executable using stdio tends tobe smaller (sometimes much smaller) than the corresponding program using iostreams. (For programs of arealistic size, this difference is rarely significant.)

Bear in mind that any efficiency advantages of stdio are highly implementation-dependent, so futureimplementations of systems I've tested or existing implementations of systems I haven't tested may show anegligible performance difference between iostreams and stdio. In fact, one can reasonably hope to discover aniostream implementation that's faster than stdio, because iostreams determine the types of their operands duringcompilation, while stdio functions typically parse a format string at runtime.

The contrast in performance between iostreams and stdio is just an example, however, it's not the main point.The main point is that different libraries offering similar functionality often feature different performancetrade-offs, so once you've identified the bottlenecks in your software (via profiling ? see Item 16), you shouldsee if it's possible to remove those bottlenecks by replacing one library with another. If your program has an I/Obottleneck, for example, you might consider replacing iostreams with stdio, but if it spends a significant portionof its time on dynamic memory allocation and deallocation, you might see if there are alternativeimplementations of operator new and operator delete available (see Item 8 and Item E10). Because differentlibraries embody different design decisions regarding efficiency, extensibility, portability, type safety, and otherissues, you can sometimes significantly improve the efficiency of your software by switching to libraries whosedesigners gave more weight to performance considerations than to other factors.

Back to Item 22: Consider using op= instead of stand-alone op Continue to Item 24: Understand the costs of virtual functions, multiple inheritance, virtual base classes, and RTTI

Page 299: Effective C++ and More Effective C++

Back to Item 23: Consider alternative libraries Continue to Techniques

Item 24: Understand the costs of virtual functions, multiple inheritance, virtual base classes, and RTTI.

C++ compilers must find a way to implement each feature in the language. Such implementation details are, ofcourse, compiler-dependent, and different compilers implement language features in different ways. For the mostpart, you need not concern yourself with such matters. However, the implementation of some features can have anoticeable impact on the size of objects and the speed at which member functions execute, so for those features,it's important to have a basic understanding of what compilers are likely to be doing under the hood. Theforemost example of such a feature is virtual functions.

When a virtual function is called, the code executed must correspond to the dynamic type of the object on whichthe function is invoked; the type of the pointer or reference to the object is immaterial. How can compilersprovide this behavior efficiently? Most implementations use virtual tables and virtual table pointers. Virtualtables and virtual table pointers are commonly referred to as vtbls and vptrs, respectively.

A vtbl is usually an array of pointers to functions. (Some compilers use a form of linked list instead of an array,but the fundamental strategy is the same.) Each class in a program that declares or inherits virtual functions hasits own vtbl, and the entries in a class's vtbl are pointers to the implementations of the virtual functions for thatclass. For example, given a class definition like this,

class C1 {

public:

C1();

virtual ~C1();

virtual void f1();

virtual int f2(char c) const;

virtual void f3(const string& s);

void f4() const;

...

};

C1's virtual table array will look something like this:

Note that the nonvirtual function f4 is not in the table, nor is C1's constructor. Nonvirtual functions ? includingconstructors, which are by definition nonvirtual ? are implemented just like ordinary C functions, so there are nospecial performance considerations surrounding their use.

If a class C2 inherits from C1, redefines some of the virtual functions it inherits, and adds some new ones of itsown,

class C2: public C1 {

public:

Page 300: Effective C++ and More Effective C++

C2(); // nonvirtual function

virtual ~C2(); // redefined function

virtual void f1(); // redefined function

virtual void f5(char *str); // new virtual function

...

};

its virtual table entries point to the functions that are appropriate for objects of its type. These entries includepointers to the C1 virtual functions that C2 chose not to redefine:

This discussion brings out the first cost of virtual functions: you have to set aside space for a virtual table foreach class that contains virtual functions. The size of a class's vtbl is proportional to the number of virtualfunctions declared for that class (including those it inherits from its base classes). There should be only onevirtual table per class, so the total amount of space required for virtual tables is not usually significant, but ifyou have a large number of classes or a large number of virtual functions in each class, you may find that thevtbls take a significant bite out of your address space.

Because you need only one copy of a class's vtbl in your programs, compilers must address a tricky problem:where to put it. Most programs and libraries are created by linking together many object files, but each objectfile is generated independently of the others. Which object file should contain the vtbl for any given class? Youmight think to put it in the object file containing main, but libraries have no main, and at any rate the source filecontaining main may make no mention of many of the classes requiring vtbls. How could compilers then knowwhich vtbls they were supposed to create?

A different strategy must be adopted, and compiler vendors tend to fall into two camps. For vendors whoprovide an integrated environment containing both compiler and linker, a brute-force strategy is to generate acopy of the vtbl in each object file that might need it. The linker then strips out duplicate copies, leaving only asingle instance of each vtbl in the final executable or library.

A more common design is to employ a heuristic to determine which object file should contain the vtbl for aclass. Usually this heuristic is as follows: a class's vtbl is generated in the object file containing the definition(i.e., the body) of the first non-inline non-pure virtual function in that class. Thus, the vtbl for class C1 abovewould be placed in the object file containing the definition of C1::~C1 (provided that function wasn't inline),and the vtbl for class C2 would be placed in the object file containing the definition of C2::~C2 (again,provided that function wasn't inline).

In practice, this heuristic works well, but you can get into trouble if you go overboard on declaring virtualfunctions inline (see Item E33). If all virtual functions in a class are declared inline, the heuristic fails, and mostheuristic-based implementations then generate a copy of the class's vtbl in every object file that uses it. In large

Page 301: Effective C++ and More Effective C++

systems, this can lead to programs containing hundreds or thousands of copies of a class's vtbl! Most compilersfollowing this heuristic give you some way to control vtbl generation manually, but a better solution to thisproblem is to avoid declaring virtual functions inline. As we'll see below, there are good reasons why presentcompilers typically ignore the inline directive for virtual functions, anyway.

Virtual tables are half the implementation machinery for virtual functions, but by themselves they are useless.They become useful only when there is some way of indicating which vtbl corresponds to each object, and it isthe job of the virtual table pointer to establish that correspondence.

Each object whose class declares virtual functions carries with it a hidden data member that points to the virtualtable for that class. This hidden data member ? the vptr ? is added by compilers at a location in the objectknown only to the compilers. Conceptually, we can think of the layout of an object that has virtual functions aslooking like this:

This picture shows the vptr at the end of the object, but don't be fooled: different compilers put them in differentplaces. In the presence of inheritance, an object's vptr is often surrounded by data members. Multiple inheritancecomplicates this picture, but we'll deal with that a bit later. At this point, simply note the second cost of virtualfunctions: you have to pay for an extra pointer inside each object that is of a class containing virtual functions.

If your objects are small, this can be a significant cost. If your objects contain, on average, four bytes of memberdata, for example, the addition of a vptr can double their size (assuming four bytes are devoted to the vptr). Onsystems with limited memory, this means the number of objects you can create is reduced. Even on systems withunconstrained memory, you may find that the performance of your software decreases, because larger objectsmean fewer fit on each cache or virtual memory page, and that means your paging activity will probablyincrease.

Suppose we have a program with several objects of types C1 and C2. Given the relationships among objects,vptrs, and vtbls that we have just seen, we can envision the objects in our program like this:

Page 302: Effective C++ and More Effective C++
Page 303: Effective C++ and More Effective C++

Now consider this program fragment: void makeACall(C1 *pC1)

{

pC1->f1();

}

This is a call to the virtual function f1 through the pointer pC1. By looking only at this code, there is no way toknow which f1 function ? C1::f1 or C2::f1 ? should be invoked, because pC1 might point to a C1 object or to aC2 object. Your compilers must nevertheless generate code for the call to f1 inside makeACall, and they mustensure that the correct function is called, no matter what pC1 points to. They do this by generating code to do thefollowing:

1. Follow the object's vptr to its vtbl. This is a simple operation, because the compilers know where to lookinside the object for the vptr. (After all, they put it there.) As a result, this costs only an offset adjustment(to get to the vptr) and a pointer indirection (to get to the vtbl).

2. Find the pointer in the vtbl that corresponds to the function being called (f1 in this example). This, too, issimple, because compilers assign each virtual function a unique index within the table. The cost of thisstep is just an offset into the vtbl array.

3. Invoke the function pointed to by the pointer located in step 2.

If we imagine that each object has a hidden member called vptr and that the vtbl index of function f1 is i, thecode generated for the statement

pC1->f1();

is (*pC1->vptr[i])(pC1); // call the function pointed to by the

// i-th entry in the vtbl pointed to

// by pC1->vptr; pC1 is passed to the

// function as the "this" pointer

This is almost as efficient as a non-virtual function call: on most machines it executes only a few moreinstructions. The cost of calling a virtual function is thus basically the same as that of calling a function through afunction pointer. Virtual functions per se are not usually a performance bottleneck.

The real runtime cost of virtual functions has to do with their interaction with inlining. For all practicalpurposes, virtual functions aren't inlined. That's because "inline" means "during compilation, replace the callsite with the body of the called function," but "virtual" means "wait until runtime to see which function iscalled." If your compilers don't know which function will be called at a particular call site, you can understandwhy they won't inline that function call. This is the third cost of virtual functions: you effectively give up

Page 304: Effective C++ and More Effective C++

inlining. (Virtual functions can be inlined when invoked through objects, but most virtual function calls are madethrough pointers or references to objects, and such calls are not inlined. Because such calls are the norm, virtualfunctions are effectively not inlined.)

Everything we've seen so far applies to both single and multiple inheritance, but when multiple inheritanceenters the picture, things get more complex (see Item E43). There is no point in dwelling on details, but withmultiple inheritance, offset calculations to find vptrs within objects become more complicated; there aremultiple vptrs within a single object (one per base class); and special vtbls must be generated for base classesin addition to the stand-alone vtbls we have discussed. As a result, both the per-class and the per-object spaceoverhead for virtual functions increases, and the runtime invocation cost grows slightly, too.

Multiple inheritance often leads to the need for virtual base classes. Without virtual base classes, if a derivedclass has more than one inheritance path to a base class, the data members of that base class are replicatedwithin each derived class object, one copy for each path between the derived class and the base class. Suchreplication is almost never what programmers want, and making base classes virtual eliminates the replication.Virtual base classes may incur a cost of their own, however, because implementations of virtual base classesoften use pointers to virtual base class parts as the means for avoiding the replication, and one or more of thosepointers may be stored inside your objects.

For example, consider this, which I generally call "the dreaded multiple inheritance diamond:"

Here A is a virtual base class because B and C virtually inherit from it. With some compilers (especially oldercompilers), the layout for an object of type D is likely to look like this:

Page 305: Effective C++ and More Effective C++

It seems a little strange to place the base class data members at the end of the object, but that's often how it'sdone. Of course, implementations are free to organize memory any way they like, so you should never rely onthis picture for anything more than a conceptual overview of how virtual base classes may lead to the addition ofhidden pointers to your objects. Some implementations add fewer pointers, and some find ways to add none atall. (Such implementations make the vptr and vtbl serve double duty).

Page 306: Effective C++ and More Effective C++

If we combine this picture with the earlier one showing how virtual table pointers are added to objects, werealize that if the base class A in the hierarchy on page 119 has any virtual functions, the memory layout for anobject of type D could look like this:

Here I've shaded the parts of the object that are added by compilers. The picture may be misleading, because theratio of shaded to unshaded areas is determined by the amount of data in your classes. For small classes, therelative overhead is large. For classes with more data, the relative overhead is less significant, though it is

Page 307: Effective C++ and More Effective C++

typically noticeable.

An oddity in the above diagram is that there are only three vptrs even though four classes are involved.Implementations are free to generate four vptrs if they like, but three suffice (it turns out that B and D can share avptr), and most implementations take advantage of this opportunity to reduce the compiler-generated overhead.

We've now seen how virtual functions make objects larger and preclude inlining, and we've examined howmultiple inheritance and virtual base classes can also increase the size of objects. Let us therefore turn to ourfinal topic, the cost of runtime type identification (RTTI).

RTTI lets us discover information about objects and classes at runtime, so there has to be a place to store theinformation we're allowed to query. That information is stored in an object of type type_info, and you can accessthe type_info object for a class by using the typeid operator.

There only needs to be a single copy of the RTTI information for each class, but there must be a way to get tothat information for any object. Actually, that's not quite true. The language specification states that we'reguaranteed accurate information on an object's dynamic type only if that type has at least one virtual function.This makes RTTI data sound a lot like a virtual function table. We need only one copy of the information perclass, and we need a way to get to the appropriate information from any object containing a virtual function. Thisparallel between RTTI and virtual function tables is no accident: RTTI was designed to be implementable interms of a class's vtbl.

For example, index 0 of a vtbl array might contain a pointer to the type_info object for the class correspondingto that vtbl. The vtbl for class C1 on page 114 would then look like this:

With this implementation, the space cost of RTTI is an additional entry in each class vtbl plus the cost of thestorage for the type_info object for each class. Just as the memory for virtual tables is unlikely to be noticeablefor most applications, however, you're unlikely to run into problems due to the size of type_info objects.

The following table summarizes the primary costs of virtual functions, multiple inheritance, virtual base classes,and RTTI:

FeatureIncreases

Size of ObjectsIncreases

Per-Class DataReducesInlining

Virtual Functions Yes Yes YesMultiple Inheritance Yes Yes NoVirtual Base Classes Often Sometimes NoRTTI No Yes No

Some people look at this table and are aghast. "I'm sticking with C!", they declare. Fair enough. But rememberthat each of these features offers functionality you'd otherwise have to code by hand. In most cases, your manual

Page 308: Effective C++ and More Effective C++

approximation would probably be less efficient and less robust than the compiler-generated code. Using nestedswitch statements or cascading if-then-elses to emulate virtual function calls, for example, yields more code thanvirtual function calls do, and the code runs more slowly, too. Furthermore, you must manually track object typesyourself, which means your objects carry around type tags of their own; you thus often fail to gain even thebenefit of smaller objects.

It is important to understand the costs of virtual functions, multiple inheritance, virtual base classes, and RTTI,but it is equally important to understand that if you need the functionality these features offer, you will pay for it,one way or another. Sometimes you have legitimate reasons for bypassing the compiler-generated services. Forexample, hidden vptrs and pointers to virtual base classes can make it difficult to store C++ objects in databasesor to move them across process boundaries, so you may wish to emulate these features in a way that makes iteasier to accomplish these other tasks. From the point of view of efficiency, however, you are unlikely to dobetter than the compiler-generated implementations by coding these features yourself.

Back to Item 23: Consider alternative libraries Continue to Techniques

Page 309: Effective C++ and More Effective C++

Back to Item 24: Understand the costs of virtual functions, multiple inheritance, virtual base classes, and RTTI Continue to Item 25: Virtualizing constructors and non-member functions

Techniques

Most of this book is concerned with programming guidelines. Such guidelines are important, but no programmerlives by guidelines alone. According to the old TV show Felix the Cat, "Whenever he gets in a fix, he reachesinto his bag of tricks." Well, if a cartoon character can have a bag of tricks, so too can C++ programmers. Thinkof this chapter as a starter set for your bag of tricks.

Some problems crop up repeatedly when designing C++ software. How can you make constructors andnon-member functions act like virtual functions? How can you limit the number of instances of a class? How canyou prevent objects from being created on the heap? How can you guarantee that they will be created there?How can you create objects that automatically perform some actions anytime some other class's memberfunctions are called? How can you have different objects share data structures while giving clients the illusionthat each has its own copy? How can you distinguish between read and write usage of operator[]? How can youcreate a virtual function whose behavior depends on the dynamic types of more than one object?

All these questions (and more) are answered in this chapter, in which I describe proven solutions to problemscommonly encountered by C++ programmers. I call such solutions techniques, but they're also known as idiomsand, when documented in a stylized fashion, patterns. Regardless of what you call them, the information thatfollows will serve you well as you engage in the day-to-day skirmishes of practical software development. Itshould also convince you that no matter what you want to do, there is almost certainly a way to do it in C++.

Back to Item 24: Understand the costs of virtual functions, multiple inheritance, virtual base classes, and RTTI Continue to Item 25: Virtualizing constructors and non-member functions

Page 310: Effective C++ and More Effective C++

Back to Techniques Continue to Item 26: Limiting the number of objects of a class

Item 25: Virtualizing constructors and non-member functions.

On the face of it, it doesn't make much sense to talk about "virtual constructors." You call a virtual function toachieve type-specific behavior when you have a pointer or reference to an object but you don't know what thereal type of the object is. You call a constructor only when you don't yet have an object but you know exactlywhat type you'd like to have. How, then, can one talk of virtual constructors?

It's easy. Though virtual constructors may seem nonsensical, they are remarkably useful. (If you think nonsensicalideas are never useful, how do you explain the success of modern physics?) For example, suppose you writeapplications for working with newsletters, where a newsletter consists of components that are either textual orgraphical. You might organize things this way:

class NLComponent { // abstract base class for

public: // newsletter components

... // contains at least one

}; // pure virtual function

class TextBlock: public NLComponent {

public:

... // contains no pure virtual

}; // functions

class Graphic: public NLComponent {

public:

... // contains no pure virtual

}; // functions

class NewsLetter { // a newsletter object

public: // consists of a list of

... // NLComponent objects

private:

list<NLComponent*> components;

};

The classes relate in this way:

Page 311: Effective C++ and More Effective C++

The list class used inside NewsLetter is part of the Standard Template Library, which is part of the standardC++ library (see Item E49 and Item 35). Objects of type list behave like doubly linked lists, though they need notbe implemented in that way.

NewsLetter objects, when not being worked on, would likely be stored on disk. To support the creation of aNewsletter from its on-disk representation, it would be convenient to give NewsLetter a constructor that takes anistream. The constructor would read information from the stream as it created the necessary in-core datastructures:

class NewsLetter {

public:

NewsLetter(istream& str);

...

};

Pseudocode for this constructor might look like this, NewsLetter::NewsLetter(istream& str)

{

while (str) {

read the next component object from str;

add the object to the list of this

newsletter's components;

}

}

Page 312: Effective C++ and More Effective C++

or, after moving the tricky stuff into a separate function called readComponent, like this: class NewsLetter {

public:

...

private:

// read the data for the next NLComponent from str,

// create the component and return a pointer to it

static NLComponent * readComponent(istream& str);

...

};

NewsLetter::NewsLetter(istream& str)

{

while (str) {

// add the pointer returned by readComponent to the

// end of the components list; "push_back" is a list

// member function that inserts at the end of the list

components.push_back(readComponent(str));

}

}

Consider what readComponent does. It creates a new object, either a TextBlock or a Graphic, depending on thedata it reads. Because it creates new objects, it acts much like a constructor, but because it can create differenttypes of objects, we call it a virtual constructor. A virtual constructor is a function that creates different typesof objects depending on the input it is given. Virtual constructors are useful in many contexts, only one of whichis reading object information from disk (or off a network connection or from a tape, etc.).

A particular kind of virtual constructor ? the virtual copy constructor ? is also widely useful. A virtual copyconstructor returns a pointer to a new copy of the object invoking the function. Because of this behavior, virtualcopy constructors are typically given names like copySelf, cloneSelf, or, as shown below, just plain clone. Fewfunctions are implemented in a more straightforward manner:

class NLComponent {

public:

// declaration of virtual copy constructor

virtual NLComponent * clone() const = 0;

...

};

class TextBlock: public NLComponent {

public:

virtual TextBlock * clone() const // virtual copy

{ return new TextBlock(*this); } // constructor

...

};

class Graphic: public NLComponent {

public:

virtual Graphic * clone() const // virtual copy

{ return new Graphic(*this); } // constructor

...

};

As you can see, a class's virtual copy constructor just calls its real copy constructor. The meaning of "copy" ishence the same for both functions. If the real copy constructor performs a shallow copy, so does the virtual copyconstructor. If the real copy constructor performs a deep copy, so does the virtual copy constructor. If the realcopy constructor does something fancy like reference counting or copy-on-write (see Item 29), so does the

Page 313: Effective C++ and More Effective C++

virtual copy constructor. Consistency ? what a wonderful thing.

Notice that the above implementation takes advantage of a relaxation in the rules for virtual function return typesthat was adopted relatively recently. No longer must a derived class's redefinition of a base class's virtualfunction declare the same return type. Instead, if the function's return type is a pointer (or a reference) to a baseclass, the derived class's function may return a pointer (or reference) to a class derived from that base class.This opens no holes in C++'s type system, and it makes it possible to accurately declare functions such as virtualcopy constructors. That's why TextBlock's clone can return a TextBlock* and Graphic's clone can return aGraphic*, even though the return type of NLComponent's clone is NLComponent*.

The existence of a virtual copy constructor in NLComponent makes it easy to implement a (normal) copyconstructor for NewsLetter:

class NewsLetter {

public:

NewsLetter(const NewsLetter& rhs);

...

private:

list<NLComponent*> components;

};

NewsLetter::NewsLetter(const NewsLetter& rhs)

{

// iterate over rhs's list, using each element's

// virtual copy constructor to copy the element into

// the components list for this object. For details on

// how the following code works, see Item 35.

for (list<NLComponent*>::const_iterator it =

rhs.components.begin();

it != rhs.components.end();

++it) {

// "it" points to the current element of rhs.components,

// so call that element's clone function to get a copy

// of the element, and add that copy to the end of

// this object's list of components

components.push_back((*it)->clone());

}

}

Unless you are familiar with the Standard Template Library, this code looks bizarre, I know, but the idea issimple: just iterate over the list of components for the NewsLetter object being copied, and for each componentin the list, call its virtual copy constructor. We need a virtual copy constructor here, because the list containspointers to NLComponent objects, but we know each pointer really points to a TextBlock or a Graphic. We wantto copy whatever the pointer really points to, and the virtual copy constructor does that for us.

Making Non-Member Functions Act Virtual

Just as constructors can't really be virtual, neither can non-member functions (see Item E19). However, just as itmakes sense to conceive of functions that construct new objects of different types, it makes sense to conceive ofnon-member functions whose behavior depends on the dynamic types of their parameters. For example, supposeyou'd like to implement output operators for the TextBlock and Graphic classes. The obvious approach to thisproblem is to make the output operator virtual. However, the output operator is operator<<, and that functiontakes an ostream& as its left-hand argument; that effectively rules out the possibility of making it a memberfunction of the TextBlock or Graphic classes.

(It can be done, but then look what happens: class NLComponent {

public:

// unconventional declaration of output operator

virtual ostream& operator<<(ostream& str) const = 0;

Page 314: Effective C++ and More Effective C++

...

};

class TextBlock: public NLComponent {

public:

// virtual output operator (also unconventional)

virtual ostream& operator<<(ostream& str) const;

};

class Graphic: public NLComponent {

public:

// virtual output operator (still unconventional)

virtual ostream& operator<<(ostream& str) const;

};

TextBlock t;

Graphic g;

...

t << cout; // print t on cout via

// virtual operator<<; note

// unconventional syntax

g << cout; // print g on cout via

// virtual operator<<; note

// unconventional syntax

Clients must place the stream object on the right-hand side of the "<<" symbol, and that's contrary to theconvention for output operators. To get back to the normal syntax, we must move operator<< out of theTextBlock and Graphic classes, but if we do that, we can no longer declare it virtual.)

An alternate approach is to declare a virtual function for printing (e.g., print) and define it for the TextBlock andGraphic classes. But if we do that, the syntax for printing TextBlock and Graphic objects is inconsistent withthat for the other types in the language, all of which rely on operator<< as their output operator.

Neither of these solutions is very satisfying. What we want is a non-member function called operator<< thatexhibits the behavior of a virtual function like print. This description of what we want is in fact very close to adescription of how to get it. We define both operator<< and print and have the former call the latter!

class NLComponent {

public:

virtual ostream& print(ostream& s) const = 0;

...

};

class TextBlock: public NLComponent {

public:

virtual ostream& print(ostream& s) const;

...

};

class Graphic: public NLComponent {

public:

virtual ostream& print(ostream& s) const;

...

};

inline

ostream& operator<<(ostream& s, const NLComponent& c)

{

Page 315: Effective C++ and More Effective C++

return c.print(s);

}

Virtual-acting non-member functions, then, are easy. You write virtual functions to do the work, then write anon-virtual function that does nothing but call the virtual function. To avoid incurring the cost of a function callfor this syntactic sleight-of-hand, of course, you inline the non-virtual function (see Item E33).

Now that you know how to make non-member functions act virtually on one of their arguments, you may wonderif it's possible to make them act virtually on more than one of their arguments. It is, but it's not easy. How hard isit? Turn to Item 31; it's devoted to that question.

Back to Techniques Continue to Item 26: Limiting the number of objects of a class

Page 316: Effective C++ and More Effective C++

Back to Item 25: Virtualizing constructors and non-member functions Continue to Item 27: Requiring or prohibiting heap-based objects

Item 26: Limiting the number of objects of a class.

Okay, you're crazy about objects, but sometimes you'd like to bound your insanity. For example, you've got onlyone printer in your system, so you'd like to somehow limit the number of printer objects to one. Or you've gotonly 16 file descriptors you can hand out, so you've got to make sure there are never more than that many filedescriptor objects in existence. How can you do such things? How can you limit the number of objects?

If this were a proof by mathematical induction, we might start with n = 1, then build from there. Fortunately, thisis neither a proof nor an induction. Moreover, it turns out to be instructive to begin with n = 0, so we'll startthere instead. How do you prevent objects from being instantiated at all?

Allowing Zero or One Objects

Each time an object is instantiated, we know one thing for sure: a constructor will be called. That being thecase, the easiest way to prevent objects of a particular class from being created is to declare the constructors ofthat class private:

class CantBeInstantiated {

private:

CantBeInstantiated();

CantBeInstantiated(const CantBeInstantiated&);

...

};

Having thus removed everybody's right to create objects, we can selectively loosen the restriction. If, forexample, we want to create a class for printers, but we also want to abide by the constraint that there is only oneprinter available to us, we can encapsulate the printer object inside a function so that everybody has access tothe printer, but only a single printer object is created:

class PrintJob; // forward declaration

// see Item E34

class Printer {

public:

void submitJob(const PrintJob& job);

void reset();

void performSelfTest();

...

friend Printer& thePrinter();

private:

Printer();

Printer(const Printer& rhs);

...

};

Printer& thePrinter()

{

static Printer p; // the single printer object

return p;

}

There are three separate components to this design. First, the constructors of the Printer class are private. That

Page 317: Effective C++ and More Effective C++

suppresses object creation. Second, the global function thePrinter is declared a friend of the class. That letsthePrinter escape the restriction imposed by the private constructors. Finally, thePrinter contains a static Printerobject. That means only a single object will be created.

Client code refers to thePrinter whenever it wishes to interact with the system's lone printer. By returning areference to a Printer object, thePrinter can be used in any context where a Printer object itself could be:

class PrintJob {

public:

PrintJob(const string& whatToPrint);

...

};

string buffer;

... // put stuff in buffer

thePrinter().reset();

thePrinter().submitJob(buffer);

It's possible, of course, that thePrinter strikes you as a needless addition to the global namespace. "Yes," youmay say, "as a global function it looks more like a global variable, but global variables are gauche, and I'dprefer to localize all printer-related functionality inside the Printer class." Well, far be it from me to argue withsomeone who uses words like gauche. thePrinter can just as easily be made a static member function of Printer,and that puts it right where you want it. It also eliminates the need for a friend declaration, which many regard astacky in its own right. Using a static member function, Printer looks like this:

class Printer {

public:

static Printer& thePrinter();

...

private:

Printer();

Printer(const Printer& rhs);

...

};

Printer& Printer::thePrinter()

{

static Printer p;

return p;

}

Clients must now be a bit wordier when they refer to the printer: Printer::thePrinter().reset();

Printer::thePrinter().submitJob(buffer);

Another approach is to move Printer and thePrinter out of the global scope and into a namespace (see Item E28). Namespaces are a recent addition to C++. Anything that can be declared at global scope can also be declaredin a namespace. This includes classes, structs, functions, variables, objects, typedefs, etc. The fact thatsomething is in a namespace doesn't affect its behavior, but it does prevent name conflicts between entities indifferent namespaces. By putting the Printer class and the thePrinter function into a namespace, we don't have toworry about whether anybody else happened to choose the names Printer or thePrinter for themselves; ournamespace prevents name conflicts.

Syntactically, namespaces look much like classes, but there are no public, protected, or private sections;everything is public. This is how we'd put Printer and thePrinter into a namespace called PrintingStuff:

namespace PrintingStuff {

Page 318: Effective C++ and More Effective C++

class Printer { // this class is in the

public: // PrintingStuff namespace

void submitJob(const PrintJob& job);

void reset();

void performSelfTest();

...

friend Printer& thePrinter();

private:

Printer();

Printer(const Printer& rhs);

...

};

Printer& thePrinter() // so is this function

{

static Printer p;

return p;

}

} // this is the end of the

// namespace

Given this namespace, clients can refer to thePrinter using a fully-qualified name (i.e., one that includes thename of the namespace),

PrintingStuff::thePrinter().reset();

PrintingStuff::thePrinter().submitJob(buffer);

but they can also employ a using declaration to save themselves keystrokes: using PrintingStuff::thePrinter; // import the name

// "thePrinter" from the

// namespace "PrintingStuff"

// into the current scope

thePrinter().reset(); // now thePrinter can be

thePrinter().submitJob(buffer); // used as if it were a

// local name

There are two subtleties in the implementation of thePrinter that are worth exploring. First, it's important that thesingle Printer object be static in a function and not in a class. An object that's static in a class is, for all intentsand purposes, always constructed (and destructed), even if it's never used. In contrast, an object that's static in afunction is created the first time through the function, so if the function is never called, the object is nevercreated. (You do, however, pay for a check each time the function is called to see whether the object needs to becreated.) One of the philosophical pillars on which C++ was built is the idea that you shouldn't pay for thingsyou don't use, and defining an object like our printer as a static object in a function is one way of adhering to thisphilosophy. It's a philosophy you should adhere to whenever you can.

There is another drawback to making the printer a class static versus a function static, and that has to do with itstime of initialization. We know exactly when a function static is initialized: the first time through the function atthe point where the static is defined. The situation with a class static (or, for that matter, a global static, shouldyou be so gauche as to use one) is less well defined. C++ offers certain guarantees regarding the order ofinitialization of statics within a particular translation unit (i.e., a body of source code that yields a single objectfile), but it says nothing about the initialization order of static objects in different translation units (see Item E47). In practice, this turns out to be a source of countless headaches. Function statics, when they can be made tosuffice, allow us to avoid these headaches. In our example here, they can, so why suffer?

The second subtlety has to do with the interaction of inlining and static objects inside functions. Look again at

Page 319: Effective C++ and More Effective C++

the code for the non-member version of thePrinter: Printer& thePrinter()

{

static Printer p;

return p;

}

Except for the first time through this function (when p must be constructed), this is a one-line function ? itconsists entirely of the statement "return p;". If ever there were a good candidate for inlining, this function wouldcertainly seem to be the one. Yet it's not declared inline. Why not?

Consider for a moment why you'd declare an object to be static. It's usually because you want only a single copyof that object, right? Now consider what inline means. Conceptually, it means compilers should replace eachcall to the function with a copy of the function body, but for non-member functions, it also means something else.It means the functions in question have internal linkage.

You don't ordinarily need to worry about such linguistic mumbo jumbo, but there is one thing you mustremember: functions with internal linkage may be duplicated within a program (i.e., the object code for theprogram may contain more than one copy of each function with internal linkage), and this duplication includesstatic objects contained within the functions. The result? If you create an inline non-member functioncontaining a local static object, you may end up with more than one copy of the static object in your program!So don't create inline non-member functions that contain local static data.9

But maybe you think this business of creating a function to return a reference to a hidden object is the wrongway to go about limiting the number of objects in the first place. Perhaps you think it's better to simply count thenumber of objects in existence and throw an exception in a constructor if too many objects are requested. Inother words, maybe you think we should handle printer creation like this:

class Printer {

public:

class TooManyObjects{}; // exception class for use

// when too many objects

// are requested

Printer();

~Printer();

...

private:

static size_t numObjects;

Printer(const Printer& rhs); // there is a limit of 1

// printer, so never allow

}; // copying (see Item E27)

The idea is to use numObjects to keep track of how many Printer objects are in existence. This value will beincremented in the class constructor and decremented in its destructor. If an attempt is made to construct toomany Printer objects, we throw an exception of type TooManyObjects:

// Obligatory definition of the class static

size_t Printer::numObjects = 0;

Printer::Printer()

{

if (numObjects >= 1) {

throw TooManyObjects();

}

proceed with normal construction here;

++numObjects;

}

Page 320: Effective C++ and More Effective C++

Printer::~Printer()

{

perform normal destruction here;

--numObjects;

}

This approach to limiting object creation is attractive for a couple of reasons. For one thing, it's straightforward? everybody should be able to understand what's going on. For another, it's easy to generalize so that themaximum number of objects is some number other than one.

Contexts for Object Construction

There is also a problem with this strategy. Suppose we have a special kind of printer, say, a color printer. Theclass for such printers would have much in common with our generic printer class, so of course we'd inheritfrom it:

class ColorPrinter: public Printer {

...

};

Now suppose we have one generic printer and one color printer in our system: Printer p;

ColorPrinter cp;

How many Printer objects result from these object definitions? The answer is two: one for p and one for thePrinter part of cp. At runtime, a TooManyObjects exception will be thrown during the construction of the baseclass part of cp. For many programmers, this is neither what they want nor what they expect. (Designs that avoidhaving concrete classes inherit from other concrete classes do not suffer from this problem. For details on thisdesign philosophy, see Item 33.)

A similar problem occurs when Printer objects are contained inside other objects:

class CPFMachine { // for machines that can

private: // copy, print, and fax

Printer p; // for printing capabilities

FaxMachine f; // for faxing capabilities

CopyMachine c; // for copying capabilities

...

};

CPFMachine m1; // fine

CPFMachine m2; // throws TooManyObjects exception

The problem is that Printer objects can exist in three different contexts: on their own, as base class parts ofmore derived objects, and embedded inside larger objects. The presence of these different contexts significantlymuddies the waters regarding what it means to keep track of the "number of objects in existence," because whatyou consider to be the existence of an object may not jibe with your compilers'.

Often you will be interested only in allowing objects to exist on their own, and you will wish to limit the numberof those kinds of instantiations. That restriction is easy to satisfy if you adopt the strategy exemplified by ouroriginal Printer class, because the Printer constructors are private, and (in the absence of friend declarations)classes with private constructors can't be used as base classes, nor can they be embedded inside other objects.

Page 321: Effective C++ and More Effective C++

The fact that you can't derive from classes with private constructors leads to a general scheme for preventingderivation, one that doesn't necessarily have to be coupled with limiting object instantiations. Suppose, forexample, you have a class, FSA, for representing finite state automata. (Such state machines are useful in manycontexts, among them user interface design.) Further suppose you'd like to allow any number of FSA objects tobe created, but you'd also like to ensure that no class ever inherits from FSA. (One reason for doing this mightbe to justify the presence of a nonvirtual destructor in FSA. Item E14 explains why base classes generally needvirtual destructors, and Item 24 explains why classes without virtual functions yield smaller objects than doequivalent classes with virtual functions.) Here's how you can design FSA to satisfy both criteria:

class FSA {

public:

// pseudo-constructors

static FSA * makeFSA();

static FSA * makeFSA(const FSA& rhs);

...

private:

FSA();

FSA(const FSA& rhs);

...

};

FSA * FSA::makeFSA()

{ return new FSA(); }

FSA * FSA::makeFSA(const FSA& rhs)

{ return new FSA(rhs); }

Unlike the thePrinter function that always returned a reference to a single object, each makeFSApseudo-constructor returns a pointer to a unique object. That's what allows an unlimited number of FSA objectsto be created.

This is nice, but the fact that each pseudo-constructor calls new implies that callers will have to remember tocall delete. Otherwise a resource leak will be introduced. Callers who wish to have delete called automaticallywhen the current scope is exited can store the pointer returned from makeFSA in an auto_ptr object (see Item 9);such objects automatically delete what they point to when they themselves go out of scope:

// indirectly call default FSA constructor

auto_ptr<FSA> pfsa1(FSA::makeFSA());

// indirectly call FSA copy constructor

auto_ptr<FSA> pfsa2(FSA::makeFSA(*pfsa1));

... // use pfsa1 and pfsa2 as normal pointers,

// but don't worry about deleting them

Allowing Objects to Come and Go

We now know how to design a class that allows only a single instantiation, we know that keeping track of thenumber of objects of a particular class is complicated by the fact that object constructors are called in threedifferent contexts, and we know that we can eliminate the confusion surrounding object counts by makingconstructors private. It is worthwhile to make one final observation. Our use of the thePrinter function toencapsulate access to a single object limits the number of Printer objects to one, but it also limits us to a singlePrinter object for each run of the program. As a result, it's not possible to write code like this:

create Printer object p1;

use p1;

destroy p1;

create Printer object p2;

Page 322: Effective C++ and More Effective C++

use p2;

destroy p2;

...

This design never instantiates more than a single Printer object at a time, but it does use different Printer objectsin different parts of the program. It somehow seems unreasonable that this isn't allowed. After all, at no point dowe violate the constraint that only one printer may exist. Isn't there a way to make this legal?

There is. All we have to do is combine the object-counting code we used earlier with the pseudo-constructorswe just saw:

class Printer {

public:

class TooManyObjects{};

// pseudo-constructor

static Printer * makePrinter();

~Printer();

void submitJob(const PrintJob& job);

void reset();

void performSelfTest();

...

private:

static size_t numObjects;

Printer();

Printer(const Printer& rhs); // we don't define this

}; // function, because we'll

// never allow copying

// (see Item E27)

// Obligatory definition of class static

size_t Printer::numObjects = 0;

Printer::Printer()

{

if (numObjects >= 1) {

throw TooManyObjects();

}

proceed with normal object construction here;

++numObjects;

}

Printer * Printer::makePrinter()

{ return new Printer; }

If the notion of throwing an exception when too many objects are requested strikes you as unreasonably harsh,you could have the pseudo-constructor return a null pointer instead. Clients would then have to check for thisbefore doing anything with it, of course.

Clients use this Printer class just as they would any other class, except they must call the pseudo-constructorfunction instead of the real constructor:

Printer p1; // error! default ctor is

// private

Page 323: Effective C++ and More Effective C++

Printer *p2 =

Printer::makePrinter(); // fine, indirectly calls

// default ctor

Printer p3 = *p2; // error! copy ctor is

// private

p2->performSelfTest(); // all other functions are

p2->reset(); // called as usual

...

delete p2; // avoid resource leak; this

// would be unnecessary if

// p2 were an auto_ptr

This technique is easily generalized to any number of objects. All we have to do is replace the hard-wiredconstant 1 with a class-specific value, then lift the restriction against copying objects. For example, thefollowing revised implementation of our Printer class allows up to 10 Printer objects to exist:

class Printer {

public:

class TooManyObjects{};

// pseudo-constructors

static Printer * makePrinter();

static Printer * makePrinter(const Printer& rhs);

...

private:

static size_t numObjects;

static const size_t maxObjects = 10; // see below

Printer();

Printer(const Printer& rhs);

};

// Obligatory definitions of class statics

size_t Printer::numObjects = 0;

const size_t Printer::maxObjects;

Printer::Printer()

{

if (numObjects >= maxObjects) {

throw TooManyObjects();

}

...

}

Printer::Printer(const Printer& rhs)

{

if (numObjects >= maxObjects) {

throw TooManyObjects();

}

...

}

Printer * Printer::makePrinter()

{ return new Printer; }

Page 324: Effective C++ and More Effective C++

Printer * Printer::makePrinter(const Printer& rhs)

{ return new Printer(rhs); }

Don't be surprised if your compilers get all upset about the declaration of Printer::maxObjects in the classdefinition above. In particular, be prepared for them to complain about the specification of 10 as an initial valuefor that variable. The ability to specify initial values for static const members (of integral type, e.g., ints, chars,enums, etc.) inside a class definition was added to C++ only relatively recently, so some compilers don't yetallow it. If your compilers are as-yet-unupdated, pacify them by declaring maxObjects to be an enumeratorinside a private anonymous enum,

class Printer {

private:

enum { maxObjects = 10 }; // within this class,

... // maxObjects is the

}; // constant 10

or by initializing the constant static like a non-const static member: class Printer {

private:

static const size_t maxObjects; // no initial value given

...

};

// this goes in a single implementation file

const size_t Printer::maxObjects = 10;

This latter approach has the same effect as the original code above, but explicitly specifying the initial value iseasier for other programmers to understand. When your compilers support the specification of initial values forconst static members in class definitions, you should take advantage of that capability.

An Object-Counting Base Class

Initialization of statics aside, the approach above works like the proverbial charm, but there is one aspect of itthat continues to nag. If we had a lot of classes like Printer whose instantiations needed to be limited, we'd haveto write this same code over and over, once per class. That would be mind-numbingly dull. Given a fancy-pantslanguage like C++, it somehow seems we should be able to automate the process. Isn't there a way toencapsulate the notion of counting instances and bundle it into a class?

We can easily come up with a base class for counting object instances and have classes like Printer inherit fromthat, but it turns out we can do even better. We can actually come up with a way to encapsulate the wholecounting kit and kaboodle, by which I mean not only the functions to manipulate the instance count, but also theinstance count itself. (We'll see the need for a similar trick when we examine reference counting in Item 29. Fora detailed examination of this design, see my article on counting objects.)

The counter in the Printer class is the static variable numObjects, so we need to move that variable into aninstance-counting class. However, we also need to make sure that each class for which we're counting instanceshas a separate counter. Use of a counting class template lets us automatically generate the appropriate numberof counters, because we can make the counter a static member of the classes generated from the template:

template<class BeingCounted>

class Counted {

public:

class TooManyObjects{}; // for throwing exceptions

static int objectCount() { return numObjects; }

protected:

Counted();

Counted(const Counted& rhs);

Page 325: Effective C++ and More Effective C++

~Counted() { --numObjects; }

private:

static int numObjects;

static const size_t maxObjects;

void init(); // to avoid ctor code

}; // duplication

template<class BeingCounted>

Counted<BeingCounted>::Counted()

{ init(); }

template<class BeingCounted>

Counted<BeingCounted>::Counted(const Counted<BeingCounted>&)

{ init(); }

template<class BeingCounted>

void Counted<BeingCounted>::init()

{

if (numObjects >= maxObjects) throw TooManyObjects();

++numObjects;

}

The classes generated from this template are designed to be used only as base classes, hence the protectedconstructors and destructor. Note the use of the private member function init to avoid duplicating the statementsin the two Counted constructors.

We can now modify the Printer class to use the Counted template: class Printer: private Counted<Printer> {

public:

// pseudo-constructors

static Printer * makePrinter();

static Printer * makePrinter(const Printer& rhs);

~Printer();

void submitJob(const PrintJob& job);

void reset();

void performSelfTest();

...

using Counted<Printer>::objectCount; // see below

using Counted<Printer>::TooManyObjects; // see below

private:

Printer();

Printer(const Printer& rhs);

};

The fact that Printer uses the Counted template to keep track of how many Printer objects exist is, frankly,nobody's business but the author of Printer's. Such implementation details are best kept private, and that's whyprivate inheritance is used here (see Item E42). The alternative would be to use public inheritance betweenPrinter and Counted<Printer>, but then we'd be obliged to give the Counted classes a virtual destructor.(Otherwise we'd risk incorrect behavior if somebody deleted a Printer object through a Counted<Printer>*pointer ? see Item E14.) As Item 24 makes clear, the presence of a virtual function in Counted would almostcertainly affect the size and layout of objects of classes inheriting from Counted. We don't want to absorb thatoverhead, and the use of private inheritance lets us avoid it.

Quite properly, most of what Counted does is hidden from Printer's clients, but those clients might reasonably

Page 326: Effective C++ and More Effective C++

want to find out how many Printer objects exist. The Counted template offers the objectCount function to providethis information, but that function becomes private in Printer due to our use of private inheritance. To restore thepublic accessibility of that function, we employ a using declaration:

class Printer: private Counted<Printer> {

public:

...

using Counted<Printer>::objectCount; // make this function

// public for clients

... // of Printer

};

This is perfectly legitimate, but if your compilers don't yet support namespaces, they won't allow it. If theydon't, you can use the older access declaration syntax:

class Printer: private Counted<Printer> {

public:

...

Counted<Printer>::objectCount; // make objectCount

// public in Printer

...

};

This more traditional syntax has the same meaning as the using declaration, but it's deprecated. The classTooManyObjects is handled in the same fashion as objectCount, because clients of Printer must have access toTooManyObjects if they are to be able to catch exceptions of that type.

When Printer inherits from Counted<Printer>, it can forget about counting objects. The class can be written as ifsomebody else were doing the counting for it, because somebody else (Counted<Printer>) is. A Printerconstructor now looks like this:

Printer::Printer()

{

proceed with normal object construction;

}

What's interesting here is not what you see, it's what you don't. No checking of the number of objects to see ifthe limit is about to be exceeded, no incrementing the number of objects in existence once the constructor isdone. All that is now handled by the Counted<Printer> constructors, and because Counted<Printer> is a baseclass of Printer, we know that a Counted<Printer> constructor will always be called before a Printerconstructor. If too many objects are created, a Counted<Printer> constructor throws an exception, and the Printerconstructor won't even be invoked. Nifty, huh?

Nifty or not, there's one loose end that demands to be tied, and that's the mandatory definitions of the staticsinside Counted. It's easy enough to take care of numObjects ? we just put this in Counted's implementation file:

template<class BeingCounted> // defines numObjects

int Counted<BeingCounted>::numObjects; // and automatically

// initializes it to 0

The situation with maxObjects is a bit trickier. To what value should we initialize this variable? If we want toallow up to 10 printers, we should initialize Counted<Printer>::maxObjects to 10. If, on the other hand, we wantto allow up to 16 file descriptor objects, we should initialize Counted<FileDescriptor>::maxObjects to 16.What to do?

We take the easy way out: we do nothing. We provide no initialization at all for maxObjects. Instead, we requirethat clients of the class provide the appropriate initialization. The author of Printer must add this to animplementation file:

const size_t Counted<Printer>::maxObjects = 10;

Page 327: Effective C++ and More Effective C++

Similarly, the author of FileDescriptor must add this: const size_t Counted<FileDescriptor>::maxObjects = 16;

What will happen if these authors forget to provide a suitable definition for maxObjects? Simple: they'll get anerror during linking, because maxObjects will be undefined. Provided we've adequately documented thisrequirement for clients of Counted, they can then say "Duh" to themselves and go back and add the requisiteinitialization.

Back to Item 25: Virtualizing constructors and non-member functions Continue to Item 27: Requiring or prohibiting heap-based objects

9 In July 1996, the °ISO/ANSI standardization committee changed the default linkage of inline functions toexternal, so the problem I describe here has been eliminated, at least on paper. Your compilers may not yet bein accord with °the standard, however, so your best bet is still to shy away from inline functions with static data. Return

Page 328: Effective C++ and More Effective C++

Back to Item 26: Limiting the number of objects of a class Continue to Item 28: Smart pointers

Item 27: Requiring or prohibiting heap-based objects.

Sometimes you want to arrange things so that objects of a particular type can commit suicide, i.e., can "deletethis." Such an arrangement clearly requires that objects of that type be allocated on the heap. Other times you'llwant to bask in the certainty that there can be no memory leaks for a particular class, because none of the objectscould have been allocated on the heap. This might be the case if you are working on an embedded system, wherememory leaks are especially troublesome and heap space is at a premium. Is it possible to produce code thatrequires or prohibits heap-based objects? Often it is, but it also turns out that the notion of being "on the heap" ismore nebulous than you might think.

Requiring Heap-Based Objects

Let us begin with the prospect of limiting object creation to the heap. To enforce such a restriction, you've got tofind a way to prevent clients from creating objects other than by calling new. This is easy to do. Non-heapobjects are automatically constructed at their point of definition and automatically destructed at the end of theirlifetime, so it suffices to simply make these implicit constructions and destructions illegal.

The straightforward way to make these calls illegal is to declare the constructors and the destructor private. Thisis overkill. There's no reason why they both need to be private. Better to make the destructor private and theconstructors public. Then, in a process that should be familiar from Item 26, you can introduce a privilegedpseudo-destructor function that has access to the real destructor. Clients then call the pseudo-destructor todestroy the objects they've created.

If, for example, we want to ensure that objects representing unlimited precision numbers are created only on theheap, we can do it like this:

class UPNumber {

public:

UPNumber();

UPNumber(int initValue);

UPNumber(double initValue);

UPNumber(const UPNumber& rhs);

// pseudo-destructor (a const member function, because

// even const objects may be destroyed)

void destroy() const { delete this; }

...

private:

~UPNumber();

};

Clients would then program like this: UPNumber n; // error! (legal here, but

// illegal when n's dtor is

// later implicitly invoked)

UPNumber *p = new UPNumber; // fine

...

delete p; // error! attempt to call

// private destructor

p->destroy(); // fine

An alternative is to declare all the constructors private. The drawback to that idea is that a class often has many

Page 329: Effective C++ and More Effective C++

constructors, and the class's author must remember to declare each of them private. This includes the copyconstructor, and it may include a default constructor, too, if these functions would otherwise be generated bycompilers; compiler-generated functions are always public (see Item E45). As a result, it's easier to declareonly the destructor private, because a class can have only one of those.

Restricting access to a class's destructor or its constructors prevents the creation of non-heap objects, but, in astory that is told in Item 26, it also prevents both inheritance and containment:

class UPNumber { ... }; // declares dtor or ctors

// private

class NonNegativeUPNumber:

public UPNumber { ... }; // error! dtor or ctors

// won't compile

class Asset {

private:

UPNumber value;

... // error! dtor or ctors

// won't compile

};

Neither of these difficulties is insurmountable. The inheritance problem can be solved by making UPNumber'sdestructor protected (while keeping its constructors public), and classes that need to contain objects of typeUPNumber can be modified to contain pointers to UPNumber objects instead:

class UPNumber { ... }; // declares dtor protected

class NonNegativeUPNumber:

public UPNumber { ... }; // now okay; derived

// classes have access to

// protected members

class Asset {

public:

Asset(int initValue);

~Asset();

...

private:

UPNumber *value;

};

Asset::Asset(int initValue)

: value(new UPNumber(initValue)) // fine

{ ... }

Asset::~Asset()

{ value->destroy(); } // also fine

Determining Whether an Object is On The Heap

If we adopt this strategy, we must reexamine what it means to be "on the heap." Given the class definitionsketched above, it's legal to define a non-heap NonNegativeUPNumber object:

NonNegativeUPNumber n; // fine

Now, the UPNumber part of the NonNegativeUPNumber object n is not on the heap. Is that okay? The answerdepends on the details of the class's design and implementation, but let us suppose it is not okay, that allUPNumber objects ? even base class parts of more derived objects ? must be on the heap. How can we enforcethis restriction?

There is no easy way. It is not possible for a UPNumber constructor to determine whether it's being invoked as

Page 330: Effective C++ and More Effective C++

the base class part of a heap-based object. That is, there is no way for the UPNumber constructor to detect thatthe following contexts are different:

NonNegativeUPNumber *n1 =

new NonNegativeUPNumber; // on heap

NonNegativeUPNumber n2; // not on heap

But perhaps you don't believe me. Perhaps you think you can play games with the interaction among the newoperator, operator new and the constructor that the new operator calls (see Item 8). Perhaps you think you canoutsmart them all by modifying UPNumber as follows:

class UPNumber {

public:

// exception to throw if a non-heap object is created

class HeapConstraintViolation {};

static void * operator new(size_t size);

UPNumber();

...

private:

static bool onTheHeap; // inside ctors, whether

// the object being

... // constructed is on heap

};

// obligatory definition of class static

bool UPNumber::onTheHeap = false;

void *UPNumber::operator new(size_t size)

{

onTheHeap = true;

return ::operator new(size);

}

UPNumber::UPNumber()

{

if (!onTheHeap) {

throw HeapConstraintViolation();

}

proceed with normal construction here;

onTheHeap = false; // clear flag for next obj.

}

There's nothing deep going on here. The idea is to take advantage of the fact that when an object is allocated onthe heap, operator new is called to allocate the raw memory, then a constructor is called to initialize an object inthat memory. In particular, operator new sets onTheHeap to true, and each constructor checks onTheHeap to seeif the raw memory of the object being constructed was allocated by operator new. If not, an exception of typeHeapConstraintViolation is thrown. Otherwise, construction proceeds as usual, and when construction isfinished, onTheHeap is set to false, thus resetting the default value for the next object to be constructed.

This is a nice enough idea, but it won't work. Consider this potential client code: UPNumber *numberArray = new UPNumber[100];

The first problem is that the memory for the array is allocated by operator new[], not operator new, but(provided your compilers support it) you can write the former function as easily as the latter. What is moretroublesome is the fact that numberArray has 100 elements, so there will be 100 constructor calls. But there is

Page 331: Effective C++ and More Effective C++

only one call to allocate memory, so onTheHeap will be set to true for only the first of those 100 constructors.When the second constructor is called, an exception is thrown, and woe is you.

Even without arrays, this bit-setting business may fail. Consider this statement: UPNumber *pn = new UPNumber(*new UPNumber);

Here we create two UPNumbers on the heap and make pn point to one of them; it's initialized with the value ofthe second one. This code has a resource leak, but let us ignore that in favor of an examination of what happensduring execution of this expression:

new UPNumber(*new UPNumber)

This contains two calls to the new operator, hence two calls to operator new and two calls to UPNumberconstructors (see Item 8). Programmers typically expect these function calls to be executed in this order,

1. Call operator new for first object 2. Call constructor for first object 3. Call operator new for second object 4. Call constructor for second object

but the language makes no guarantee that this is how it will be done. Some compilers generate the function callsin this order instead:

1. Call operator new for first object 2. Call operator new for second object 3. Call constructor for first object 4. Call constructor for second object

There is nothing wrong with compilers that generate this kind of code, but the set-a-bit-in-operator-new trickfails with such compilers. That's because the bit set in steps 1 and 2 is cleared in step 3, thus making the objectconstructed in step 4 think it's not on the heap, even though it is.

These difficulties don't invalidate the basic idea of having each constructor check to see if *this is on the heap.Rather, they indicate that checking a bit set inside operator new (or operator new[]) is not a reliable way todetermine this information. What we need is a better way to figure it out.

If you're desperate enough, you might be tempted to descend into the realm of the unportable. For example, youmight decide to take advantage of the fact that on many systems, a program's address space is organized as alinear sequence of addresses, with the program's stack growing down from the top of the address space and theheap rising up from the bottom:

Page 332: Effective C++ and More Effective C++

On systems that organize a program's memory in this way (many do, but many do not), you might think you coulduse the following function to determine whether a particular address is on the heap:

// incorrect attempt to determine whether an address

// is on the heap

bool onHeap(const void *address)

{

char onTheStack; // local stack variable

return address < &onTheStack;

}

Page 333: Effective C++ and More Effective C++

The thinking behind this function is interesting. Inside onHeap, onTheStack is a local variable. As such, it is,well, it's on the stack. When onHeap is called, its stack frame (i.e., its activation record) will be placed at thetop of the program's stack, and because the stack grows down (toward lower addresses) in this architecture, theaddress of onTheStack must be less than the address of any other stack-based variable or object. If the parameteraddress is less than the location of onTheStack, it can't be on the stack, so it must be on the heap.

Such logic is fine, as far as it goes, but it doesn't go far enough. The fundamental problem is that there are threeplaces where objects may be allocated, not two. Yes, the stack and the heap hold objects, but let us not forgetabout static objects. Static objects are those that are initialized only once during a program run. Static objectscomprise not only those objects explicitly declared static, but also objects at global and namespace scope (see Item E47). Such objects have to go somewhere, and that somewhere is neither the stack nor the heap.

Where they go is system-dependent, but on many of the systems that have the stack and heap grow toward oneanother, they go below the heap. The earlier picture of memory organization, while telling the truth and nothingbut the truth for many systems, failed to tell the whole truth for those systems. With static objects added to thepicture, it looks like this:

Page 334: Effective C++ and More Effective C++

Suddenly it becomes clear why onHeap won't work, not even on systems where it's purported to: it fails todistinguish between heap objects and static objects:

void allocateSomeObjects()

{

char *pc = new char; // heap object: onHeap(pc)

// will return true

char c; // stack object: onHeap(&c)

// will return false

static char sc; // static object: onHeap(&sc)

// will return true

...

}

Now, you may be desperate for a way to tell heap objects from stack objects, and in your desperation you maybe willing to strike a deal with the portability Devil, but are you so desperate that you'll strike a deal that fails toguarantee you the right answers? Surely not, so I know you'll reject this seductive but unreliable

Page 335: Effective C++ and More Effective C++

compare-the-addresses trick.

The sad fact is there's not only no portable way to determine whether an object is on the heap, there isn't even asemi-portable way that works most of the time. If you absolutely, positively have to tell whether an address is onthe heap, you're going to have to turn to unportable, implementation-dependent system calls, and that's that. Assuch, you're better off trying to redesign your software so you don't need to determine whether an object is on theheap in the first place.

If you find yourself obsessing over whether an object is on the heap, the likely cause is that you want to know ifit's safe to invoke delete on it. Often such deletion will take the form of the infamous "delete this." Knowingwhether it's safe to delete a pointer, however, is not the same as simply knowing whether that pointer points tosomething on the heap, because not all pointers to things on the heap can be safely deleted. Consider again anAsset object that contains a UPNumber object:

class Asset {

private:

UPNumber value;

...

};

Asset *pa = new Asset;

Clearly *pa (including its member value) is on the heap. Equally clearly, it's not safe to invoke delete on apointer to pa->value, because no such pointer was ever returned from new.

As luck would have it, it's easier to determine whether it's safe to delete a pointer than to determine whether apointer points to something on the heap, because all we need to answer the former question is a collection ofaddresses that have been returned by operator new. Since we can write operator new ourselves (see Items E8-E10), it's easy to construct such a collection. Here's how we might approach the problem:

void *operator new(size_t size)

{

void *p = getMemory(size); // call some function to

// allocate memory and

// handle out-of-memory

// conditions

add p to the collection of allocated addresses;

return p;

}

void operator delete(void *ptr)

{

releaseMemory(ptr); // return memory to

// free store

remove ptr from the collection of allocated addresses;

}

bool isSafeToDelete(const void *address)

{

return whether address is in collection of

allocated addresses;

}

This is about as simple as it gets. operator new adds entries to a collection of allocated addresses, operatordelete removes entries, and isSafeToDelete does a lookup in the collection to see if a particular address isthere. If the operator new and operator delete functions are at global scope, this should work for all types, eventhe built-ins.

Page 336: Effective C++ and More Effective C++

In practice, three things are likely to dampen our enthusiasm for this design. The first is our extreme reluctanceto define anything at global scope, especially functions with predefined meanings like operator new andoperator delete. Knowing as we do that there is but one global scope and but a single version of operator newand operator delete with the "normal" signatures (i.e., sets of parameter types) within that scope (see Item E9),the last thing we want to do is seize those function signatures for ourselves. Doing so would render our softwareincompatible with any other software that also implements global versions of operator new and operator delete(such as many object-oriented database systems).

Our second consideration is one of efficiency: why burden all heap allocations with the bookkeeping overheadnecessary to keep track of returned addresses if we don't need to?

Our final concern is pedestrian, but important. It turns out to be essentially impossible to implementisSafeToDelete so that it always works. The difficulty has to do with the fact that objects with multiple orvirtual base classes have multiple addresses, so there's no guarantee that the address passed to isSafeToDeleteis the same as the one returned from operator new, even if the object in question was allocated on the heap. Fordetails, see Items 24 and 31.

What we'd like is the functionality provided by these functions without the concomitant pollution of the globalnamespace, the mandatory overhead, and the correctness problems. Fortunately, C++ gives us exactly what weneed in the form of an abstract mixin base class.

An abstract base class is a base class that can't be instantiated, i.e., one with at least one pure virtual function. Amixin ("mix in") class is one that provides a single well-defined capability and is designed to be compatiblewith any other capabilities an inheriting class might provide (see Item E7). Such classes are nearly alwaysabstract. We can therefore come up with an abstract mixin base class that offers derived classes the ability todetermine whether a pointer was allocated from operator new. Here's such a class:

class HeapTracked { // mixin class; keeps track of

public: // ptrs returned from op. new

class MissingAddress{}; // exception class; see below

virtual ~HeapTracked() = 0;

static void *operator new(size_t size);

static void operator delete(void *ptr);

bool isOnHeap() const;

private:

typedef const void* RawAddress;

static list<RawAddress> addresses;

};

This class uses the list data structure that's part of the standard C++ library (see Item E49 and Item 35) to keeptrack of all pointers returned from operator new. That function allocates memory and adds entries to the list;operator delete deallocates memory and removes entries from the list; and isOnHeap returns whether an object'saddress is in the list.

Implementation of the HeapTracked class is simple, because the global operator new and operator deletefunctions are called to perform the real memory allocation and deallocation, and the list class has functions tomake insertion, removal, and lookup single-statement operations. Here's the full implementation ofHeapTracked:

// mandatory definition of static class member

list<RawAddress> HeapTracked::addresses;

// HeapTracked's destructor is pure virtual to make the

// class abstract (see Item E14). The destructor must still

// be defined, however, so we provide this empty definition.

Page 337: Effective C++ and More Effective C++

HeapTracked::~HeapTracked() {}

void * HeapTracked::operator new(size_t size)

{

void *memPtr = ::operator new(size); // get the memory

addresses.push_front(memPtr); // put its address at

// the front of the list

return memPtr;

}

void HeapTracked::operator delete(void *ptr)

{

// get an "iterator" that identifies the list

// entry containing ptr; see Item 35 for details

list<RawAddress>::iterator it =

find(addresses.begin(), addresses.end(), ptr);

if (it != addresses.end()) { // if an entry was found

addresses.erase(it); // remove the entry

::operator delete(ptr); // deallocate the memory

} else { // otherwise

throw MissingAddress(); // ptr wasn't allocated by

} // op. new, so throw an

} // exception

bool HeapTracked::isOnHeap() const

{

// get a pointer to the beginning of the memory

// occupied by *this; see below for details

const void *rawAddress = dynamic_cast<const void*>(this);

// look up the pointer in the list of addresses

// returned by operator new

list<RawAddress>::iterator it =

find(addresses.begin(), addresses.end(), rawAddress);

return it != addresses.end(); // return whether it was

} // found

This code is straightforward, though it may not look that way if you are unfamiliar with the list class and theother components of the Standard Template Library. Item 35 explains everything, but the comments in the codeabove should be sufficient to explain what's happening in this example.

The only other thing that may confound you is this statement (in isOnHeap): const void *rawAddress = dynamic_cast<const void*>(this);

I mentioned earlier that writing the global function isSafeToDelete is complicated by the fact that objects withmultiple or virtual base classes have several addresses. That problem plagues us in isOnHeap, too, but becauseisOnHeap applies only to HeapTracked objects, we can exploit a special feature of the dynamic_cast operator(see Item 2) to eliminate the problem. Simply put, dynamic_casting a pointer to void* (or const void* or volatilevoid* or, for those who can't get enough modifiers in their usual diet, const volatile void*) yields a pointer to thebeginning of the memory for the object pointed to by the pointer. But dynamic_cast is applicable only to pointersto objects that have at least one virtual function. Our ill-fated isSafeToDelete function had to work with any typeof pointer, so dynamic_cast wouldn't help it. isOnHeap is more selective (it tests only pointers to HeapTrackedobjects), so dynamic_casting this to const void* gives us a pointer to the beginning of the memory for the currentobject. That's the pointer that HeapTracked::operator new must have returned if the memory for the currentobject was allocated by HeapTracked::operator new in the first place. Provided your compilers support thedynamic_cast operator, this technique is completely portable.

Page 338: Effective C++ and More Effective C++

Given this class, even BASIC programmers could add to a class the ability to track pointers to heap allocations.All they'd need to do is have the class inherit from HeapTracked. If, for example, we want to be able todetermine whether a pointer to an Asset object points to a heap-based object, we'd modify Asset's classdefinition to specify HeapTracked as a base class:

class Asset: public HeapTracked {

private:

UPNumber value;

...

};

We could then query Asset* pointers as follows: void inventoryAsset(const Asset *ap)

{

if (ap->isOnHeap()) {

ap is a heap-based asset ? inventory it as such;

}

else {

ap is a non-heap-based asset ? record it that way;

}

}

A disadvantage of a mixin class like HeapTracked is that it can't be used with the built-in types, because typeslike int and char can't inherit from anything. Still, the most common reason for wanting to use a class likeHeapTracked is to determine whether it's okay to "delete this," and you'll never want to do that with a built-intype because such types have no this pointer.

Prohibiting Heap-Based Objects

Thus ends our examination of determining whether an object is on the heap. At the opposite end of the spectrumis preventing objects from being allocated on the heap. Here the outlook is a bit brighter. There are, as usual,three cases: objects that are directly instantiated, objects instantiated as base class parts of derived classobjects, and objects embedded inside other objects. We'll consider each in turn.

Preventing clients from directly instantiating objects on the heap is easy, because such objects are alwayscreated by calls to new and you can make it impossible for clients to call new. Now, you can't affect theavailability of the new operator (that's built into the language), but you can take advantage of the fact that thenew operator always calls operator new (see Item 8), and that function is one you can declare yourself. Inparticular, it is one you can declare private. If, for example, you want to keep clients from creating UPNumberobjects on the heap, you could do it this way:

class UPNumber {

private:

static void *operator new(size_t size);

static void operator delete(void *ptr);

...

};

Clients can now do only what they're supposed to be able to do: UPNumber n1; // okay

static UPNumber n2; // also okay

UPNumber *p = new UPNumber; // error! attempt to call

// private operator new

It suffices to declare operator new private, but it looks strange to have operator new be private and operatordelete be public, so unless there's a compelling reason to split up the pair, it's best to declare them in the same

Page 339: Effective C++ and More Effective C++

part of a class. If you'd like to prohibit heap-based arrays of UPNumber objects, too, you could declare operatornew[] and operator delete[] (see Item 8) private as well. (The bond between operator new and operator deleteis stronger than many people think. For information on a rarely-understood aspect of their relationship, turn to the sidebar in my article on counting objects.)

Interestingly, declaring operator new private often also prevents UPNumber objects from being instantiated asbase class parts of heap-based derived class objects. That's because operator new and operator delete areinherited, so if these functions aren't declared public in a derived class, that class inherits the private versionsdeclared in its base(s):

class UPNumber { ... }; // as above

class NonNegativeUPNumber: // assume this class

public UPNumber { // declares no operator new

...

};

NonNegativeUPNumber n1; // okay

static NonNegativeUPNumber n2; // also okay

NonNegativeUPNumber *p = // error! attempt to call

new NonNegativeUPNumber; // private operator new

If the derived class declares an operator new of its own, that function will be called when allocating derivedclass objects on the heap, and a different way will have to be found to prevent UPNumber base class parts fromwinding up there. Similarly, the fact that UPNumber's operator new is private has no effect on attempts toallocate objects containing UPNumber objects as members:

class Asset {

public:

Asset(int initValue);

...

private:

UPNumber value;

};

Asset *pa = new Asset(100); // fine, calls

// Asset::operator new or

// ::operator new, not

// UPNumber::operator new

For all practical purposes, this brings us back to where we were when we wanted to throw an exception in theUPNumber constructors if a UPNumber object was being constructed in memory that wasn't on the heap. Thistime, of course, we want to throw an exception if the object in question is on the heap. Just as there is noportable way to determine if an address is on the heap, however, there is no portable way to determine that it isnot on the heap, so we're out of luck. This should be no surprise. After all, if we could tell when an address ison the heap, we could surely tell when an address is not on the heap. But we can't, so we can't. Oh well.

Back to Item 26: Limiting the number of objects of a class Continue to Item 28: Smart pointers

Page 340: Effective C++ and More Effective C++

Back to Item 27: Requiring or prohibiting heap-based objects Continue to Item 29: Reference counting

Item 28: Smart pointers.

Smart pointers are objects that are designed to look, act, and feel like built-in pointers, but to offer greaterfunctionality. They have a variety of applications, including resource management (see Items 9, 10, 25, and 31)and the automation of repetitive coding tasks (see Items 17 and 29).

When you use smart pointers in place of C++'s built-in pointers (i.e., dumb pointers), you gain control over thefollowing aspects of pointer behavior:

Construction and destruction. You determine what happens when a smart pointer is created anddestroyed. It is common to give smart pointers a default value of 0 to avoid the headaches associated withuninitialized pointers. Some smart pointers are made responsible for deleting the object they point to whenthe last smart pointer pointing to the object is destroyed. This can go a long way toward eliminatingresource leaks.

Copying and assignment. You control what happens when a smart pointer is copied or is involved in anassignment. For some smart pointer types, the desired behavior is to automatically copy or make anassignment to what is pointed to, i.e., to perform a deep copy. For others, only the pointer itself should becopied or assigned. For still others, these operations should not be allowed at all. Regardless of whatbehavior you consider "right," the use of smart pointers lets you call the shots.

Dereferencing. What should happen when a client refers to the object pointed to by a smart pointer? Youget to decide. You could, for example, use smart pointers to help implement the lazy fetching strategyoutlined in Item 17.

Smart pointers are generated from templates because, like built-in pointers, they must be strongly typed; thetemplate parameter specifies the type of object pointed to. Most smart pointer templates look something likethis:

template<class T> // template for smart

class SmartPtr { // pointer objects

public:

SmartPtr(T* realPtr = 0); // create a smart ptr to an

// obj given a dumb ptr to

// it; uninitialized ptrs

// default to 0 (null)

SmartPtr(const SmartPtr& rhs); // copy a smart ptr

~SmartPtr(); // destroy a smart ptr

// make an assignment to a smart ptr

SmartPtr& operator=(const SmartPtr& rhs);

T* operator->() const; // dereference a smart ptr

// to get at a member of

// what it points to

T& operator*() const; // dereference a smart ptr

private:

T *pointee; // what the smart ptr

}; // points to

The copy constructor and assignment operator are both shown public here. For smart pointer classes wherecopying and assignment are not allowed, they would typically be declared private (see Item E27). The twodereferencing operators are declared const, because dereferencing a pointer doesn't modify it (though it maylead to modification of what the pointer points to). Finally, each smart pointer-to-T object is implemented bycontaining a dumb pointer-to-T within it. It is this dumb pointer that does the actual pointing.

Page 341: Effective C++ and More Effective C++

Before going into the details of smart pointer implementation, it's worth seeing how clients might use smartpointers. Consider a distributed system in which some objects are local and some are remote. Access to localobjects is generally simpler and faster than access to remote objects, because remote access may require remoteprocedure calls or some other way of communicating with a distant machine.

For clients writing application code, the need to handle local and remote objects differently is a nuisance. It ismore convenient to have all objects appear to be located in the same place. Smart pointers allow a library tooffer this illusion:

template<class T> // template for smart ptrs

class DBPtr { // to objects in a

public: // distributed DB

DBPtr(T *realPtr = 0); // create a smart ptr to a

// DB object given a local

// dumb pointer to it

DBPtr(DataBaseID id); // create a smart ptr to a

// DB object given its

// unique DB identifier

... // other smart ptr

}; // functions as above

class Tuple { // class for database

public: // tuples

...

void displayEditDialog(); // present a graphical

// dialog box allowing a

// user to edit the tuple

bool isValid() const; // return whether *this

}; // passes validity check

// class template for making log entries whenever a T

// object is modified; see below for details

template<class T>

class LogEntry {

public:

LogEntry(const T& objectToBeModified);

~LogEntry();

};

void editTuple(DBPtr<Tuple>& pt)

{

LogEntry<Tuple> entry(*pt); // make log entry for this

// editing operation; see

// below for details

// repeatedly display edit dialog until valid values

// are provided

do {

pt->displayEditDialog();

} while (pt->isValid() == false);

}

The tuple to be edited inside editTuple may be physically located on a remote machine, but the programmerwriting editTuple need not be concerned with such matters; the smart pointer class hides that aspect of thesystem. As far as the programmer is concerned, all tuples are accessed through objects that, except for howthey're declared, act just like run-of-the-mill built-in pointers.

Notice the use of a LogEntry object in editTuple. A more conventional design would have been to surround the

Page 342: Effective C++ and More Effective C++

call to displayEditDialog with calls to begin and end the log entry. In the approach shown here, the LogEntry'sconstructor begins the log entry and its destructor ends the log entry. As Item 9 explains, using an object to beginand end logging is more robust in the face of exceptions than explicitly calling functions, so you should accustomyourself to using classes like LogEntry. Besides, it's easier to create a single LogEntry object than to addseparate calls to start and stop an entry.

As you can see, using a smart pointer isn't much different from using the dumb pointer it replaces. That'stestimony to the effectiveness of encapsulation. Clients of smart pointers are supposed to be able to treat them asdumb pointers. As we shall see, sometimes the substitution is more transparent than others.

Construction, Assignment, and Destruction of Smart Pointers

Construction of a smart pointer is usually straightforward: locate an object to point to (typically by using thesmart pointer's constructor arguments), then make the smart pointer's internal dumb pointer point there. If noobject can be located, set the internal pointer to 0 or signal an error (possibly by throwing an exception).

Implementing a smart pointer's copy constructor, assignment operator(s) and destructor is complicatedsomewhat by the issue of ownership. If a smart pointer owns the object it points to, it is responsible for deletingthat object when it (the smart pointer) is destroyed. This assumes the object pointed to by the smart pointer isdynamically allocated. Such an assumption is common when working with smart pointers. (For ideas on how tomake sure the assumption is true, see Item 27.)

Consider the auto_ptr template from the standard C++ library. As Item 9 explains, an auto_ptr object is a smartpointer that points to a heap-based object until it (the auto_ptr) is destroyed. When that happens, the auto_ptr'sdestructor deletes the pointed-to object. The auto_ptr template might be implemented like this:

template<class T>

class auto_ptr {

public:

auto_ptr(T *ptr = 0): pointee(ptr) {}

~auto_ptr() { delete pointee; }

...

private:

T *pointee;

};

This works fine provided only one auto_ptr owns an object. But what should happen when an auto_ptr is copiedor assigned?

auto_ptr<TreeNode> ptn1(new TreeNode);

auto_ptr<TreeNode> ptn2 = ptn1; // call to copy ctor;

// what should happen?

auto_ptr<TreeNode> ptn3;

ptn3 = ptn2; // call to operator=;

// what should happen?

If we just copied the internal dumb pointer, we'd end up with two auto_ptrs pointing to the same object. Thiswould lead to grief, because each auto_ptr would delete what it pointed to when the auto_ptr was destroyed.That would mean we'd delete an object more than once. The results of such double-deletes are undefined (andare frequently disastrous).

An alternative would be to create a new copy of what was pointed to by calling new. That would guarantee wedidn't have too many auto_ptrs pointing to a single object, but it might engender an unacceptable performance hitfor the creation (and later destruction) of the new object. Furthermore, we wouldn't necessarily know what typeof object to create, because an auto_ptr<T> object need not point to an object of type T; it might point to anobject of a type derived from T. Virtual constructors (see Item 25) can help solve this problem, but it seemsinappropriate to require their use in a general-purpose class like auto_ptr.

Page 343: Effective C++ and More Effective C++

The problems would vanish if auto_ptr prohibited copying and assignment, but a more flexible solution wasadopted for the auto_ptr classes: object ownership is transferred when an auto_ptr is copied or assigned:

template<class T>

class auto_ptr {

public:

...

auto_ptr(auto_ptr<T>& rhs); // copy constructor

auto_ptr<T>& // assignment

operator=(auto_ptr<T>& rhs); // operator

...

};

template<class T>

auto_ptr<T>::auto_ptr(auto_ptr<T>& rhs)

{

pointee = rhs.pointee; // transfer ownership of

// *pointee to *this

rhs.pointee = 0; // rhs no longer owns

} // anything

template<class T>

auto_ptr<T>& auto_ptr<T>::operator=(auto_ptr<T>& rhs)

{

if (this == &rhs) // do nothing if this

return *this; // object is being assigned

// to itself

delete pointee; // delete currently owned

// object

pointee = rhs.pointee; // transfer ownership of

rhs.pointee = 0; // *pointee from rhs to *this

return *this;

}

Notice that the assignment operator must delete the object it owns before assuming ownership of a new object.If it failed to do this, the object would never be deleted. Remember, nobody but the auto_ptr object owns theobject the auto_ptr points to.

Because object ownership is transferred when auto_ptr's copy constructor is called, passing auto_ptrs by valueis often a very bad idea. Here's why:

// this function will often lead to disaster

void printTreeNode(ostream& s, auto_ptr<TreeNode> p)

{ s << *p; }

int main()

{

auto_ptr<TreeNode> ptn(new TreeNode);

...

printTreeNode(cout, ptn); // pass auto_ptr by value

...

}

Page 344: Effective C++ and More Effective C++

When printTreeNode's parameter p is initialized (by calling auto_ptr's copy constructor), ownership of theobject pointed to by ptn is transferred to p. When printTreeNode finishes executing, p goes out of scope and itsdestructor deletes what it points to (which is what ptn used to point to). ptn, however, no longer points toanything (its underlying dumb pointer is null), so just about any attempt to use it after the call to printTreeNodewill yield undefined behavior. Passing auto_ptrs by value, then, is something to be done only if you're sure youwant to transfer ownership of an object to a (transient) function parameter. Only rarely will you want to do this.

This doesn't mean you can't pass auto_ptrs as parameters, it just means that pass-by-value is not the way to do it.Pass-by-reference-to-const is:

// this function behaves much more intuitively

void printTreeNode(ostream& s,

const auto_ptr<TreeNode>& p)

{ s << *p; }

In this function, p is a reference, not an object, so no constructor is called to initialize p. When ptn is passed tothis version of printTreeNode, it retains ownership of the object it points to, and ptn can safely be used after thecall to printTreeNode. Thus, passing auto_ptrs by reference-to-const avoids the hazards arising frompass-by-value. (For other reasons to prefer pass-by-reference to pass-by-value, check out Item E22.)

The notion of transferring ownership from one smart pointer to another during copying and assignment isinteresting, but you may have been at least as interested in the unconventional declarations of the copyconstructor and assignment operator. These functions normally take const parameters, but above they do not. Infact, the code above changes these parameters during the copy or the assignment. In other words, auto_ptrobjects are modified if they are copied or are the source of an assignment!

Yes, that's exactly what's happening. Isn't it nice that C++ is flexible enough to let you do this? If the languagerequired that copy constructors and assignment operators take const parameters, you'd probably have to castaway the parameters' constness (see Item E21) or play other games to implement ownership transferral. Instead,you get to say exactly what you want to say: when an object is copied or is the source of an assignment, thatobject is changed. This may not seem intuitive, but it's simple, direct, and, in this case, accurate.

If you find this examination of auto_ptr member functions interesting, you may wish to see a completeimplementation. You'll find one on pages 291-294, where you'll also see that the auto_ptr template in thestandard C++ library has copy constructors and assignment operators that are more flexible than those describedhere. In the standard auto_ptr template, those functions are member function templates, not just memberfunctions. (Member function templates are described later in this Item. You can also read about them in Item E25.)

A smart pointer's destructor often looks like this: template<class T>

SmartPtr<T>::~SmartPtr()

{

if (*this owns *pointee) {

delete pointee;

}

}

Sometimes there is no need for the test. An auto_ptr always owns what it points to, for example. At other timesthe test is a bit more complicated. A smart pointer that employs reference counting (see Item 29) must adjust areference count before determining whether it has the right to delete what it points to. Of course, some smartpointers are like dumb pointers: they have no effect on the object they point to when they themselves aredestroyed.

Implementing the Dereferencing Operators

Let us now turn our attention to the very heart of smart pointers, the operator* and operator-> functions. Theformer returns the object pointed to. Conceptually, this is simple:

Page 345: Effective C++ and More Effective C++

template<class T>

T& SmartPtr<T>::operator*() const

{

perform "smart pointer" processing;

return *pointee;

}

First the function does whatever processing is needed to initialize or otherwise make pointee valid. Forexample, if lazy fetching is being used (see Item 17), the function may have to conjure up a new object forpointee to point to. Once pointee is valid, the operator* function just returns a reference to the pointed-to object.

Note that the return type is a reference. It would be disastrous to return an object instead, though compilers willlet you do it. Bear in mind that pointee need not point to an object of type T; it may point to an object of a class derived from T. If that is the case and your operator* function returns a T object instead of a reference to theactual derived class object, your function will return an object of the wrong type! (This is the slicing problem.See Item E22 and Item 13.) Virtual functions invoked on the object returned from your star-crossed operator*will not invoke the function corresponding to the dynamic type of the pointed-to object. In essence, your smartpointer will not properly support virtual functions, and how smart is a pointer like that? Besides, returning areference is more efficient anyway, because there is no need to construct a temporary object (see Item 19). Thisis one of those happy occasions when correctness and efficiency go hand in hand.

If you're the kind who likes to worry, you may wonder what you should do if somebody invokes operator* on anull smart pointer, i.e., one whose embedded dumb pointer is null. Relax. You can do anything you want. Theresult of dereferencing a null pointer is undefined, so there is no "wrong" behavior. Wanna throw an exception?Go ahead, throw it. Wanna call abort (possibly by having an assert call fail)? Fine, call it. Wanna walk throughmemory setting every byte to your birth date modulo 256? That's okay, too. It's not nice, but as far as thelanguage is concerned, you are completely unfettered.

The story with operator-> is similar to that for operator*, but before examining operator->, let us remindourselves of the unusual meaning of a call to this function. Consider again the editTuple function that uses asmart pointer-to-Tuple object:

void editTuple(DBPtr<Tuple>& pt)

{

LogEntry<Tuple> entry(*pt);

do {

pt->displayEditDialog();

} while (pt->isValid() == false);

}

The statement pt->displayEditDialog();

is interpreted by compilers as: (pt.operator->())->displayEditDialog();

That means that whatever operator-> returns, it must be legal to apply the member-selection operator (->) to it.There are thus only two things operator-> can return: a dumb pointer to an object or another smart pointerobject. Most of the time, you'll want to return an ordinary dumb pointer. In those cases, you implementoperator-> as follows:

template<class T>

T* SmartPtr<T>::operator->() const

{

perform "smart pointer" processing;

return pointee;

}

Page 346: Effective C++ and More Effective C++

This will work fine. Because this function returns a pointer, virtual function calls via operator-> will behavethe way they're supposed to.

For many applications, this is all you need to know about smart pointers. The reference-counting code of Item 29, for example, draws on no more functionality than we've discussed here. If you want to push your smart pointersfurther, however, you must know more about dumb pointer behavior and how smart pointers can and cannotemulate it. If your motto is "Most people stop at the Z ? but not me!", the material that follows is for you.

Testing Smart Pointers for Nullness

With the functions we have discussed so far, we can create, destroy, copy, assign, and dereference smartpointers. One of the things we cannot do, however, is find out if a smart pointer is null:

SmartPtr<TreeNode> ptn;

...

if (ptn == 0) ... // error!

if (ptn) ... // error!

if (!ptn) ... // error!

This is a serious limitation.

It would be easy to add an isNull member function to our smart pointer classes, but that wouldn't address theproblem that smart pointers don't act like dumb pointers when testing for nullness. A different approach is toprovide an implicit conversion operator that allows the tests above to compile. The conversion traditionallyemployed for this purpose is to void*:

template<class T>

class SmartPtr {

public:

...

operator void*(); // returns 0 if the smart

... // ptr is null, nonzero

}; // otherwise

SmartPtr<TreeNode> ptn;

...

if (ptn == 0) ... // now fine

if (ptn) ... // also fine

if (!ptn) ... // fine

This is similar to a conversion provided by the iostream classes, and it explains why it's possible to write codelike this:

ifstream inputFile("datafile.dat");

if (inputFile) ... // test to see if inputFile

// was successfully

// opened

Like all type conversion functions, this one has the drawback of letting function calls succeed that mostprogrammers would expect to fail (see Item 5). In particular, it allows comparisons of smart pointers ofcompletely different types:

SmartPtr<Apple> pa;

SmartPtr<Orange> po;

Page 347: Effective C++ and More Effective C++

...

if (pa == po) ... // this compiles!

Even if there is no operator== taking a SmartPtr<Apple> and a SmartPtr<Orange>, this compiles, because bothsmart pointers can be implicitly converted into void* pointers, and there is a built-in comparison function forbuilt-in pointers. This kind of behavior makes implicit conversion functions dangerous. (Again, see Item 5, andkeep seeing it over and over until you can see it in the dark.)

There are variations on the conversion-to-void* motif. Some designers advocate conversion to const void*,others embrace conversion to bool. Neither of these variations eliminates the problem of allowing mixed-typecomparisons.

There is a middle ground that allows you to offer a reasonable syntactic form for testing for nullness whileminimizing the chances of accidentally comparing smart pointers of different types. It is to overload operator!for your smart pointer classes so that operator! returns true if and only if the smart pointer on which it's invokedis null:

template<class T>

class SmartPtr {

public:

...

bool operator!() const; // returns true if and only

... // if the smart ptr is null

};

This lets your clients program like this, SmartPtr<TreeNode> ptn;

...

if (!ptn) { // fine

... // ptn is null

}

else {

... // ptn is not null

}

but not like this: if (ptn == 0) ... // still an error

if (ptn) ... // also an error

The only risk for mixed-type comparisons is statements such as these: SmartPtr<Apple> pa;

SmartPtr<Orange> po;

...

if (!pa == !po) ... // alas, this compiles

Fortunately, programmers don't write code like this very often. Interestingly, iostream library implementationsprovide an operator! in addition to the implicit conversion to void*, but these two functions typically test forslightly different stream states. (In the C++ library standard (see Item E49 and Item 35), the implicit conversionto void* has been replaced by an implicit conversion to bool, and operator bool always returns the negation ofoperator!.)

Page 348: Effective C++ and More Effective C++

Converting Smart Pointers to Dumb Pointers

Sometimes you'd like to add smart pointers to an application or library that already uses dumb pointers. Forexample, your distributed database system may not originally have been distributed, so you may have some oldlibrary functions that aren't designed to use smart pointers:

class Tuple { ... }; // as before

void normalize(Tuple *pt); // put *pt into canonical

// form; note use of dumb

// pointer

Consider what will happen if you try to call normalize with a smart pointer-to-Tuple: DBPtr<Tuple> pt;

...

normalize(pt); // error!

The call will fail to compile, because there is no way to convert a DBPtr<Tuple> to a Tuple*. You can make itwork by doing this,

normalize(&*pt); // gross, but legal

but I hope you'll agree this is repugnant.

The call can be made to succeed by adding to the smart pointer-to-T template an implicit conversion operator toa dumb pointer-to-T:

template<class T> // as before

class DBPtr {

public:

...

operator T*() { return pointee; }

...

};

DBPtr<Tuple> pt;

...

normalize(pt); // this now works

Addition of this function also eliminates the problem of testing for nullness: if (pt == 0) ... // fine, converts pt to a

// Tuple*

if (pt) ... // ditto

if (!pt) ... // ditto (reprise)

However, there is a dark side to such conversion functions. (There almost always is. Have you been seeing Item5?) They make it easy for clients to program directly with dumb pointers, thus bypassing the smarts yourpointer-like objects are designed to provide:

void processTuple(DBPtr<Tuple>& pt)

{

Tuple *rawTuplePtr = pt; // converts DBPtr<Tuple> to

// Tuple*

Page 349: Effective C++ and More Effective C++

use rawTuplePtr to modify the tuple;

}

Usually, the "smart" behavior provided by a smart pointer is an essential component of your design, so allowingclients to use dumb pointers typically leads to disaster. For example, if DBPtr implements thereference-counting strategy of Item 29, allowing clients to manipulate dumb pointers directly will almostcertainly lead to bookkeeping errors that corrupt the reference-counting data structures.

Even if you provide an implicit conversion operator to go from a smart pointer to the dumb pointer it's built on,your smart pointer will never be truly interchangeable with the dumb pointer. That's because the conversionfrom a smart pointer to a dumb pointer is a user-defined conversion, and compilers are forbidden from applyingmore than one such conversion at a time. For example, suppose you have a class representing all the clients whohave accessed a particular tuple:

class TupleAccessors {

public:

TupleAccessors(const Tuple *pt); // pt identifies the

... // tuple whose accessors

}; // we care about

As usual, TupleAccessors' single-argument constructor also acts as a type-conversion operator from Tuple* toTupleAccessors (see Item 5). Now consider a function for merging the information in two TupleAccessorsobjects:

TupleAccessors merge(const TupleAccessors& ta1,

const TupleAccessors& ta2);

Because a Tuple* may be implicitly converted to a TupleAccessors, calling merge with two dumb Tuple*pointers is fine:

Tuple *pt1, *pt2;

...

merge(pt1, pt2); // fine, both pointers are converted

// to TupleAccessors objects

The corresponding call with smart DBPtr<Tuple> pointers, however, fails to compile: DBPtr<Tuple> pt1, pt2;

...

merge(pt1, pt2); // error! No way to convert pt1 and

// pt2 to TupleAccessors objects

That's because a conversion from DBPtr<Tuple> to TupleAccessors calls for two user-defined conversions(one from DBPtr<Tuple> to Tuple* and one from Tuple* to TupleAccessors), and such sequences ofconversions are prohibited by the language.

Smart pointer classes that provide an implicit conversion to a dumb pointer open the door to a particularly nastybug. Consider this code:

DBPtr<Tuple> pt = new Tuple;

...

delete pt;

This should not compile. After all, pt is not a pointer, it's an object, and you can't delete an object. Only

Page 350: Effective C++ and More Effective C++

pointers can be deleted, right?

Right. But remember from Item 5 that compilers use implicit type conversions to make function calls succeedwhenever they can, and recall from Item 8 that use of the delete operator leads to calls to a destructor and tooperator delete, both of which are functions. Compilers want these function calls to succeed, so in the deletestatement above, they implicitly convert pt to a Tuple*, then they delete that. This will almost certainly breakyour program.

If pt owns the object it points to, that object is now deleted twice, once at the point where delete is called, asecond time when pt's destructor is invoked. If pt doesn't own the object, somebody else does. That somebodymay be the person who deleted pt, in which case all is well. If, however, the owner of the object pointed to by ptis not the person who deleted pt, we can expect the rightful owner to delete that object again later. The first andlast of these scenarios leads to an object being deleted twice, and deleting an object more than once yieldsundefined behavior.

This bug is especially pernicious because the whole idea behind smart pointers is to make them look and feel asmuch like dumb pointers as possible. The closer you get to this ideal, the more likely your clients are to forgetthey are using smart pointers. If they do, who can blame them if they continue to think that in order to avoidresource leaks, they must call delete if they called new?

The bottom line is simple: don't provide implicit conversion operators to dumb pointers unless there is acompelling reason to do so.

Smart Pointers and Inheritance-Based Type Conversions

Suppose we have a public inheritance hierarchy modeling consumer products for storing music:

class MusicProduct {

public:

MusicProduct(const string& title);

virtual void play() const = 0;

virtual void displayTitle() const = 0;

...

};

class Cassette: public MusicProduct {

public:

Cassette(const string& title);

virtual void play() const;

virtual void displayTitle() const;

...

};

class CD: public MusicProduct {

public:

CD(const string& title);

virtual void play() const;

virtual void displayTitle() const;

...

Page 351: Effective C++ and More Effective C++

};

Further suppose we have a function that, given a MusicProduct object, displays the title of the product and thenplays it:

void displayAndPlay(const MusicProduct* pmp, int numTimes)

{

for (int i = 1; i <= numTimes; ++i) {

pmp->displayTitle();

pmp->play();

}

}

Such a function might be used like this: Cassette *funMusic = new Cassette("Alapalooza");

CD *nightmareMusic = new CD("Disco Hits of the 70s");

displayAndPlay(funMusic, 10);

displayAndPlay(nightmareMusic, 0);

There are no surprises here, but look what happens if we replace the dumb pointers with their allegedly smartcounterparts:

void displayAndPlay(const SmartPtr<MusicProduct>& pmp,

int numTimes);

SmartPtr<Cassette> funMusic(new Cassette("Alapalooza"));

SmartPtr<CD> nightmareMusic(new CD("Disco Hits of the 70s"));

displayAndPlay(funMusic, 10); // error!

displayAndPlay(nightmareMusic, 0); // error!

If smart pointers are so brainy, why won't these compile?

They won't compile because there is no conversion from a SmartPtr<CD> or a SmartPtr<Cassette> to aSmartPtr<MusicProduct>. As far as compilers are concerned, these are three separate classes ? they have norelationship to one another. Why should compilers think otherwise? After all, it's not like SmartPtr<CD> orSmartPtr<Cassette> inherits from SmartPtr<MusicProduct>. With no inheritance relationship between theseclasses, we can hardly expect compilers to run around converting objects of one type to objects of other types.

Fortunately, there is a way to get around this limitation, and the idea (if not the practice) is simple: give eachsmart pointer class an implicit type conversion operator (see Item 5) for each smart pointer class to which itshould be implicitly convertible. For example, in the music hierarchy, you'd add an operatorSmartPtr<MusicProduct> to the smart pointer classes for Cassette and CD:

class SmartPtr<Cassette> {

public:

operator SmartPtr<MusicProduct>()

{ return SmartPtr<MusicProduct>(pointee); }

...

private:

Cassette *pointee;

};

class SmartPtr<CD> {

public:

operator SmartPtr<MusicProduct>()

{ return SmartPtr<MusicProduct>(pointee); }

...

Page 352: Effective C++ and More Effective C++

private:

CD *pointee;

};

The drawbacks to this approach are twofold. First, you must manually specialize the SmartPtr classinstantiations so you can add the necessary implicit type conversion operators, but that pretty much defeats thepurpose of templates. Second, you may have to add many such conversion operators, because your pointed-toobject may be deep in an inheritance hierarchy, and you must provide a conversion operator for each base classfrom which that object directly or indirectly inherits. (If you think you can get around this by providing only animplicit type conversion operator for each direct base class, think again. Because compilers are prohibited fromemploying more than one user-defined type conversion function at a time, they can't convert a smart pointer-to-Tto a smart pointer-to-indirect-base-class-of-T unless they can do it in a single step.)

It would be quite the time-saver if you could somehow get compilers to write all these implicit type conversionfunctions for you. Thanks to a recent language extension, you can. The extension in question is the ability todeclare (nonvirtual) member function templates (usually just called member templates), and you use it togenerate smart pointer conversion functions like this:

template<class T> // template class for smart

class SmartPtr { // pointers-to-T objects

public:

SmartPtr(T* realPtr = 0);

T* operator->() const;

T& operator*() const;

template<class newType> // template function for

operator SmartPtr<newType>() // implicit conversion ops.

{

return SmartPtr<newType>(pointee);

}

...

};

Now hold on to your headlights, this isn't magic ? but it's close. It works as follows. (I'll give a specificexample in a moment, so don't despair if the remainder of this paragraph reads like so much gobbledygook. Afteryou've seen the example, it'll make more sense, I promise.) Suppose a compiler has a smart pointer-to-T object,and it's faced with the need to convert that object into a smart pointer-to-base-class-of-T. The compiler checksthe class definition for SmartPtr<T> to see if the requisite conversion operator is declared, but it is not. (It can'tbe: no conversion operators are declared in the template above.) The compiler then checks to see if there's amember function template it can instantiate that would let it perform the conversion it's looking for. It finds sucha template (the one taking the formal type parameter newType), so it instantiates the template with newTypebound to the base class of T that's the target of the conversion. At that point, the only question is whether thecode for the instantiated member function will compile. In order for it to compile, it must be legal to pass the(dumb) pointer pointee to the constructor for the smart pointer-to-base-of-T. pointee is of type T, so it iscertainly legal to convert it into a pointer to its (public or protected) base classes. Hence, the code for the typeconversion operator will compile, and the implicit conversion from smart pointer-to-T to smartpointer-to-base-of-T will succeed.

An example will help. Let us return to the music hierarchy of CDs, cassettes, and music products. We sawearlier that the following code wouldn't compile, because there was no way for compilers to convert the smartpointers to CDs or cassettes into smart pointers to music products:

void displayAndPlay(const SmartPtr<MusicProduct>& pmp,

int howMany);

SmartPtr<Cassette> funMusic(new Cassette("Alapalooza"));

SmartPtr<CD> nightmareMusic(new CD("Disco Hits of the 70s"));

displayAndPlay(funMusic, 10); // used to be an error

Page 353: Effective C++ and More Effective C++

displayAndPlay(nightmareMusic, 0); // used to be an error

With the revised smart pointer class containing the member function template for implicit type conversionoperators, this code will succeed. To see why, look at this call:

displayAndPlay(funMusic, 10);

The object funMusic is of type SmartPtr<Cassette>. The function displayAndPlay expects aSmartPtr<MusicProduct> object. Compilers detect the type mismatch and seek a way to convert funMusic into aSmartPtr<MusicProduct> object. They look for a single-argument constructor (see Item 5) in theSmartPtr<MusicProduct> class that takes a SmartPtr<Cassette>, but they find none. They look for an implicittype conversion operator in the SmartPtr<Cassette> class that yields a SmartPtr<MusicProduct> class, but thatsearch also fails. They then look for a member function template they can instantiate to yield one of thesefunctions. They discover that the template inside SmartPtr<Cassette>, when instantiated with newType bound toMusicProduct, generates the necessary function. They instantiate the function, yielding the following code:

SmartPtr<Cassette>:: operator SmartPtr<MusicProduct>()

{

return SmartPtr<MusicProduct>(pointee);

}

Will this compile? For all intents and purposes, nothing is happening here except the calling of theSmartPtr<MusicProduct> constructor with pointee as its argument, so the real question is whether one canconstruct a SmartPtr<MusicProduct> object with a Cassette* pointer. The SmartPtr<MusicProduct> constructorexpects a MusicProduct* pointer, but now we're on the familiar ground of conversions between dumb pointertypes, and it's clear that Cassette* can be passed in where a MusicProduct* is expected. The construction of theSmartPtr<MusicProduct> is therefore successful, and the conversion of the SmartPtr<Cassette> toSmartPtr<MusicProduct> is equally successful. Voilà! Implicit conversion of smart pointer types. What could besimpler?

Furthermore, what could be more powerful? Don't be misled by this example into assuming that this works onlyfor pointer conversions up an inheritance hierarchy. The method shown succeeds for any legal implicitconversion between pointer types. If you've got a dumb pointer type T1* and another dumb pointer type T2*, youcan implicitly convert a smart pointer-to-T1 to a smart pointer-to-T2 if and only if you can implicitly convert aT1* to a T2*.

This technique gives you exactly the behavior you want ? almost. Suppose we augment our MusicProducthierarchy with a new class, CasSingle, for representing cassette singles. The revised hierarchy looks like this:

Page 354: Effective C++ and More Effective C++

Now consider this code: template<class T> // as above, including member tem-

class SmartPtr { ... }; // plate for conversion operators

void displayAndPlay(const SmartPtr<MusicProduct>& pmp,

int howMany);

void displayAndPlay(const SmartPtr<Cassette>& pc,

int howMany);

SmartPtr<CasSingle> dumbMusic(new CasSingle("Achy Breaky Heart"));

displayAndPlay(dumbMusic, 1); // error!

In this example, displayAndPlay is overloaded, with one function taking a SmartPtr<MusicProduct> object andthe other taking a SmartPtr<Cassette> object. When we invoke displayAndPlay with a SmartPtr<CasSingle>, weexpect the SmartPtr<Cassette> function to be chosen, because CasSingle inherits directly from Cassette and onlyindirectly from MusicProduct. Certainly that's how it would work with dumb pointers. Alas, our smart pointersaren't that smart. They employ member functions as conversion operators, and as far as C++ compilers areconcerned, all calls to conversion functions are equally good. As a result, the call to displayAndPlay isambiguous, because the conversion from SmartPtr<CasSingle> to SmartPtr<Cassette> is no better than theconversion to SmartPtr<MusicProduct>.

Implementing smart pointer conversions through member templates has two additional drawbacks. First, supportfor member templates is rare, so this technique is currently anything but portable. In the future, that will change,but nobody knows just how far in the future that will be. Second, the mechanics of why this works are far fromtransparent, relying as they do on a detailed understanding of argument-matching rules for function calls, implicittype conversion functions, implicit instantiation of template functions, and the existence of member functiontemplates. Pity the poor programmer who has never seen this trick before and is then asked to maintain orenhance code that relies on it. The technique is clever, that's for sure, but too much cleverness can be adangerous thing.

Let's stop beating around the bush. What we really want to know is how we can make smart pointer classesbehave just like dumb pointers for purposes of inheritance-based type conversions. The answer is simple: wecan't. As Daniel Edelson has noted, smart pointers are smart, but they're not pointers. The best we can do is touse member templates to generate conversion functions, then use casts (see Item 2) in those cases whereambiguity results. This isn't a perfect state of affairs, but it's pretty good, and having to cast away ambiguity in afew cases is a small price to pay for the sophisticated functionality smart pointers can provide.

Smart Pointers and const

Recall that for dumb pointers, const can refer to the thing pointed to, to the pointer itself, or both (see Item E21):

CD goodCD("Flood");

const CD *p; // p is a non-const pointer

// to a const CD object

Page 355: Effective C++ and More Effective C++

CD * const p = &goodCD; // p is a const pointer to

// a non-const CD object;

// because p is const, it

// must be initialized

const CD * const p = &goodCD; // p is a const pointer to

// a const CD object

Naturally, we'd like to have the same flexibility with smart pointers. Unfortunately, there's only one place to putthe const, and there it applies to the pointer, not to the object pointed to:

const SmartPtr<CD> p = // p is a const smart ptr

&goodCD; // to a non-const CD object

This seems simple enough to remedy ? just create a smart pointer to a const CD: SmartPtr<const CD> p = // p is a non-const smart ptr

&goodCD; // to a const CD object

Now we can create the four combinations of const and non-const objects and pointers we seek: SmartPtr<CD> p; // non-const object,

// non-const pointer

SmartPtr<const CD> p; // const object,

// non-const pointer

const SmartPtr<CD> p = &goodCD; // non-const object,

// const pointer

const SmartPtr<const CD> p = &goodCD; // const object,

// const pointer

Alas, this ointment has a fly in it. Using dumb pointers, we can initialize const pointers with non-const pointersand we can initialize pointers to const objects with pointers to non-consts; the rules for assignments areanalogous. For example:

CD *pCD = new CD("Famous Movie Themes");

const CD * pConstCD = pCD; // fine

But look what happens if we try the same thing with smart pointers: SmartPtr<CD> pCD = new CD("Famous Movie Themes");

SmartPtr<const CD> pConstCD = pCD; // fine?

SmartPtr<CD> and SmartPtr<const CD> are completely different types. As far as your compilers know, theyare unrelated, so they have no reason to believe they are assignment-compatible. In what must be an old story bynow, the only way these two types will be considered assignment-compatible is if you've provided a function toconvert objects of type SmartPtr<CD> to objects of type SmartPtr<const CD>. If you've got a compiler thatsupports member templates, you can use the technique shown above for automatically generating the implicittype conversion operators you need. (I remarked earlier that the technique worked anytime the correspondingconversion for dumb pointers would work, and I wasn't kidding. Conversions involving const are no exception.)If you don't have such a compiler, you have to jump through one more hoop.

Conversions involving const are a one-way street: it's safe to go from non-const to const, but it's not safe to gofrom const to non-const. Furthermore, anything you can do with a const pointer you can do with a non-constpointer, but with non-const pointers you can do other things, too (for example, assignment). Similarly, anythingyou can do with a pointer-to-const is legal for a pointer-to-non-const, but you can do some things (such asassignment) with pointers-to-non-consts that you can't do with pointers-to-consts.

Page 356: Effective C++ and More Effective C++

These rules sound like the rules for public inheritance (see Item E35). You can convert from a derived classobject to a base class object, but not vice versa, and you can do anything to a derived class object you can do toa base class object, but you can typically do additional things to a derived class object, as well. We can takeadvantage of this similarity when implementing smart pointers by having each smart pointer-to-T class publiclyinherit from a corresponding smart pointer-to-const-T class:

template<class T> // smart pointers to const

class SmartPtrToConst { // objects

... // the usual smart pointer

// member functions

protected:

union {

const T* constPointee; // for SmartPtrToConst access

T* pointee; // for SmartPtr access

};

};

template<class T> // smart pointers to

class SmartPtr: // non-const objects

public SmartPtrToConst<T> {

... // no data members

};

With this design, the smart pointer-to-non-const-T object needs to contain a dumb pointer-to-non-const-T, andthe smart pointer-to-const-T needs to contain a dumb pointer-to-const-T. The naive way to handle this would beto put a dumb pointer-to-const-T in the base class and a dumb pointer-to-non-const-T in the derived class. Thatwould be wasteful, however, because SmartPtr objects would contain two dumb pointers: the one they inheritedfrom SmartPtrToConst and the one in SmartPtr itself.

This problem is resolved by employing that old battle axe of the C world, a union, which can be as useful inC++ as it is in C. The union is protected, so both classes have access to it, and it contains both of the necessarydumb pointer types. SmartPtrToConst<T> objects use the constPointee pointer, SmartPtr<T> objects use thepointee pointer. We therefore get the advantages of two different pointers without having to allocate space formore than one. (See Item E10 for another example of this.) Such is the beauty of a union. Of course, the memberfunctions of the two classes must constrain themselves to using only the appropriate pointer, and you'll get no

Page 357: Effective C++ and More Effective C++

help from compilers in enforcing that constraint. Such is the risk of a union.

With this new design, we get the behavior we want: SmartPtr<CD> pCD = new CD("Famous Movie Themes");

SmartPtrToConst<CD> pConstCD = pCD; // fine

Evaluation

That wraps up the subject of smart pointers, but before we leave the topic, we should ask this question: are theyworth the trouble, especially if your compilers lack support for member function templates?

Often they are. The reference-counting code of Item 29, for example, is greatly simplified by using smartpointers. Furthermore, as that example demonstrates, some uses of smart pointers are sufficiently limited inscope that things like testing for nullness, conversion to dumb pointers, inheritance-based conversions, andsupport for pointers-to-consts are irrelevant. At the same time, smart pointers can be tricky to implement,understand, and maintain. Debugging code using smart pointers is more difficult than debugging code using dumbpointers. Try as you may, you will never succeed in designing a general-purpose smart pointer that canseamlessly replace its dumb pointer counterpart.

Smart pointers nevertheless make it possible to achieve effects in your code that would otherwise be difficult toimplement. Smart pointers should be used judiciously, but every C++ programmer will find them useful at onetime or another.

Back to Item 27: Requiring or prohibiting heap-based objects Continue to Item 29: Reference counting

Page 358: Effective C++ and More Effective C++

Back to Item 28: Smart pointersContinue to Item 30: Proxy classes

Item 29: Reference counting.

Reference counting is a technique that allows multiple objects with the same value to share a singlerepresentation of that value. There are two common motivations for the technique. The first is to simplify thebookkeeping surrounding heap objects. Once an object is allocated by calling new, it's crucial to keep track ofwho owns that object, because the owner ? and only the owner ? is responsible for calling delete on it. Butownership can be transferred from object to object as a program runs (by passing pointers as parameters, forexample), so keeping track of an object's ownership is hard work. Classes like auto_ptr (see Item 9) can helpwith this task, but experience has shown that most programs still fail to get it right. Reference countingeliminates the burden of tracking object ownership, because when an object employs reference counting, it ownsitself. When nobody is using it any longer, it destroys itself automatically. Thus, reference counting constitutes asimple form of garbage collection.

The second motivation for reference counting is simple common sense. If many objects have the same value, it'ssilly to store that value more than once. Instead, it's better to let all the objects with that value share itsrepresentation. Doing so not only saves memory, it also leads to faster-running programs, because there's noneed to construct and destruct redundant copies of the same object value.

Like most simple ideas, this one hovers above a sea of interesting details. God may or may not be in the details,but successful implementations of reference counting certainly are. Before delving into details, however, let usmaster basics. A good way to begin is by seeing how we might come to have many objects with the same valuein the first place. Here's one way:

class String { // the standard string type may

public: // employ the techniques in this

// Item, but that is not required

String(const char *value = "");

String& operator=(const String& rhs);

...

private:

char *data;

};

String a, b, c, d, e;

a = b = c = d = e = "Hello";

It should be apparent that objects a through e all have the same value, namely "Hello". How that value isrepresented depends on how the String class is implemented, but a common implementation would have eachString object carry its own copy of the value. For example, String's assignment operator might be implementedlike this:

String& String::operator=(const String& rhs)

{

if (this == &rhs) return *this; // see Item E17

delete [] data;

data = new char[strlen(rhs.data) + 1];

strcpy(data, rhs.data);

return *this; // see Item E15

}

Given this implementation, we can envision the five objects and their values as follows:

Page 359: Effective C++ and More Effective C++

The redundancy in this approach is clear. In an ideal world, we'd like to change the picture to look like this:

Here only one copy of the value "Hello" is stored, and all the String objects with that value share itsrepresentation.

In practice, it isn't possible to achieve this ideal, because we need to keep track of how many objects are sharinga value. If object a above is assigned a different value from "Hello", we can't destroy the value "Hello", becausefour other objects still need it. On the other hand, if only a single object had the value "Hello" and that objectwent out of scope, no object would have that value and we'd have to destroy the value to avoid a resource leak.

The need to store information on the number of objects currently sharing ? referring to ? a value means our idealpicture must be modified somewhat to take into account the existence of a reference count:

Page 360: Effective C++ and More Effective C++

(Some people call this number a use count, but I am not one of them. C++ has enough idiosyncrasies of its own;the last thing it needs is terminological factionalism.)

Implementing Reference Counting

Creating a reference-counted String class isn't difficult, but it does require attention to detail, so we'll walkthrough the implementation of the most common member functions of such a class. Before we do that, however,it's important to recognize that we need a place to store the reference count for each String value. That placecannot be in a String object, because we need one reference count per string value, not one reference count perstring object. That implies a coupling between values and reference counts, so we'll create a class to storereference counts and the values they track. We'll call this class StringValue, and because its only raison d'être isto help implement the String class, we'll nest it inside String's private section. Furthermore, it will be convenientto give all the member functions of String full access to the StringValue data structure, so we'll declareStringValue to be a struct. This is a trick worth knowing: nesting a struct in the private part of a class is aconvenient way to give access to the struct to all the members of the class, but to deny access to everybody else(except, of course, friends of the class).

Our basic design looks like this: class String {

public:

... // the usual String member

// functions go here

private:

struct StringValue { ... }; // holds a reference count

// and a string value

StringValue *value; // value of this String

};

Page 361: Effective C++ and More Effective C++

We could give this class a different name (RCString, perhaps) to emphasize that it's implemented usingreference counting, but the implementation of a class shouldn't be of concern to clients of that class. Rather,clients should interest themselves only in a class's public interface. Our reference-counting implementation ofthe String interface supports exactly the same operations as a non-reference-counted version, so why muddy theconceptual waters by embedding implementation decisions in the names of classes that correspond to abstractconcepts? Why indeed? So we don't.

Here's StringValue: class String {

private:

struct StringValue {

int refCount;

char *data;

StringValue(const char *initValue);

~StringValue();

};

...

};

String::StringValue::StringValue(const char *initValue)

: refCount(1)

{

data = new char[strlen(initValue) + 1];

strcpy(data, initValue);

}

String::StringValue::~StringValue()

{

delete [] data;

}

That's all there is to it, and it should be clear that's nowhere near enough to implement the full functionality of areference-counted string. For one thing, there's neither a copy constructor nor an assignment operator (see ItemE11), and for another, there's no manipulation of the refCount field. Worry not ? the missing functionality will beprovided by the String class. The primary purpose of StringValue is to give us a place to associate a particularvalue with a count of the number of String objects sharing that value. StringValue gives us that, and that'senough.

We're now ready to walk our way through String's member functions. We'll begin with the constructors: class String {

public:

String(const char *initValue = "");

String(const String& rhs);

...

};

The first constructor is implemented about as simply as possible. We use the passed-in char* string to create anew StringValue object, then we make the String object we're constructing point to the newly-mintedStringValue:

String::String(const char *initValue)

: value(new StringValue(initValue))

{}

Page 362: Effective C++ and More Effective C++

For client code that looks like this, String s("More Effective C++");

we end up with a data structure that looks like this:

String objects constructed separately, but with the same initial value do not share a data structure, so client codeof this form,

String s1("More Effective C++");

String s2("More Effective C++");

yields this data structure:

It is possible to eliminate such duplication by having String (or StringValue) keep track of existing StringValueobjects and create new ones only for truly unique strings, but such refinements on reference counting aresomewhat off the beaten path. As a result, I'll leave them in the form of the feared and hated exercise for thereader.

The String copy constructor is not only unfeared and unhated, it's also efficient: the newly created String objectshares the same StringValue object as the String object that's being copied:

String::String(const String& rhs)

: value(rhs.value)

{

++value->refCount;

}

Graphically, code like this, String s1("More Effective C++");

String s2 = s1;

results in this data structure:

Page 363: Effective C++ and More Effective C++

This is substantially more efficient than a conventional (non-reference-counted) String class, because there is noneed to allocate memory for the second copy of the string value, no need to deallocate that memory later, and noneed to copy the value that would go in that memory. Instead, we merely copy a pointer and increment areference count.

The String destructor is also easy to implement, because most of the time it doesn't do anything. As long as thereference count for a StringValue is non-zero, at least one String object is using the value; it must therefore notbe destroyed. Only when the String being destructed is the sole user of the value ? i.e., when the value'sreference count is 1 ? should the String destructor destroy the StringValue object:

class String {

public:

~String();

...

};

String::~String()

{

if (--value->refCount == 0) delete value;

}

Compare the efficiency of this function with that of the destructor for a non-reference-counted implementation.Such a function would always call delete and would almost certainly have a nontrivial runtime cost. Providedthat different String objects do in fact sometimes have the same values, the implementation above willsometimes do nothing more than decrement a counter and compare it to zero.

If, at this point, the appeal of reference counting is not becoming apparent, you're just not paying attention.

That's all there is to String construction and destruction, so we'll move on to consideration of the Stringassignment operator:

class String {

public:

String& operator=(const String& rhs);

...

};

When a client writes code like this, s1 = s2; // s1 and s2 are both String objects

the result of the assignment should be that s1 and s2 both point to the same StringValue object. That object'sreference count should therefore be incremented during the assignment. Furthermore, the StringValue object thats1 pointed to prior to the assignment should have its reference count decremented, because s1 will no longerhave that value. If s1 was the only String with that value, the value should be destroyed. In C++, all that lookslike this:

String& String::operator=(const String& rhs)

{

if (value == rhs.value) { // do nothing if the values

return *this; // are already the same; this

} // subsumes the usual test of

Page 364: Effective C++ and More Effective C++

// this against &rhs (see Item E17)

if (--value->refCount == 0) { // destroy *this's value if

delete value; // no one else is using it

}

value = rhs.value; // have *this share rhs's

++value->refCount; // value

return *this;

}

Copy-on-Write

To round out our examination of reference-counted strings, consider an array-bracket operator ([]), whichallows individual characters within strings to be read and written:

class String {

public:

const char&

operator[](int index) const; // for const Strings

char& operator[](int index); // for non-const Strings

...

};

Implementation of the const version of this function is straightforward, because it's a read-only operation; thevalue of the string can't be affected:

const char& String::operator[](int index) const

{

return value->data[index];

}

(This function performs sanity checking on index in the grand C++ tradition, which is to say not at all. As usual,if you'd like a greater degree of parameter validation, it's easy to add.)

The non-const version of operator[] is a completely different story. This function may be called to read acharacter, but it might be called to write one, too:

String s;

...

cout << s[3]; // this is a read

s[5] = 'x'; // this is a write

We'd like to deal with reads and writes differently. A simple read can be dealt with in the same way as theconst version of operator[] above, but a write must be implemented in quite a different fashion.

When we modify a String's value, we have to be careful to avoid modifying the value of other String objects thathappen to be sharing the same StringValue object. Unfortunately, there is no way for C++ compilers to tell uswhether a particular use of operator[] is for a read or a write, so we must be pessimistic and assume that allcalls to the non-const operator[] are for writes. (Proxy classes can help us differentiate reads from writes ? see Item 30.)

To implement the non-const operator[] safely, we must ensure that no other String object shares the StringValueto be modified by the presumed write. In short, we must ensure that the reference count for a String's StringValueobject is exactly one any time we return a reference to a character inside that StringValue object. Here's how we

Page 365: Effective C++ and More Effective C++

do it: char& String::operator[](int index)

{

// if we're sharing a value with other String objects,

// break off a separate copy of the value for ourselves

if (value->refCount > 1) {

--value->refCount; // decrement current value's

// refCount, because we won't

// be using that value any more

value = // make a copy of the

new StringValue(value->data); // value for ourselves

}

// return a reference to a character inside our

// unshared StringValue object

return value->data[index];

}

This idea ? that of sharing a value with other objects until we have to write on our own copy of the value ? hasa long and distinguished history in Computer Science, especially in operating systems, where processes areroutinely allowed to share pages until they want to modify data on their own copy of a page. The technique iscommon enough to have a name: copy-on-write. It's a specific example of a more general approach to efficiency,that of lazy evaluation (see Item 17).

Pointers, References, and Copy-on-Write

This implementation of copy-on-write allows us to preserve both efficiency and correctness ? almost. There isone lingering problem. Consider this code:

String s1 = "Hello";

char *p = &s1[1];

Our data structure at this point looks like this:

Now consider an additional statement: String s2 = s1;

The String copy constructor will make s2 share s1's StringValue, so the resulting data structure will be thisone:

Page 366: Effective C++ and More Effective C++

The implications of a statement such as the following, then, are not pleasant to contemplate: *p = 'x'; // modifies both s1 and s2!

There is no way the String copy constructor can detect this problem, because it has no way to know that apointer into s1's StringValue object exists. And this problem isn't limited to pointers: it would exist if someonehad saved a reference to the result of a call to String's non-const operator[].

There are at least three ways of dealing with this problem. The first is to ignore it, to pretend it doesn't exist.This approach turns out to be distressingly common in class libraries that implement reference-counted strings.If you have access to a reference-counted string, try the above example and see if you're distressed, too. If you'renot sure if you have access to a reference-counted string, try the example anyway. Through the wonder ofencapsulation, you may be using such a type without knowing it.

Not all implementations ignore such problems. A slightly more sophisticated way of dealing with suchdifficulties is to define them out of existence. Implementations adopting this strategy typically put something intheir documentation that says, more or less, "Don't do that. If you do, results are undefined." If you then do itanyway ? wittingly or no ? and complain about the results, they respond, "Well, we told you not to do that." Suchimplementations are often efficient, but they leave much to be desired in the usability department.

There is a third solution, and that's to eliminate the problem. It's not difficult to implement, but it can reduce theamount of value sharing between objects. Its essence is this: add a flag to each StringValue object indicatingwhether that object is shareable. Turn the flag on initially (the object is shareable), but turn it off whenever thenon-const operator[] is invoked on the value represented by that object. Once the flag is set to false, it stays thatway forever.10

Here's a modified version of StringValue that includes a shareability flag: class String {

private:

struct StringValue {

int refCount;

bool shareable; // add this

char *data;

StringValue(const char *initValue);

~StringValue();

};

...

};

Page 367: Effective C++ and More Effective C++

String::StringValue::StringValue(const char *initValue)

: refCount(1),

shareable(true) // add this

{

data = new char[strlen(initValue) + 1];

strcpy(data, initValue);

}

String::StringValue::~StringValue()

{

delete [] data;

}

As you can see, not much needs to change; the two lines that require modification are flagged with comments.Of course, String's member functions must be updated to take the shareable field into account. Here's how thecopy constructor would do that:

String::String(const String& rhs)

{

if (rhs.value->shareable) {

value = rhs.value;

++value->refCount;

}

else {

value = new StringValue(rhs.value->data);

}

}

All the other String member functions would have to check the shareable field in an analogous fashion. Thenon-const version of operator[] would be the only function to set the shareable flag to false:

char& String::operator[](int index)

{

if (value->refCount > 1) {

--value->refCount;

value = new StringValue(value->data);

}

value->shareable = false; // add this

return value->data[index];

}

If you use the proxy class technique of Item 30 to distinguish read usage from write usage in operator[], you canusually reduce the number of StringValue objects that must be marked unshareable.

A Reference-Counting Base Class

Reference counting is useful for more than just strings. Any class in which different objects may have values incommon is a legitimate candidate for reference counting. Rewriting a class to take advantage of referencecounting can be a lot of work, however, and most of us already have more than enough to do. Wouldn't it be niceif we could somehow write (and test and document) the reference counting code in a context-independentmanner, then just graft it onto classes when needed? Of course it would. In a curious twist of fate, there's a wayto do it (or at least to do most of it).

The first step is to create a base class, RCObject, for reference-counted objects. Any class wishing to takeadvantage of automatic reference counting must inherit from this class. RCObject encapsulates the referencecount itself, as well as functions for incrementing and decrementing that count. It also contains the code fordestroying a value when it is no longer in use, i.e., when its reference count becomes 0. Finally, it contains afield that keeps track of whether this value is shareable, and it provides functions to query this value and set it to

Page 368: Effective C++ and More Effective C++

false. There is no need for a function to set the shareability field to true, because all values are shareable bydefault. As noted above, once an object has been tagged unshareable, there is no way to make it shareableagain.

RCObject's class definition looks like this: class RCObject {

public:

RCObject();

RCObject(const RCObject& rhs);

RCObject& operator=(const RCObject& rhs);

virtual ~RCObject() = 0;

void addReference();

void removeReference();

void markUnshareable();

bool isShareable() const;

bool isShared() const;

private:

int refCount;

bool shareable;

};

RCObjects can be created (as the base class parts of more derived objects) and destroyed; they can have newreferences added to them and can have current references removed; their shareability status can be queried andcan be disabled; and they can report whether they are currently being shared. That's all they offer. As a classencapsulating the notion of being reference-countable, that's really all we have a right to expect them to do. Notethe tell-tale virtual destructor, a sure sign this class is designed for use as a base class (see Item E14). Note alsohow the destructor is a pure virtual function, a sure sign this class is designed to be used only as a base class.

The code to implement RCObject is, if nothing else, brief: RCObject::RCObject()

: refCount(0), shareable(true) {}

RCObject::RCObject(const RCObject&)

: refCount(0), shareable(true) {}

RCObject& RCObject::operator=(const RCObject&)

{ return *this; }

RCObject::~RCObject() {} // virtual dtors must always

// be implemented, even if

// they are pure virtual

// and do nothing (see also

// Item 33 and Item E14)

void RCObject::addReference() { ++refCount; }

void RCObject::removeReference()

{ if (--refCount == 0) delete this; }

void RCObject::markUnshareable()

{ shareable = false; }

bool RCObject::isShareable() const

{ return shareable; }

bool RCObject::isShared() const

{ return refCount > 1; }

Page 369: Effective C++ and More Effective C++

Curiously, we set refCount to 0 inside both constructors. This seems counterintuitive. Surely at least the creatorof the new RCObject is referring to it! As it turns out, it simplifies things for the creators of RCObjects to setrefCount to 1 themselves, so we oblige them here by not getting in their way. We'll get a chance to see theresulting code simplification shortly.

Another curious thing is that the copy constructor always sets refCount to 0, regardless of the value of refCountfor the RCObject we're copying. That's because we're creating a new object representing a value, and newvalues are always unshared and referenced only by their creator. Again, the creator is responsible for setting therefCount to its proper value.

The RCObject assignment operator looks downright subversive: it does nothing. Frankly, it's unlikely thisoperator will ever be called. RCObject is a base class for a shared value object, and in a system based onreference counting, such objects are not assigned to one another, objects pointing to them are. In our case, wedon't expect StringValue objects to be assigned to one another, we expect only String objects to be involved inassignments. In such assignments, no change is made to the value of a StringValue ? only the StringValuereference count is modified.

Nevertheless, it is conceivable that some as-yet-unwritten class might someday inherit from RCObject and mightwish to allow assignment of reference-counted values (see Item 32 and Item E16). If so, RCObject's assignmentoperator should do the right thing, and the right thing is to do nothing. To see why, imagine that we wished toallow assignments between StringValue objects. Given StringValue objects sv1 and sv2, what should happen tosv1's and sv2's reference counts in an assignment?

sv1 = sv2; // how are sv1's and sv2's reference

// counts affected?

Before the assignment, some number of String objects are pointing to sv1. That number is unchanged by theassignment, because only sv1's value changes. Similarly, some number of String objects are pointing to sv2prior to the assignment, and after the assignment, exactly the same String objects point to sv2. sv2's referencecount is also unchanged. When RCObjects are involved in an assignment, then, the number of objects pointing tothose objects is unaffected, hence RCObject::operator= should change no reference counts. That's exactly whatthe implementation above does. Counterintuitive? Perhaps, but it's still correct.

The code for RCObject::removeReference is responsible not only for decrementing the object's refCount, butalso for destroying the object if the new value of refCount is 0. It accomplishes this latter task by deleteing this,which, as Item 27 explains, is safe only if we know that *this is a heap object. For this class to be successful, wemust engineer things so that RCObjects can be created only on the heap. General approaches to achieving thatend are discussed in Item 27, but the specific measures we'll employ in this case are described at the conclusionof this Item.

To take advantage of our new reference-counting base class, we modify StringValue to inherit its referencecounting capabilities from RCObject:

class String {

private:

struct StringValue: public RCObject {

char *data;

StringValue(const char *initValue);

~StringValue();

};

...

};

String::StringValue::StringValue(const char *initValue)

{

data = new char[strlen(initValue) + 1];

Page 370: Effective C++ and More Effective C++

strcpy(data, initValue);

}

String::StringValue::~StringValue()

{

delete [] data;

}

This version of StringValue is almost identical to the one we saw earlier. The only thing that's changed is thatStringValue's member functions no longer manipulate the refCount field. RCObject now handles what they usedto do.

Don't feel bad if you blanched at the sight of a nested class (StringValue) inheriting from a class (RCObject)that's unrelated to the nesting class (String). It looks weird to everybody at first, but it's perfectly kosher. Anested class is just as much a class as any other, so it has the freedom to inherit from whatever other classes itlikes. In time, you won't think twice about such inheritance relationships.

Automating Reference Count Manipulations

The RCObject class gives us a place to store a reference count, and it gives us member functions through whichthat reference count can be manipulated, but the calls to those functions must still be manually inserted in otherclasses. It is still up to the String copy constructor and the String assignment operator to call addReference andremoveReference on StringValue objects. This is clumsy. We'd like to move those calls out into a reusableclass, too, thus freeing authors of classes like String from worrying about any of the details of referencecounting. Can it be done? Isn't C++ supposed to support reuse?

It can, and it does. There's no easy way to arrange things so that all reference-counting considerations can bemoved out of application classes, but there is a way to eliminate most of them for most classes. (In someapplication classes, you can eliminate all reference-counting code, but our String class, alas, isn't one of them.One member function spoils the party, and I suspect you won't be too surprised to hear it's our old nemesis, thenon-const version of operator[]. Take heart, however; we'll tame that miscreant in the end.)

Notice that each String object contains a pointer to the StringValue object representing that String's value: class String {

private:

struct StringValue: public RCObject { ... };

StringValue *value; // value of this String

...

};

We have to manipulate the refCount field of the StringValue object anytime anything interesting happens to oneof the pointers pointing to it. "Interesting happenings" include copying a pointer, reassigning one, and destroyingone. If we could somehow make the pointer itself detect these happenings and automatically perform thenecessary manipulations of the refCount field, we'd be home free. Unfortunately, pointers are rather densecreatures, and the chances of them detecting anything, much less automatically reacting to things they detect, arepretty slim. Fortunately, there's a way to smarten them up: replace them with objects that act like pointers, butthat do more.

Such objects are called smart pointers, and you can read about them in more detail than you probably care to inItem 28. For our purposes here, it's enough to know that smart pointer objects support the member selection (->)and dereferencing (*) operations, just like real pointers (which, in this context, are generally referred to as dumbpointers), and, like dumb pointers, they are strongly typed: you can't make a smart pointer-to-T point to anobject that isn't of type T.

Here's a template for objects that act as smart pointers to reference-counted objects: // template class for smart pointers-to-T objects. T must

Page 371: Effective C++ and More Effective C++

// support the RCObject interface, typically by inheriting

// from RCObject

template<class T>

class RCPtr {

public:

RCPtr(T* realPtr = 0);

RCPtr(const RCPtr& rhs);

~RCPtr();

RCPtr& operator=(const RCPtr& rhs);

T* operator->() const; // see Item 28

T& operator*() const; // see Item 28

private:

T *pointee; // dumb pointer this

// object is emulating

void init(); // common initialization

}; // code

This template gives smart pointer objects control over what happens during their construction, assignment, anddestruction. When such events occur, these objects can automatically perform the appropriate manipulations ofthe refCount field in the objects to which they point.

For example, when an RCPtr is created, the object it points to needs to have its reference count increased.There's no need to burden application developers with the requirement to tend to this irksome detail manually,because RCPtr constructors can handle it themselves. The code in the two constructors is all but identical ? onlythe member initialization lists differ ? so rather than write it twice, we put it in a private member function calledinit and have both constructors call that:

template<class T>

RCPtr<T>::RCPtr(T* realPtr): pointee(realPtr)

{

init();

}

template<class T>

RCPtr<T>::RCPtr(const RCPtr& rhs): pointee(rhs.pointee)

{

init();

}

template<class T>

void RCPtr<T>::init()

{

if (pointee == 0) { // if the dumb pointer is

return; // null, so is the smart one

}

if (pointee->isShareable() == false) { // if the value

pointee = new T(*pointee); // isn't shareable,

} // copy it

pointee->addReference(); // note that there is now a

} // new reference to the value

Moving common code into a separate function like init is exemplary software engineering, but its luster dimswhen, as in this case, the function doesn't behave correctly.

Page 372: Effective C++ and More Effective C++

The problem is this. When init needs to create a new copy of a value (because the existing copy isn't shareable),it executes the following code:

pointee = new T(*pointee);

The type of pointee is pointer-to-T, so this statement creates a new T object and initializes it by calling T's copyconstructor. In the case of an RCPtr in the String class, T will be String::StringValue, so the statement abovewill call String::StringValue's copy constructor. We haven't declared a copy constructor for that class, however,so our compilers will generate one for us. The copy constructor so generated will, in accordance with the rulesfor automatically generated copy constructors in C++, copy only StringValue's data pointer; it will not copy thechar* string data points to. Such behavior is disastrous in nearly any class (not just reference-counted classes),and that's why you should get into the habit of writing a copy constructor (and an assignment operator) for allyour classes that contain pointers (see Item E11).

The correct behavior of the RCPtr<T> template depends on T containing a copy constructor that makes a trulyindependent copy (i.e., a deep copy) of the value represented by T. We must augment StringValue with such aconstructor before we can use it with the RCPtr class:

class String {

private:

struct StringValue: public RCObject {

StringValue(const StringValue& rhs);

...

};

...

};

String::StringValue::StringValue(const StringValue& rhs)

{

data = new char[strlen(rhs.data) + 1];

strcpy(data, rhs.data);

}

The existence of a deep-copying copy constructor is not the only assumption RCPtr<T> makes about T. It alsorequires that T inherit from RCObject, or at least that T provide all the functionality that RCObject does. In viewof the fact that RCPtr objects are designed to point only to reference-counted objects, this is hardly anunreasonable assumption. Nevertheless, the assumption must be documented.

A final assumption in RCPtr<T> is that the type of the object pointed to is T. This seems obvious enough. Afterall, pointee is declared to be of type T*. But pointee might really point to a class derived from T. For example,if we had a class SpecialStringValue that inherited from String::StringValue,

class String {

private:

struct StringValue: public RCObject { ... };

struct SpecialStringValue: public StringValue { ... };

...

};

we could end up with a String containing a RCPtr<StringValue> pointing to a SpecialStringValue object. In thatcase, we'd want this part of init,

pointee = new T(*pointee); // T is StringValue, but

Page 373: Effective C++ and More Effective C++

// pointee really points to

// a SpecialStringValue

to call SpecialStringValue's copy constructor, not StringValue's. We can arrange for this to happen by using avirtual copy constructor (see Item 25). In the case of our String class, we don't expect classes to derive fromStringValue, so we'll disregard this issue.

With RCPtr's constructors out of the way, the rest of the class's functions can be dispatched with considerablygreater alacrity. Assignment of an RCPtr is straightforward, though the need to test whether the newly assignedvalue is shareable complicates matters slightly. Fortunately, such complications have already been handled bythe init function that was created for RCPtr's constructors. We take advantage of that fact by using it again here:

template<class T>

RCPtr<T>& RCPtr<T>::operator=(const RCPtr& rhs)

{

if (pointee != rhs.pointee) { // skip assignments

// where the value

// doesn't change

if (pointee) {

pointee->removeReference(); // remove reference to

} // current value

pointee = rhs.pointee; // point to new value

init(); // if possible, share it

} // else make own copy

return *this;

}

The destructor is easier. When an RCPtr is destroyed, it simply removes its reference to the reference-countedobject:

template<class T>

RCPtr<T>::~RCPtr()

{

if (pointee)pointee->removeReference();

}

If the RCPtr that just expired was the last reference to the object, that object will be destroyed insideRCObject's removeReference member function. Hence RCPtr objects never need to worry about destroying thevalues they point to.

Finally, RCPtr's pointer-emulating operators are part of the smart pointer boilerplate you can read about in Item28:

template<class T>

T* RCPtr<T>::operator->() const { return pointee; }

template<class T>

T& RCPtr<T>::operator*() const { return *pointee; }

Putting it All Together

Enough! Finis! At long last we are in a position to put all the pieces together and build a reference-countedString class based on the reusable RCObject and RCPtr classes. With luck, you haven't forgotten that that wasour original goal.

Each reference-counted string is implemented via this data structure:

Page 374: Effective C++ and More Effective C++
Page 375: Effective C++ and More Effective C++

The classes making up this data structure are defined like this:

template<class T> // template class for smart

class RCPtr { // pointers-to-T objects; T

public: // must inherit from RCObject

RCPtr(T* realPtr = 0);

RCPtr(const RCPtr& rhs);

~RCPtr();

RCPtr& operator=(const RCPtr& rhs);

T* operator->() const;

T& operator*() const;

private:

T *pointee;

void init();

};

class RCObject { // base class for reference-

public: // counted objects

void addReference();

void removeReference();

void markUnshareable();

bool isShareable() const;

bool isShared() const;

protected:

RCObject();

RCObject(const RCObject& rhs);

RCObject& operator=(const RCObject& rhs);

virtual ~RCObject() = 0;

private:

int refCount;

bool shareable;

};

class String { // class to be used by

public: // application developers

String(const char *value = "");

const char& operator[](int index) const;

char& operator[](int index);

Page 376: Effective C++ and More Effective C++

private:

// class representing string values

struct StringValue: public RCObject {

char *data;

StringValue(const char *initValue);

StringValue(const StringValue& rhs);

void init(const char *initValue);

~StringValue();

};

RCPtr<StringValue> value;

};

For the most part, this is just a recap of what we've already developed, so nothing should be much of a surprise.Close examination reveals we've added an init function to String::StringValue, but, as we'll see below, thatserves the same purpose as the corresponding function in RCPtr: it prevents code duplication in theconstructors.

There is a significant difference between the public interface of this String class and the one we used at thebeginning of this Item. Where is the copy constructor? Where is the assignment operator? Where is thedestructor? Something is definitely amiss here.

Actually, no. Nothing is amiss. In fact, some things are working perfectly. If you don't see what they are, prepareyourself for a C++ epiphany.

We don't need those functions anymore. Sure, copying of String objects is still supported, and yes, the copyingwill correctly handle the underlying reference-counted StringValue objects, but the String class doesn't have toprovide a single line of code to make this happen. That's because the compiler-generated copy constructor forString will automatically call the copy constructor for String's RCPtr member, and the copy constructor for thatclass will perform all the necessary manipulations of the StringValue object, including its reference count. AnRCPtr is a smart pointer, remember? We designed it to take care of the details of reference counting, so that'swhat it does. It also handles assignment and destruction, and that's why String doesn't need to write thosefunctions, either. Our original goal was to move the unreusable reference-counting code out of our hand-writtenString class and into context-independent classes where it would be available for use with any class. Nowwe've done it (in the form of the RCObject and RCPtr classes), so don't be so surprised when it suddenly startsworking. It's supposed to work.

Just so you have everything in one place, here's the implementation of RCObject: RCObject::RCObject()

: refCount(0), shareable(true) {}

RCObject::RCObject(const RCObject&)

: refCount(0), shareable(true) {}

RCObject& RCObject::operator=(const RCObject&)

{ return *this; }

RCObject::~RCObject() {}

void RCObject::addReference() { ++refCount; }

void RCObject::removeReference()

{ if (--refCount == 0) delete this; }

void RCObject::markUnshareable()

{ shareable = false; }

bool RCObject::isShareable() const

{ return shareable; }

Page 377: Effective C++ and More Effective C++

bool RCObject::isShared() const

{ return refCount > 1; }

And here's the implementation of RCPtr: template<class T>

void RCPtr<T>::init()

{

if (pointee == 0) return;

if (pointee->isShareable() == false) {

pointee = new T(*pointee);

}

pointee->addReference();

}

template<class T>

RCPtr<T>::RCPtr(T* realPtr)

: pointee(realPtr)

{ init(); }

template<class T>

RCPtr<T>::RCPtr(const RCPtr& rhs)

: pointee(rhs.pointee)

{ init(); }

template<class T>

RCPtr<T>::~RCPtr()

{ if (pointee)pointee->removeReference(); }

template<class T>

RCPtr<T>& RCPtr<T>::operator=(const RCPtr& rhs)

{

if (pointee != rhs.pointee) {

if (pointee) pointee->removeReference();

pointee = rhs.pointee;

init();

}

return *this;

}

template<class T>

T* RCPtr<T>::operator->() const { return pointee; }

template<class T>

T& RCPtr<T>::operator*() const { return *pointee; }

The implementation of String::StringValue looks like this: void String::StringValue::init(const char *initValue)

{

data = new char[strlen(initValue) + 1];

strcpy(data, initValue);

}

String::StringValue::StringValue(const char *initValue)

{ init(initValue); }

String::StringValue::StringValue(const StringValue& rhs)

Page 378: Effective C++ and More Effective C++

{ init(rhs.data); }

String::StringValue::~StringValue()

{ delete [] data; }

Ultimately, all roads lead to String, and that class is implemented this way: String::String(const char *initValue)

: value(new StringValue(initValue)) {}

const char& String::operator[](int index) const

{ return value->data[index]; }

char& String::operator[](int index)

{

if (value->isShared()) {

value = new StringValue(value->data);

}

value->markUnshareable();

return value->data[index];

}

If you compare the code for this String class with that we developed for the String class using dumb pointers,you'll be struck by two things. First, there's a lot less of it here than there. That's because RCPtr has assumedmuch of the reference-counting burden that used to fall on String. Second, the code that remains in String isnearly unchanged: the smart pointer replaced the dumb pointer essentially seamlessly. In fact, the only changesare in operator[], where we call isShared instead of checking the value of refCount directly and where our useof the smart RCPtr object eliminates the need to manually manipulate the reference count during acopy-on-write.

This is all very nice, of course. Who can object to less code? Who can oppose encapsulation success stories?The bottom line, however, is determined more by the impact of this newfangled String class on its clients than byany of its implementation details, and it is here that things really shine. If no news is good news, the news here isvery good indeed. The String interface has not changed. We added reference counting, we added the ability tomark individual string values as unshareable, we moved the notion of reference countability into a new baseclass, we added smart pointers to automate the manipulation of reference counts, yet not one line of client codeneeds to be changed. Sure, we changed the String class definition, so clients who want to take advantage ofreference-counted strings must recompile and relink, but their investment in code is completely and utterlypreserved. You see? Encapsulation really is a wonderful thing.

Adding Reference Counting to Existing Classes

Everything we've discussed so far assumes we have access to the source code of the classes we're interested in.But what if we'd like to apply the benefits of reference counting to some class Widget that's in a library we can'tmodify? There's no way to make Widget inherit from RCObject, so we can't use smart RCPtrs with it. Are weout of luck?

We're not. With some minor modifications to our design, we can add reference counting to any type.

First, let's consider what our design would look like if we could have Widget inherit from RCObject. In thatcase, we'd have to add a class, RCWidget, for clients to use, but everything would then be analogous to ourString/StringValue example, with RCWidget playing the role of String and Widget playing the role ofStringValue. The design would look like this:

Page 379: Effective C++ and More Effective C++

We can now apply the maxim that most problems in Computer Science can be solved with an additional level ofindirection. We add a new class, CountHolder, to hold the reference count, and we have CountHolder inheritfrom RCObject. We also have CountHolder contain a pointer to a Widget. We then replace the smart RCPtrtemplate with an equally smart RCIPtr template that knows about the existence of the CountHolder class. (The"I" in RCIPtr stands for "indirect.") The modified design looks like this:

Page 380: Effective C++ and More Effective C++

Just as StringValue was an implementation detail hidden from clients of String, CountHolder is animplementation detail hidden from clients of RCWidget. In fact, it's an implementation detail of RCIPtr, so it'snested inside that class. RCIPtr is implemented this way:

template<class T>

class RCIPtr {

public:

Page 381: Effective C++ and More Effective C++

RCIPtr(T* realPtr = 0);

RCIPtr(const RCIPtr& rhs);

~RCIPtr();

RCIPtr& operator=(const RCIPtr& rhs);

const T* operator->() const; // see below for an

T* operator->(); // explanation of why

const T& operator*() const; // these functions are

T& operator*(); // declared this way

private:

struct CountHolder: public RCObject {

~CountHolder() { delete pointee; }

T *pointee;

};

CountHolder *counter;

void init();

void makeCopy(); // see below

};

template<class T>

void RCIPtr<T>::init()

{

if (counter->isShareable() == false) {

T *oldValue = counter->pointee;

counter = new CountHolder;

counter->pointee = new T(*oldValue);

}

counter->addReference();

}

template<class T>

RCIPtr<T>::RCIPtr(T* realPtr)

: counter(new CountHolder)

{

counter->pointee = realPtr;

init();

}

template<class T>

RCIPtr<T>::RCIPtr(const RCIPtr& rhs)

: counter(rhs.counter)

{ init(); }

template<class T>

RCIPtr<T>::~RCIPtr()

{ counter->removeReference(); }

template<class T>

RCIPtr<T>& RCIPtr<T>::operator=(const RCIPtr& rhs)

{

if (counter != rhs.counter) {

counter->removeReference();

counter = rhs.counter;

init();

}

return *this;

}

template<class T> // implement the copy

void RCIPtr<T>::makeCopy() // part of copy-on-

Page 382: Effective C++ and More Effective C++

{ // write (COW)

if (counter->isShared()) {

T *oldValue = counter->pointee;

counter->removeReference();

counter = new CountHolder;

counter->pointee = new T(*oldValue);

counter->addReference();

}

}

template<class T> // const access;

const T* RCIPtr<T>::operator->() const // no COW needed

{ return counter->pointee; }

template<class T> // non-const

T* RCIPtr<T>::operator->() // access; COW

{ makeCopy(); return counter->pointee; } // needed

template<class T> // const access;

const T& RCIPtr<T>::operator*() const // no COW needed

{ return *(counter->pointee); }

template<class T> // non-const

T& RCIPtr<T>::operator*() // access; do the

{ makeCopy(); return *(counter->pointee); } // COW thing

RCIPtr differs from RCPtr in only two ways. First, RCPtr objects point to values directly, while RCIPtr objectspoint to values through intervening CountHolder objects. Second, RCIPtr overloads operator-> and operator* sothat a copy-on-write is automatically performed whenever a non-const access is made to a pointed-to object.

Given RCIPtr, it's easy to implement RCWidget, because each function in RCWidget is implemented byforwarding the call through the underlying RCIPtr to a Widget object. For example, if Widget looks like this,

class Widget {

public:

Widget(int size);

Widget(const Widget& rhs);

~Widget();

Widget& operator=(const Widget& rhs);

void doThis();

int showThat() const;

};

RCWidget will be defined this way: class RCWidget {

public:

RCWidget(int size): value(new Widget(size)) {}

void doThis() { value->doThis(); }

int showThat() const { return value->showThat(); }

private:

RCIPtr<Widget> value;

};

Note how the RCWidget constructor calls the Widget constructor (via the new operator ? see Item 8) with theargument it was passed; how RCWidget's doThis calls doThis in the Widget class; and howRCWidget::showThat returns whatever its Widget counterpart returns. Notice also how RCWidget declares nocopy constructor, no assignment operator, and no destructor. As with the String class, there is no need to writethese functions. Thanks to the behavior of the RCIPtr class, the default versions do the right things.

Page 383: Effective C++ and More Effective C++

If the thought occurs to you that creation of RCWidget is so mechanical, it could be automated, you're right. Itwould not be difficult to write a program that takes a class like Widget as input and produces a class likeRCWidget as output. If you write such a program, please let me know.

Evaluation

Let us disentangle ourselves from the details of widgets, strings, values, smart pointers, and reference-countingbase classes. That gives us an opportunity to step back and view reference counting in a broader context. In thatmore general context, we must address a higher-level question, namely, when is reference counting anappropriate technique?

Reference-counting implementations are not without cost. Each reference-counted value carries a referencecount with it, and most operations require that this reference count be examined or manipulated in some way.Object values therefore require more memory, and we sometimes execute more code when we work with them.Furthermore, the underlying source code is considerably more complex for a reference-counted class than for aless elaborate implementation. An un-reference-counted string class typically stands on its own, while our finalString class is useless unless it's augmented with three auxiliary classes (StringValue, RCObject, and RCPtr).True, our more complicated design holds out the promise of greater efficiency when values can be shared, iteliminates the need to track object ownership, and it promotes reusability of the reference counting idea andimplementation. Nevertheless, that quartet of classes has to be written, tested, documented, and maintained, andthat's going to be more work than writing, testing, documenting, and maintaining a single class. Even a managercan see that.

Reference counting is an optimization technique predicated on the assumption that objects will commonly sharevalues (see also Item 18). If this assumption fails to hold, reference counting will use more memory than a moreconventional implementation and it will execute more code. On the other hand, if your objects do tend to havecommon values, reference counting should save you both time and space. The bigger your object values and themore objects that can simultaneously share values, the more memory you'll save. The more you copy and assignvalues between objects, the more time you'll save. The more expensive it is to create and destroy a value, themore time you'll save there, too. In short, reference counting is most useful for improving efficiency under thefollowing conditions:

Relatively few values are shared by relatively many objects. Such sharing typically arises throughcalls to assignment operators and copy constructors. The higher the objects/values ratio, the better the casefor reference counting.

Object values are expensive to create or destroy, or they use lots of memory. Even when this is thecase, reference counting still buys you nothing unless these values can be shared by multiple objects.

There is only one sure way to tell whether these conditions are satisfied, and that way is not to guess or rely onyour programmer's intuition (see Item 16). The reliable way to find out whether your program can benefit fromreference counting is to profile or instrument it. That way you can find out if creating and destroying values is aperformance bottleneck, and you can measure the objects/values ratio. Only when you have such data in hand areyou in a position to determine whether the benefits of reference counting (of which there are many) outweigh thedisadvantages (of which there are also many).

Even when the conditions above are satisfied, a design employing reference counting may still be inappropriate.Some data structures (e.g., directed graphs) lead to self-referential or circular dependency structures. Such datastructures have a tendency to spawn isolated collections of objects, used by no one, whose reference countsnever drop to zero. That's because each object in the unused structure is pointed to by at least one other object inthe same structure. Industrial-strength garbage collectors use special techniques to find such structures andeliminate them, but the simple reference-counting approach we've examined here is not easily extended toinclude such techniques.

Reference counting can be attractive even if efficiency is not your primary concern. If you find yourself weigheddown with uncertainty over who's allowed to delete what, reference counting could be just the technique youneed to ease your burden. Many programmers are devoted to reference counting for this reason alone.

Let us close this discussion on a technical note by tying up one remaining loose end. WhenRCObject::removeReference decrements an object's reference count, it checks to see if the new count is 0. If it is, removeReference destroys the object by deleteing this. This is a safe operation only if the object was

Page 384: Effective C++ and More Effective C++

allocated by calling new, so we need some way of ensuring that RCObjects are created only in that manner.

In this case we do it by convention. RCObject is designed for use as a base class of reference-counted valueobjects, and those value objects should be referred to only by smart RCPtr pointers. Furthermore, the valueobjects should be instantiated only by application objects that realize values are being shared; the classesdescribing the value objects should never be available for general use. In our example, the class for valueobjects is StringValue, and we limit its use by making it private in String. Only String can create StringValueobjects, so it is up to the author of the String class to ensure that all such objects are allocated via new.

Our approach to the constraint that RCObjects be created only on the heap, then, is to assign responsibility forconformance to this constraint to a well-defined set of classes and to ensure that only that set of classes cancreate RCObjects. There is no possibility that random clients can accidently (or maliciously) create RCObjectsin an inappropriate manner. We limit the right to create reference-counted objects, and when we do hand out theright, we make it clear that it's accompanied by the concomitant responsibility to follow the rules governingobject creation.

Back to Item 28: Smart pointersContinue to Item 30: Proxy classes

10 The string type in the standard C++ library (see Item E49 and Item 35) uses a combination of solutions twoand three. The reference returned from the non-const operator[] is guaranteed to be valid until the next functioncall that might modify the string. After that, use of the reference (or the character to which it refers) yieldsundefined results. This allows the string's shareability flag to be reset to true whenever a function is called thatmight modify the string. Return

Page 385: Effective C++ and More Effective C++

Back to Item 29: Reference counting Continue to Item 31: Making functions virtual with respect to more than one object

Item 30: Proxy classes.

Though your in-laws may be one-dimensional, the world, in general, is not. Unfortunately, C++ hasn't yet caughton to that fact. At least, there's little evidence for it in the language's support for arrays. You can createtwo-dimensional, three-dimensional ? heck, you can create n-dimensional ? arrays in FORTRAN, in BASIC,even in COBOL (okay, FORTRAN only allows up to seven dimensions, but let's not quibble), but can you do itin C++? Only sometimes, and even then only sort of.

This much is legal: int data[10][20]; // 2D array: 10 by 20

The corresponding construct using variables as dimension sizes, however, is not: void processInput(int dim1, int dim2)

{

int data[dim1][dim2]; // error! array dimensions

... // must be known during

} // compilation

It's not even legal for a heap-based allocation: int *data =

new int[dim1][dim2]; // error!

Implementing Two-Dimensional Arrays

Multidimensional arrays are as useful in C++ as they are in any other language, so it's important to come upwith a way to get decent support for them. The usual way is the standard one in C++: create a class to representthe objects we need but that are missing in the language proper. Hence we can define a class template fortwo-dimensional arrays:

template<class T>

class Array2D {

public:

Array2D(int dim1, int dim2);

...

};

Now we can define the arrays we want: Array2D<int> data(10, 20); // fine

Array2D<float> *data =

new Array2D<float>(10, 20); // fine

void processInput(int dim1, int dim2)

{

Array2D<int> data(dim1, dim2); // fine

...

}

Using these array objects, however, isn't quite as straightforward. In keeping with the grand syntactic traditionof both C and C++, we'd like to be able to use brackets to index into our arrays,

cout << data[3][6];

but how do we declare the indexing operator in Array2D to let us do this?

Page 386: Effective C++ and More Effective C++

Our first impulse might be to declare operator[][] functions, like this: template<class T>

class Array2D {

public:

// declarations that won't compile

T& operator[][](int index1, int index2);

const T& operator[][](int index1, int index2) const;

...

};

We'd quickly learn to rein in such impulses, however, because there is no such thing as operator[][], and don'tthink your compilers will forget it. (For a complete list of operators, overloadable and otherwise, see Item 7.)We'll have to do something else.

If you can stomach the syntax, you might follow the lead of the many programming languages that use parenthesesto index into arrays. To use parentheses, you just overload operator():

template<class T>

class Array2D {

public:

// declarations that will compile

T& operator()(int index1, int index2);

const T& operator()(int index1, int index2) const;

...

};

Clients then use arrays this way: cout << data(3, 6);

This is easy to implement and easy to generalize to as many dimensions as you like. The drawback is that yourArray2D objects don't look like built-in arrays any more. In fact, the above access to element (3, 6) of datalooks, on the face of it, like a function call.

If you reject the thought of your arrays looking like FORTRAN refugees, you might turn again to the notion ofusing brackets as the indexing operator. Although there is no such thing as operator[][], it is nonetheless legal towrite code that appears to use it:

int data[10][20];

...

cout << data[3][6]; // fine

What gives?

What gives is that the variable data is not really a two-dimensional array at all, it's a 10-elementone-dimensional array. Each of those 10 elements is itself a 20-element array, so the expression data[3][6]really means (data[3])[6], i.e., the seventh element of the array that is the fourth element of data. In short, thevalue yielded by the first application of the brackets is another array, so the second application of the bracketsgets an element from that secondary array.

We can play the same game with our Array2D class by overloading operator[] to return an object of a newclass, Array1D. We can then overload operator[] again in Array1D to return an element in our original

Page 387: Effective C++ and More Effective C++

two-dimensional array: template<class T>

class Array2D {

public:

class Array1D {

public:

T& operator[](int index);

const T& operator[](int index) const;

...

};

Array1D operator[](int index);

const Array1D operator[](int index) const;

...

};

The following then becomes legal: Array2D<float> data(10, 20);

...

cout << data[3][6]; // fine

Here, data[3] yields an Array1D object and the operator[] invocation on that object yields the float in position(3, 6) of the original two-dimensional array.

Clients of the Array2D class need not be aware of the presence of the Array1D class. Objects of this latter classstand for one-dimensional array objects that, conceptually, do not exist for clients of Array2D. Such clientsprogram as if they were using real, live, honest-to-Allah two-dimensional arrays. It is of no concern to Array2Dclients that those objects must, in order to satisfy the vagaries of C++, be syntactically compatible withone-dimensional arrays of other one-dimensional arrays.

Each Array1D object stands for a one-dimensional array that is absent from the conceptual model used byclients of Array2D. Objects that stand for other objects are often called proxy objects, and the classes that giverise to proxy objects are often called proxy classes. In this example, Array1D is a proxy class. Its instancesstand for one-dimensional arrays that, conceptually, do not exist. (The terminology for proxy objects and classesis far from universal; objects of such classes are also sometimes known as surrogates.)

Distinguishing Reads from Writes via operator[]

The use of proxies to implement classes whose instances act like multidimensional arrays is common, but proxyclasses are more flexible than that. Item 5, for example, shows how proxy classes can be employed to preventsingle-argument constructors from being used to perform unwanted type conversions. Of the varied uses of proxyclasses, however, the most heralded is that of helping distinguish reads from writes through operator[].

Consider a reference-counted string type that supports operator[]. Such a type is examined in detail in Item 29. Ifthe concepts behind reference counting have slipped your mind, it would be a good idea to familiarize yourselfwith the material in that Item now.

A string type supporting operator[] allows clients to write code like this:

String s1, s2; // a string-like class; the

// use of proxies keeps this

// class from conforming to

// the standard string

... // interface

cout << s1[5]; // read s1

Page 388: Effective C++ and More Effective C++

s2[5] = 'x'; // write s2

s1[3] = s2[8]; // write s1, read s2

Note that operator[] can be called in two different contexts: to read a character or to write a character. Readsare known as rvalue usages; writes are known as lvalue usages. (The terms come from the field of compilers,where an lvalue goes on the left-hand side of an assignment and an rvalue goes on the right-hand side.) Ingeneral, using an object as an lvalue means using it such that it might be modified, and using it as an rvaluemeans using it such that it cannot be modified.

We'd like to distinguish between lvalue and rvalue usage of operator[] because, especially forreference-counted data structures, reads can be much less expensive to implement than writes. As Item 29 explains, writes of reference-counted objects may involve copying an entire data structure, but reads never requiremore than the simple returning of a value. Unfortunately, inside operator[], there is no way to determine thecontext in which the function was called; it is not possible to distinguish lvalue usage from rvalue usage withinoperator[].

"But wait," you say, "we don't need to. We can overload operator[] on the basis of its constness, and that willallow us to distinguish reads from writes." In other words, you suggest we solve our problem this way:

class String {

public:

const char& operator[](int index) const; // for reads

char& operator[](int index); // for writes

...

};

Alas, this won't work. Compilers choose between const and non-const member functions by looking only atwhether the object invoking a function is const. No consideration is given to the context in which a call is made.Hence:

String s1, s2;

...

cout << s1[5]; // calls non-const operator[],

// because s1 isn't const

s2[5] = 'x'; // also calls non-const

// operator[]: s2 isn't const

s1[3] = s2[8]; // both calls are to non-const

// operator[], because both s1

// and s2 are non-const objects

Overloading operator[], then, fails to distinguish reads from writes.

In Item 29, we resigned ourselves to this unsatisfactory state of affairs and made the conservative assumptionthat all calls to operator[] were for writes. This time we shall not give up so easily. It may be impossible todistinguish lvalue from rvalue usage inside operator[], but we still want to do it. We will therefore find a way.What fun is life if you allow yourself to be limited by the possible?

Our approach is based on the fact that though it may be impossible to tell whether operator[] is being invoked inan lvalue or an rvalue context from within operator[], we can still treat reads differently from writes if we delayour lvalue-versus-rvalue actions until we see how the result of operator[] is used. All we need is a way topostpone our decision on whether our object is being read or written until after operator[] has returned. (This isan example of lazy evaluation ? see Item 17.)

Page 389: Effective C++ and More Effective C++

A proxy class allows us to buy the time we need, because we can modify operator[] to return a proxy for a stringcharacter instead of a string character itself. We can then wait to see how the proxy is used. If it's read, we canbelatedly treat the call to operator[] as a read. If it's written, we must treat the call to operator[] as a write.

We will see the code for this in a moment, but first it is important to understand the proxies we'll be using. Thereare only three things you can do with a proxy:

Create it, i.e., specify which string character it stands for. Use it as the target of an assignment, in which case you are really making an assignment to the string

character it stands for. When used in this way, a proxy represents an lvalue use of the string on whichoperator[] was invoked.

Use it in any other way. When used like this, a proxy represents an rvalue use of the string on whichoperator[] was invoked.

Here are the class definitions for a reference-counted String class using a proxy class to distinguish betweenlvalue and rvalue usages of operator[]:

class String { // reference-counted strings;

public: // see Item 29 for details

class CharProxy { // proxies for string chars

public:

CharProxy(String& str, int index); // creation

CharProxy& operator=(const CharProxy& rhs); // lvalue

CharProxy& operator=(char c); // uses

operator char() const; // rvalue

// use

private:

String& theString; // string this proxy pertains to

int charIndex; // char within that string

// this proxy stands for

};

// continuation of String class

const CharProxy

operator[](int index) const; // for const Strings

CharProxy operator[](int index); // for non-const Strings

...

friend class CharProxy;

private:

RCPtr<StringValue> value;

};

Other than the addition of the CharProxy class (which we'll examine below), the only difference between thisString class and the final String class in Item 29 is that both operator[] functions now return CharProxy objects.Clients of String can generally ignore this, however, and program as if the operator[] functions returnedcharacters (or references to characters ? see Item 1) in the usual manner:

String s1, s2; // reference-counted strings

// using proxies

...

cout << s1[5]; // still legal, still works

s2[5] = 'x'; // also legal, also works

Page 390: Effective C++ and More Effective C++

s1[3] = s2[8]; // of course it's legal,

// of course it works

What's interesting is not that this works. What's interesting is how it works.

Consider first this statement: cout << s1[5];

The expression s1[5] yields a CharProxy object. No output operator is defined for such objects, so yourcompilers labor to find an implicit type conversion they can apply to make the call to operator<< succeed (see Item 5). They find one: the implicit conversion from CharProxy to char declared in the CharProxy class. Theyautomatically invoke this conversion operator, and the result is that the string character represented by theCharProxy is printed. This is representative of the CharProxy-to-char conversion that takes place for allCharProxy objects used as rvalues.

Lvalue usage is handled differently. Look again at s2[5] = 'x';

As before, the expression s2[5] yields a CharProxy object, but this time that object is the target of anassignment. Which assignment operator is invoked? The target of the assignment is a CharProxy, so theassignment operator that's called is in the CharProxy class. This is crucial, because inside a CharProxyassignment operator, we know that the CharProxy object being assigned to is being used as an lvalue. Wetherefore know that the string character for which the proxy stands is being used as an lvalue, and we must takewhatever actions are necessary to implement lvalue access for that character.

Similarly, the statement s1[3] = s2[8];

calls the assignment operator for two CharProxy objects, and inside that operator we know the object on the leftis being used as an lvalue and the object on the right as an rvalue.

"Yeah, yeah, yeah," you grumble, "show me." Okay. Here's the code for String's operator[] functions: const String::CharProxy String::operator[](int index) const

{

return CharProxy(const_cast<String&>(*this), index);

}

String::CharProxy String::operator[](int index)

{

return CharProxy(*this, index);

}

Each function just creates and returns a proxy for the requested character. No action is taken on the characteritself: we defer such action until we know whether the access is for a read or a write.

Note that the const version of operator[] returns a const proxy. Because CharProxy::operator= isn't a constmember function, such proxies can't be used as the target of assignments. Hence neither the proxy returned fromthe const version of operator[] nor the character for which it stands may be used as an lvalue. Convenientlyenough, that's exactly the behavior we want for the const version of operator[].

Note also the use of a const_cast (see Item 2) on *this when creating the CharProxy object that the constoperator[] returns. That's necessary to satisfy the constraints of the CharProxy constructor, which accepts only anon-const String. Casts are usually worrisome, but in this case the CharProxy object returned by operator[] isitself const, so there is no risk the String containing the character to which the proxy refers will be modified.

Each proxy returned by an operator[] function remembers which string it pertains to and, within that string, the

Page 391: Effective C++ and More Effective C++

index of the character it represents: String::CharProxy::CharProxy(String& str, int index)

: theString(str), charIndex(index) {}

Conversion of a proxy to an rvalue is straightforward ? we just return a copy of the character represented by theproxy:

String::CharProxy::operator char() const

{

return theString.value->data[charIndex];

}

If you've forgotten the relationship among a String object, its value member, and the data member it points to,you can refresh your memory by turning to Item 29. Because this function returns a character by value, andbecause C++ limits the use of such by-value returns to rvalue contexts only, this conversion function can be usedonly in places where an rvalue is legal.

We thus turn to implementation of CharProxy's assignment operators, which is where we must deal with the factthat a character represented by a proxy is being used as the target of an assignment, i.e., as an lvalue. We canimplement CharProxy's conventional assignment operator as follows:

String::CharProxy&

String::CharProxy::operator=(const CharProxy& rhs)

{

// if the string is sharing a value with other String objects,

// break off a separate copy of the value for this string only

if (theString.value->isShared()) {

theString.value = new StringValue(theString.value->data);

}

// now make the assignment: assign the value of the char

// represented by rhs to the char represented by *this

theString.value->data[charIndex] =

rhs.theString.value->data[rhs.charIndex];

return *this;

}

If you compare this with the implementation of the non-const String::operator in Item 29, you'll see that they arestrikingly similar. This is to be expected. In Item 29, we pessimistically assumed that all invocations of thenon-const operator[] were writes, so we treated them as such. Here, we moved the code implementing a writeinto CharProxy's assignment operators, and that allows us to avoid paying for a write when the non-constoperator[] is used only in an rvalue context. Note, by the way, that this function requires access to String'sprivate data member value. That's why CharProxy is declared a friend in the earlier class definition for String.

The second CharProxy assignment operator is almost identical: String::CharProxy& String::CharProxy::operator=(char c)

{

if (theString.value->isShared()) {

theString.value = new StringValue(theString.value->data);

}

theString.value->data[charIndex] = c;

return *this;

}

As an accomplished software engineer, you would, of course, banish the code duplication present in these twoassignment operators to a private CharProxy member function that both would call. Aren't you the modular one?

Page 392: Effective C++ and More Effective C++

Limitations

The use of a proxy class is a nice way to distinguish lvalue and rvalue usage of operator[], but the technique isnot without its drawbacks. We'd like proxy objects to seamlessly replace the objects they stand for, but thisideal is difficult to achieve. That's because objects are used as lvalues in contexts other than just assignment, andusing proxies in such contexts usually yields behavior different from using real objects.

Consider again the code fragment from Item 29 that motivated our decision to add a shareability flag to eachStringValue object. If String::operator[] returns a CharProxy instead of a char&, that code will no longercompile:

String s1 = "Hello";

char *p = &s1[1]; // error!

The expression s1[1] returns a CharProxy, so the type of the expression on the right-hand side of the "=" isCharProxy*. There is no conversion from a CharProxy* to a char*, so the initialization of p fails to compile. Ingeneral, taking the address of a proxy yields a different type of pointer than does taking the address of a realobject.

To eliminate this difficulty, you'll need to overload the address-of operators for the CharProxy class: class String {

public:

class CharProxy {

public:

...

char * operator&();

const char * operator&() const;

...

};

...

};

These functions are easy to implement. The const function just returns a pointer to a const version of thecharacter represented by the proxy:

const char * String::CharProxy::operator&() const

{

return &(theString.value->data[charIndex]);

}

The non-const function is a bit more work, because it returns a pointer to a character that may be modified. Thisis analogous to the behavior of the non-const version of String::operator[] in Item 29, and the implementation isequally analogous:

char * String::CharProxy::operator&()

{

// make sure the character to which this function returns

// a pointer isn't shared by any other String objects

if (theString.value->isShared()) {

theString.value = new StringValue(theString.value->data);

}

// we don't know how long the pointer this function

// returns will be kept by clients, so the StringValue

// object can never be shared

theString.value->markUnshareable();

return &(theString.value->data[charIndex]);

}

Page 393: Effective C++ and More Effective C++

Much of this code is common to other CharProxy member functions, so I know you'd encapsulate it in a privatemember function that all would call.

A second difference between chars and the CharProxys that stand for them becomes apparent if we have atemplate for reference-counted arrays that use proxy classes to distinguish lvalue and rvalue invocations ofoperator[]:

template<class T> // reference-counted array

class Array { // using proxies

public:

class Proxy {

public:

Proxy(Array<T>& array, int index);

Proxy& operator=(const T& rhs);

operator T() const;

...

};

const Proxy operator[](int index) const;

Proxy operator[](int index);

...

};

Consider how these arrays might be used: Array<int> intArray;

...

intArray[5] = 22; // fine

intArray[5] += 5; // error!

++intArray[5]; // error!

As expected, use of operator[] as the target of a simple assignment succeeds, but use of operator[] on theleft-hand side of a call to operator+= or operator++ fails. That's because operator[] returns a proxy, and there isno operator+= or operator++ for Proxy objects. A similar situation exists for other operators that requirelvalues, including operator*=, operator<<=, operator--, etc. If you want these operators to work with operator[]functions that return proxies, you must define each of these functions for the Array<T>::Proxy class. That's a lotof work, and you probably don't want to do it. Unfortunately, you either do the work or you do without. Them'sthe breaks.

A related problem has to do with invoking member functions on real objects through proxies. To be blunt aboutit, you can't. For example, suppose we'd like to work with reference-counted arrays of rational numbers. Wecould define a class Rational and then use the Array template we just saw:

class Rational {

public:

Rational(int numerator = 0, int denominator = 1);

int numerator() const;

int denominator() const;

...

};

Array<Rational> array;

This is how we'd expect to be able to use such arrays, but, alas, we'd be disappointed: cout << array[4].numerator(); // error!

int denom = array[22].denominator(); // error!

Page 394: Effective C++ and More Effective C++

By now the difficulty is predictable; operator[] returns a proxy for a rational number, not an actual Rationalobject. But the numerator and denominator member functions exist only for Rationals, not their proxies. Hencethe complaints by your compilers. To make proxies behave like the objects they stand for, you must overloadeach function applicable to the real objects so it applies to proxies, too.

Yet another situation in which proxies fail to replace real objects is when being passed to functions that takereferences to non-const objects:

void swap(char& a, char& b); // swaps the value of a and b

String s = "+C+"; // oops, should be "C++"

swap(s[0], s[1]); // this should fix the

// problem, but it won't

// compile

String::operator[] returns a CharProxy, but swap demands that its arguments be of type char&. A CharProxymay be implicitly converted into a char, but there is no conversion function to a char&. Furthermore, the char towhich it may be converted can't be bound to swap's char& parameters, because that char is a temporary object(it's operator char's return value) and, as Item 19 explains, there are good reasons for refusing to bind temporaryobjects to non-const reference parameters.

A final way in which proxies fail to seamlessly replace real objects has to do with implicit type conversions.When a proxy object is implicitly converted into the real object it stands for, a user-defined conversion functionis invoked. For instance, a CharProxy can be converted into the char it stands for by calling operator char. As Item 5 explains, compilers may use only one user-defined conversion function when converting a parameter at acall site into the type needed by the corresponding function parameter. As a result, it is possible for functioncalls that succeed when passed real objects to fail when passed proxies. For example, suppose we have aTVStation class and a function, watchTV:

class TVStation {

public:

TVStation(int channel);

...

};

void watchTV(const TVStation& station, float hoursToWatch);

Thanks to implicit type conversion from int to TVStation (see Item 5), we could then do this:

watchTV(10, 2.5); // watch channel 10 for

// 2.5 hours

Using the template for reference-counted arrays that use proxy classes to distinguish lvalue and rvalueinvocations of operator[], however, we could not do this:

Array<int> intArray;

intArray[4] = 10;

watchTV(intArray[4], 2.5); // error! no conversion

// from Proxy<int> to

// TVStation

Given the problems that accompany implicit type conversions, it's hard to get too choked up about this. In fact, abetter design for the TVStation class would declare its constructor explicit, in which case even the first call to

Page 395: Effective C++ and More Effective C++

watchTV would fail to compile. For all the details on implicit type conversions and how explicit affects them,see Item 5.

Evaluation

Proxy classes allow you to achieve some types of behavior that are otherwise difficult or impossible toimplement. Multidimensional arrays are one example, lvalue/rvalue differentiation is a second, suppression ofimplicit conversions (see Item 5) is a third.

At the same time, proxy classes have disadvantages. As function return values, proxy objects are temporaries(see Item 19), so they must be created and destroyed. That's not free, though the cost may be more than recoupedthrough their ability to distinguish write operations from read operations. The very existence of proxy classesincreases the complexity of software systems that employ them, because additional classes make things harder todesign, implement, understand, and maintain, not easier.

Finally, shifting from a class that works with real objects to a class that works with proxies often changes thesemantics of the class, because proxy objects usually exhibit behavior that is subtly different from that of the realobjects they represent. Sometimes this makes proxies a poor choice when designing a system, but in many casesthere is little need for the operations that would make the presence of proxies apparent to clients. For instance,few clients will want to take the address of an Array1D object in the two-dimensional array example we saw atthe beginning of this Item, and there isn't much chance that an ArrayIndex object (see Item 5) would be passed toa function expecting a different type. In many cases, proxies can stand in for real objects perfectly acceptably.When they can, it is often the case that nothing else will do.

Back to Item 29: Reference counting Continue to Item 31: Making functions virtual with respect to more than one object

Page 396: Effective C++ and More Effective C++

Back to Item 30: Proxy classes Continue to Miscellany

Item 31: Making functions virtual with respect to more than one object.

Sometimes, to borrow a phrase from Jacqueline Susann, once is not enough. Suppose, for example, you'rebucking for one of those high-profile, high-prestige, high-paying programming jobs at that famous softwarecompany in Redmond, Washington ? by which of course I mean Nintendo. To bring yourself to the attention ofNintendo's management, you might decide to write a video game. Such a game might take place in outer spaceand involve space ships, space stations, and asteroids.

As the ships, stations, and asteroids whiz around in your artificial world, they naturally run the risk of collidingwith one another. Let's assume the rules for such collisions are as follows:

If a ship and a station collide at low velocity, the ship docks at the station. Otherwise the ship and thestation sustain damage that's proportional to the speed at which they collide.

If a ship and a ship or a station and a station collide, both participants in the collision sustain damagethat's proportional to the speed at which they hit.

If a small asteroid collides with a ship or a station, the asteroid is destroyed. If it's a big asteroid, the shipor the station is destroyed.

If an asteroid collides with another asteroid, both break into pieces and scatter little baby asteroids in alldirections.

This may sound like a dull game, but it suffices for our purpose here, which is to consider how to structure theC++ code that handles collisions between objects.

We begin by noting that ships, stations, and asteroids share some common features. If nothing else, they're all inmotion, so they all have a velocity that describes that motion. Given this commonality, it is natural to define abase class from which they all inherit. In practice, such a class is almost invariably an abstract base class, and,if you heed the warning I give in Item 33, base classes are always abstract. The hierarchy might therefore looklike this:

class GameObject { ... };

Page 397: Effective C++ and More Effective C++

class SpaceShip: public GameObject { ... };

class SpaceStation: public GameObject { ... };

class Asteroid: public GameObject { ... };

Now, suppose you're deep in the bowels of your program, writing the code to check for and handle objectcollisions. You might come up with a function that looks something like this:

void checkForCollision(GameObject& object1,

GameObject& object2)

{

if (theyJustCollided(object1, object2)) {

processCollision(object1, object2);

}

else {

...

}

}

This is where the programming challenge becomes apparent. When you call processCollision, you know thatobject1 and object2 just collided, and you know that what happens in that collision depends on what object1really is and what object2 really is, but you don't know what kinds of objects they really are; all you know is thatthey're both GameObjects. If the collision processing depended only on the dynamic type of object1, you couldmake processCollision virtual in GameObject and call object1.processCollision(object2). You could do thesame thing with object2 if the details of the collision depended only on its dynamic type. What happens in thecollision, however, depends on both their dynamic types. A function call that's virtual on only one object, yousee, is not enough.

What you need is a kind of function whose behavior is somehow virtual on the types of more than one object.C++ offers no such function. Nevertheless, you still have to implement the behavior required above. Thequestion, then, is how you are going to do it.

One possibility is to scrap the use of C++ and choose another programming language. You could turn to CLOS,for example, the Common Lisp Object System. CLOS supports what is possibly the most general object-orientedfunction-invocation mechanism one can imagine: multi-methods. A multi-method is a function that's virtual on asmany parameters as you'd like, and CLOS goes even further by giving you substantial control over how calls tooverloaded multi-methods are resolved.

Let us assume, however, that you must implement your game in C++ ? that you must come up with your own wayof implementing what is commonly referred to as double-dispatching. (The name comes from theobject-oriented programming community, where what C++ programmers know as a virtual function call istermed a "message dispatch." A call that's virtual on two parameters is implemented through a "doubledispatch." The generalization of this ? a function acting virtual on several parameters ? is called multipledispatch.) There are several approaches you might consider. None is without its disadvantages, but thatshouldn't surprise you. C++ offers no direct support for double-dispatching, so you must yourself do the workcompilers do when they implement virtual functions (see Item 24). If that were easy to do, we'd probably all bedoing it ourselves and simply programming in C. We aren't and we don't, so fasten your seat belts, it's going tobe a bumpy ride.

Using Virtual Functions and RTTI

Virtual functions implement a single dispatch; that's half of what we need; and compilers do virtual functions forus, so we begin by declaring a virtual function collide in GameObject. This function is overridden in the derivedclasses in the usual manner:

class GameObject {

public:

virtual void collide(GameObject& otherObject) = 0;

...

Page 398: Effective C++ and More Effective C++

};

class SpaceShip: public GameObject {

public:

virtual void collide(GameObject& otherObject);

...

};

Here I'm showing only the derived class SpaceShip, but SpaceStation and Asteroid are handled in exactly thesame manner.

The most common approach to double-dispatching returns us to the unforgiving world of virtual functionemulation via chains of if-then-elses. In this harsh world, we first discover the real type of otherObject, then wetest it against all the possibilities:

// if we collide with an object of unknown type, we

// throw an exception of this type:

class CollisionWithUnknownObject {

public:

CollisionWithUnknownObject(GameObject& whatWeHit);

...

};

void SpaceShip::collide(GameObject& otherObject)

{

const type_info& objectType = typeid(otherObject);

if (objectType == typeid(SpaceShip)) {

SpaceShip& ss = static_cast<SpaceShip&>(otherObject);

process a SpaceShip-SpaceShip collision;

}

else if (objectType == typeid(SpaceStation)) {

SpaceStation& ss =

static_cast<SpaceStation&>(otherObject);

process a SpaceShip-SpaceStation collision;

}

else if (objectType == typeid(Asteroid)) {

Asteroid& a = static_cast<Asteroid&>(otherObject);

process a SpaceShip-Asteroid collision;

}

else {

throw CollisionWithUnknownObject(otherObject);

}

}

Notice how we need to determine the type of only one of the objects involved in the collision. The other objectis *this, and its type is determined by the virtual function mechanism. We're inside a SpaceShip memberfunction, so *this must be a SpaceShip object. Thus we only have to figure out the real type of otherObject.

There's nothing complicated about this code. It's easy to write. It's even easy to make work. That's one of thereasons RTTI is worrisome: it looks harmless. The true danger in this code is hinted at only by the final elseclause and the exception that's thrown there.

Page 399: Effective C++ and More Effective C++

We've pretty much bidden adios to encapsulation, because each collide function must be aware of each of itssibling classes, i.e., those classes that inherit from GameObject. In particular, if a new type of object ? a newclass ? is added to the game, we must update each RTTI-based if-then-else chain in the program that mightencounter the new object type. If we forget even a single one, the program will have a bug, and the bug will notbe obvious. Furthermore, compilers are in no position to help us detect such an oversight, because they have noidea what we're doing (see also Item E39).

This kind of type-based programming has a long history in C, and one of the things we know about it is that ityields programs that are essentially unmaintainable. Enhancement of such programs eventually becomesunthinkable. This is the primary reason why virtual functions were invented in the first place: to shift the burdenof generating and maintaining type-based function calls from programmers to compilers. When we employ RTTIto implement double-dispatching, we are harking back to the bad old days.

The techniques of the bad old days led to errors in C, and they'll lead to errors in C++, too. In recognition of ourhuman frailty, we've included a final else clause in the collide function, a clause where control winds up if wehit an object we don't know about. Such a situation is, in principle, impossible, but where were our principleswhen we decided to use RTTI? There are various ways to handle such unanticipated interactions, but none isvery satisfying. In this case, we've chosen to throw an exception, but it's not clear how our callers can hope tohandle the error any better than we can, since we've just run into something we didn't know existed.

Using Virtual Functions Only

There is a way to minimize the risks inherent in an RTTI approach to implementing double-dispatching, butbefore we look at that, it's convenient to see how to attack the problem using nothing but virtual functions. Thatstrategy begins with the same basic structure as the RTTI approach. The collide function is declared virtual inGameObject and is redefined in each derived class. In addition, collide is overloaded in each class, oneoverloading for each derived class in the hierarchy:

class SpaceShip; // forward declarations

class SpaceStation;

class Asteroid;

class GameObject {

public:

virtual void collide(GameObject& otherObject) = 0;

virtual void collide(SpaceShip& otherObject) = 0;

virtual void collide(SpaceStation& otherObject) = 0;

virtual void collide(Asteroid& otherobject) = 0;

...

};

class SpaceShip: public GameObject {

public:

virtual void collide(GameObject& otherObject);

virtual void collide(SpaceShip& otherObject);

virtual void collide(SpaceStation& otherObject);

virtual void collide(Asteroid& otherobject);

...

};

The basic idea is to implement double-dispatching as two single dispatches, i.e., as two separate virtualfunction calls: the first determines the dynamic type of the first object, the second determines that of the secondobject. As before, the first virtual call is to the collide function taking a GameObject& parameter. Thatfunction's implementation now becomes startlingly simple:

void SpaceShip::collide(GameObject& otherObject)

{

otherObject.collide(*this);

}

Page 400: Effective C++ and More Effective C++

At first glance, this appears to be nothing more than a recursive call to collide with the order of the parametersreversed, i.e., with otherObject becoming the object calling the member function and *this becoming thefunction's parameter. Glance again, however, because this is not a recursive call. As you know, compilers figureout which of a set of functions to call on the basis of the static types of the arguments passed to the function. Inthis case, four different collide functions could be called, but the one chosen is based on the static type of *this.What is that static type? Being inside a member function of the class SpaceShip, *this must be of typeSpaceShip. The call is therefore to the collide function taking a SpaceShip&, not the collide function taking aGameObject&.

All the collide functions are virtual, so the call inside SpaceShip::collide resolves to the implementation ofcollide corresponding to the real type of otherObject. Inside that implementation of collide, the real types ofboth objects are known, because the left-hand object is *this (and therefore has as its type the class implementing the member function) and the right-hand object's real type is SpaceShip, the same as the declared type ofthe parameter.

All this may be clearer when you see the implementations of the other collide functions in SpaceShip: void SpaceShip::collide(SpaceShip& otherObject)

{

process a SpaceShip-SpaceShip collision;

}

void SpaceShip::collide(SpaceStation& otherObject)

{

process a SpaceShip-SpaceStation collision;

}

void SpaceShip::collide(Asteroid& otherObject)

{

process a SpaceShip-Asteroid collision;

}

As you can see, there's no muss, no fuss, no RTTI, no need to throw exceptions for unexpected object types.There can be no unexpected object types ? that's the whole point of using virtual functions. In fact, were it not forits fatal flaw, this would be the perfect solution to the double-dispatching problem.

The flaw is one it shares with the RTTI approach we saw earlier: each class must know about its siblings. Asnew classes are added, the code must be updated. However, the way in which the code must be updated isdifferent in this case. True, there are no if-then-elses to modify, but there is something that is often worse: eachclass definition must be amended to include a new virtual function. If, for example, you decide to add a newclass Satellite (inheriting from GameObject) to your game, you'd have to add a new collide function to each ofthe existing classes in the program.

Modifying existing classes is something you are frequently in no position to do. If, instead of writing the entirevideo game yourself, you started with an off-the-shelf class library comprising a video game applicationframework, you might not have write access to the GameObject class or the framework classes derived from it.In that case, adding new member functions, virtual or otherwise, is not an option. Alternatively, you may have physical access to the classes requiring modification, but you may not have practical access. For example,suppose you were hired by Nintendo and were put to work on programs using a library containing GameObjectand other useful classes. Surely you wouldn't be the only one using that library, and Nintendo would probably beless than thrilled about recompiling every application using that library each time you decided to add a new typeof object to your program. In practice, libraries in wide use are modified only rarely, because the cost ofrecompiling everything using those libraries is too great. (See Item E34 for information on how to designlibraries that minimize compilation dependencies.)

The long and short of it is if you need to implement double-dispatching in your program, your best recourse is tomodify your design to eliminate the need. Failing that, the virtual function approach is safer than the RTTIstrategy, but it constrains the extensibility of your system to match that of your ability to edit header files. TheRTTI approach, on the other hand, makes no recompilation demands, but, if implemented as shown above, itgenerally leads to software that is unmaintainable. You pays your money and you takes your chances.

Page 401: Effective C++ and More Effective C++

Emulating Virtual Function Tables

There is a way to improve those chances. You may recall from Item 24 that compilers typically implementvirtual functions by creating an array of function pointers (the vtbl) and then indexing into that array when avirtual function is called. Using a vtbl eliminates the need for compilers to perform chains of if-then-else-likecomputations, and it allows compilers to generate the same code at all virtual function call sites: determine thecorrect vtbl index, then call the function pointed to at that position in the vtbl.

There is no reason you can't do this yourself. If you do, you not only make your RTTI-based code more efficient(indexing into an array and following a function pointer is almost always more efficient than running through aseries of if-then-else tests, and it generates less code, too), you also isolate the use of RTTI to a single location:the place where your array of function pointers is initialized. I should mention that the meek may inherit theearth, but the meek of heart may wish to take a few deep breaths before reading what follows.

We begin by making some modifications to the functions in the GameObject hierarchy: class GameObject {

public:

virtual void collide(GameObject& otherObject) = 0;

...

};

class SpaceShip: public GameObject {

public:

virtual void collide(GameObject& otherObject);

virtual void hitSpaceShip(SpaceShip& otherObject);

virtual void hitSpaceStation(SpaceStation& otherObject);

virtual void hitAsteroid(Asteroid& otherobject);

...

};

void SpaceShip::hitSpaceShip(SpaceShip& otherObject)

{

process a SpaceShip-SpaceShip collision;

}

void SpaceShip::hitSpaceStation(SpaceStation& otherObject)

{

process a SpaceShip-SpaceStation collision;

}

void SpaceShip::hitAsteroid(Asteroid& otherObject)

{

process a SpaceShip-Asteroid collision;

}

Like the RTTI-based hierarchy we started out with, the GameObject class contains only one function forprocessing collisions, the one that performs the first of the two necessary dispatches. Like thevirtual-function-based hierarchy we saw later, each kind of interaction is encapsulated in a separate function,though in this case the functions have different names instead of sharing the name collide. There is a reason forthis abandonment of overloading, and we shall see it soon. For the time being, note that the design abovecontains everything we need except an implementation for SpaceShip::collide; that's where the various hitfunctions will be invoked. As before, once we successfully implement the SpaceShip class, the SpaceStationand Asteroid classes will follow suit.

Inside SpaceShip::collide, we need a way to map the dynamic type of the parameter otherObject to a memberfunction pointer that points to the appropriate collision-handling function. An easy way to do this is to create anassociative array that, given a class name, yields the appropriate member function pointer. It's possible toimplement collide using such an associative array directly, but it's a bit easier to understand what's going on ifwe add an intervening function, lookup, that takes a GameObject and returns the appropriate member functionpointer. That is, you pass lookup a GameObject, and it returns a pointer to the member function to call when you

Page 402: Effective C++ and More Effective C++

collide with something of that GameObject's type.

Here's the declaration of lookup: class SpaceShip: public GameObject {

private:

typedef void (SpaceShip::*HitFunctionPtr)(GameObject&);

static HitFunctionPtr lookup(const GameObject& whatWeHit);

...

};

The syntax of function pointers is never very pretty, and for member function pointers it's worse than usual, sowe've typedefed HitFunctionPtr to be shorthand for a pointer to a member function of SpaceShip that takes aGameObject& and returns nothing.

Once we've got lookup, implementation of collide becomes the proverbial piece of cake: void SpaceShip::collide(GameObject& otherObject)

{

HitFunctionPtr hfp =

lookup(otherObject); // find the function to call

if (hfp) { // if a function was found

(this->*hfp)(otherObject); // call it

}

else {

throw CollisionWithUnknownObject(otherObject);

}

}

Provided we've kept the contents of our associative array in sync with the class hierarchy under GameObject,lookup must always find a valid function pointer for the object we pass it. People are people, however, andmistakes have been known to creep into even the most carefully crafted software systems. That's why we stillcheck to make sure a valid pointer was returned from lookup, and that's why we still throw an exception if theimpossible occurs and the lookup fails.

All that remains now is the implementation of lookup. Given an associative array that maps from object types tomember function pointers, the lookup itself is easy, but creating, initializing, and destroying the associative arrayis an interesting problem of its own.

Such an array should be created and initialized before it's used, and it should be destroyed when it's no longerneeded. We could use new and delete to create and destroy the array manually, but that would be error-prone:how could we guarantee the array wasn't used before we got around to initializing it? A better solution is to havecompilers automate the process, and we can do that by making the associative array static in lookup. That way itwill be created and initialized the first time lookup is called, and it will be automatically destroyed sometimeafter main is exited (see Item E47).

Furthermore, we can use the map template from the Standard Template Library (see Item 35) as the associativearray, because that's what a map is:

class SpaceShip: public GameObject {

private:

typedef void (SpaceShip::*HitFunctionPtr)(GameObject&);

typedef map<string, HitFunctionPtr> HitMap;

...

};

SpaceShip::HitFunctionPtr

SpaceShip::lookup(const GameObject& whatWeHit)

{

static HitMap collisionMap;

Page 403: Effective C++ and More Effective C++

...

}

Here, collisionMap is our associative array. It maps the name of a class (as a string object) to a SpaceShipmember function pointer. Because map<string, HitFunctionPtr> is quite a mouthful, we use a typedef to make iteasier to swallow. (For fun, try writing the declaration of collisionMap without using the HitMap andHitFunctionPtr typedefs. Most people will want to do this only once.)

Given collisionMap, the implementation of lookup is rather anticlimactic. That's because searching forsomething is an operation directly supported by the map class, and the one member function we can always(portably) call on the result of a typeid invocation is name (which, predictably11, yields the name of theobject's dynamic type). To implement lookup, then, we just find the entry in collisionMap corresponding to thedynamic type of lookup's argument.

The code for lookup is straightforward, but if you're not familiar with the Standard Template Library (again, see Item 35), it may not seem that way. Don't worry. The comments in the function explain what's going on.

SpaceShip::HitFunctionPtr

SpaceShip::lookup(const GameObject& whatWeHit)

{

static HitMap collisionMap; // we'll see how to

// initialize this below

// look up the collision-processing function for the type

// of whatWeHit. The value returned is a pointer-like

// object called an "iterator" (see Item 35).

HitMap::iterator mapEntry=

collisionMap.find(typeid(whatWeHit).name());

// mapEntry == collisionMap.end() if the lookup failed;

// this is standard map behavior. Again, see Item 35.

if (mapEntry == collisionMap.end()) return 0;

// If we get here, the search succeeded. mapEntry

// points to a complete map entry, which is a

// (string, HitFunctionPtr) pair. We want only the

// second part of the pair, so that's what we return.

return (*mapEntry).second;

}

The final statement in the function returns (*mapEntry).second instead of the more conventionalmapEntry->second in order to satisfy the vagaries of the STL. For details, see page 96.

Initializing Emulated Virtual Function Tables

Which brings us to the initialization of collisionMap. We'd like to say something like this, // An incorrect implementation

SpaceShip::HitFunctionPtr

SpaceShip::lookup(const GameObject& whatWeHit)

{

static HitMap collisionMap;

collisionMap["SpaceShip"] = &hitSpaceShip;

collisionMap["SpaceStation"] = &hitSpaceStation;

collisionMap["Asteroid"] = &hitAsteroid;

...

}

but this inserts the member function pointers into collisionMap each time lookup is called, and that's needlessly

Page 404: Effective C++ and More Effective C++

inefficient. In addition, this won't compile, but that's a secondary problem we'll address shortly.

What we need now is a way to put the member function pointers into collisionMap only once ? whencollisionMap is created. That's easy enough to accomplish; we just write a private static member function calledinitializeCollisionMap to create and initialize our map, then we initialize collisionMap withinitializeCollisionMap's return value:

class SpaceShip: public GameObject {

private:

static HitMap initializeCollisionMap();

...

};

SpaceShip::HitFunctionPtr

SpaceShip::lookup(const GameObject& whatWeHit)

{

static HitMap collisionMap = initializeCollisionMap();

...

}

But this means we may have to pay the cost of copying the map object returned from initializeCollisionMap intocollisionMap (see Items 19 and 20). We'd prefer not to do that. We wouldn't have to pay ifinitializeCollisionMap returned a pointer, but then we'd have to worry about making sure the map object thepointer pointed to was destroyed at an appropriate time.

Fortunately, there's a way for us to have it all. We can turn collisionMap into a smart pointer (see Item 28) thatautomatically deletes what it points to when the pointer itself is destroyed. In fact, the standard C++ librarycontains a template, auto_ptr, for just such a smart pointer (see Item 9). By making collisionMap a staticauto_ptr in lookup, we can have initializeCollisionMap return a pointer to an initialized map object, yet neverhave to worry about a resource leak; the map to which collisionMap points will be automatically destroyedwhen collisionMap is. Thus:

class SpaceShip: public GameObject {

private:

static HitMap * initializeCollisionMap();

...

};

SpaceShip::HitFunctionPtr

SpaceShip::lookup(const GameObject& whatWeHit)

{

static auto_ptr<HitMap>

collisionMap(initializeCollisionMap());

...

}

The clearest way to implement initializeCollisionMap would seem to be this, SpaceShip::HitMap * SpaceShip::initializeCollisionMap()

{

HitMap *phm = new HitMap;

(*phm)["SpaceShip"] = &hitSpaceShip;

(*phm)["SpaceStation"] = &hitSpaceStation;

(*phm)["Asteroid"] = &hitAsteroid;

return phm;

}

Page 405: Effective C++ and More Effective C++

but as I noted earlier, this won't compile. That's because a HitMap is declared to hold pointers to memberfunctions that all take the same type of argument, namely GameObject. But hitSpaceShip takes a SpaceShip,hitSpaceStation takes a SpaceStation, and, hitAsteroid takes an Asteroid. Even though SpaceShip, SpaceStation,and Asteroid can all be implicitly converted to GameObject, there is no such conversion for pointers tofunctions taking these argument types.

To placate your compilers, you might be tempted to employ reinterpret_casts (see Item 2), which are generallythe casts of choice when converting between function pointer types:

// A bad idea...

SpaceShip::HitMap * SpaceShip::initializeCollisionMap()

{

HitMap *phm = new HitMap;

(*phm)["SpaceShip"] =

reinterpret_cast<HitFunctionPtr>(&hitSpaceShip);

(*phm)["SpaceStation"] =

reinterpret_cast<HitFunctionPtr>(&hitSpaceStation);

(*phm)["Asteroid"] =

reinterpret_cast<HitFunctionPtr>(&hitAsteroid);

return phm;

}

This will compile, but it's a bad idea. It entails doing something you should never do: lying to your compilers.Telling them that hitSpaceShip, hitSpaceStation, and hitAsteroid are functions expecting a GameObject argumentis simply not true. hitSpaceShip expects a SpaceShip, hitSpaceStation expects a SpaceStation, and hitAsteroidexpects an Asteroid. The casts say otherwise. The casts lie.

More than morality is on the line here. Compilers don't like to be lied to, and they often find a way to exactrevenge when they discover they've been deceived. In this case, they're likely to get back at you by generatingbad code for functions you call through *phm in cases where GameObject's derived classes employ multipleinheritance or have virtual base classes. In other words, if SpaceStation, SpaceShip, or Asteroid had other baseclasses (in addition to GameObject), you'd probably find that your calls to collision-processing functions incollide would behave quite rudely.

Consider again the A-B-C-D inheritance hierarchy and the possible object layout for a D object that is describedin Item 24:

Page 406: Effective C++ and More Effective C++

Each of the four class parts in a D object has a different address. This is important, because even though pointersand references behave differently (see Item 1), compilers typically implement references by using pointers in thegenerated code. Thus, pass-by-reference is typically implemented by passing a pointer to an object. When anobject with multiple base classes (such as a D object) is passed by reference, it is crucial that compilers passthe correct address ? the one corresponding to the declared type of the parameter in the function being called.

Page 407: Effective C++ and More Effective C++

But what if you've lied to your compilers and told them your function expects a GameObject when it reallyexpects a SpaceShip or a SpaceStation? Then they'll pass the wrong address when you call the function, and theresulting runtime carnage will probably be gruesome. It will also be very difficult to determine the cause of theproblem. There are good reasons why casting is discouraged. This is one of them.

Okay, so casting is out. Fine. But the type mismatch between the function pointers a HitMap is willing to containand the pointers to the hitSpaceShip, hitSpaceStation, and hitAsteroid functions remains. There is only one wayto resolve the conflict: change the types of the functions so they all take GameObject arguments:

class GameObject { // this is unchanged

public:

virtual void collide(GameObject& otherObject) = 0;

...

};

class SpaceShip: public GameObject {

public:

virtual void collide(GameObject& otherObject);

// these functions now all take a GameObject parameter

virtual void hitSpaceShip(GameObject& spaceShip);

virtual void hitSpaceStation(GameObject& spaceStation);

virtual void hitAsteroid(GameObject& asteroid);

...

};

Our solution to the double-dispatching problem that was based on virtual functions overloaded the functionname collide. Now we are in a position to understand why we didn't follow suit here ? why we decided to usean associative array of member function pointers instead. All the hit functions take the same parameter type, sowe must give them different names.

Now we can write initializeCollisionMap the way we always wanted to: SpaceShip::HitMap * SpaceShip::initializeCollisionMap()

{

HitMap *phm = new HitMap;

(*phm)["SpaceShip"] = &hitSpaceShip;

(*phm)["SpaceStation"] = &hitSpaceStation;

(*phm)["Asteroid"] = &hitAsteroid;

return phm;

}

Regrettably, our hit functions now get a general GameObject parameter instead of the derived class parametersthey expect. To bring reality into accord with expectation, we must resort to a dynamic_cast (see Item 2) at thetop of each function:

void SpaceShip::hitSpaceShip(GameObject& spaceShip)

{

SpaceShip& otherShip=

dynamic_cast<SpaceShip&>(spaceShip);

process a SpaceShip-SpaceShip collision;

}

void SpaceShip::hitSpaceStation(GameObject& spaceStation)

{

SpaceStation& station=

dynamic_cast<SpaceStation&>(spaceStation);

process a SpaceShip-SpaceStation collision;

Page 408: Effective C++ and More Effective C++

}

void SpaceShip::hitAsteroid(GameObject& asteroid)

{

Asteroid& theAsteroid =

dynamic_cast<Asteroid&>(asteroid);

process a SpaceShip-Asteroid collision;

}

Each of the dynamic_casts will throw a bad_cast exception if the cast fails. They should never fail, of course,because the hit functions should never be called with incorrect parameter types. Still, we're better off safe thansorry.

Using Non-Member Collision-Processing Functions

We now know how to build a vtbl-like associative array that lets us implement the second half of adouble-dispatch, and we know how to encapsulate the details of the associative array inside a lookup function.Because this array contains pointers to member functions, however, we still have to modify class definitions if anew type of GameObject is added to the game, and that means everybody has to recompile, even people whodon't care about the new type of object. For example, if Satellite were added to our game, we'd have to augmentthe SpaceShip class with a declaration of a function to handle collisions between satellites and spaceships. AllSpaceShip clients would then have to recompile, even if they couldn't care less about the existence of satellites.This is the problem that led us to reject the implementation of double-dispatching based purely on virtualfunctions, and that solution was a lot less work than the one we've just seen.

The recompilation problem would go away if our associative array contained pointers to non-member functions.Furthermore, switching to non-member collision-processing functions would let us address a design question wehave so far ignored, namely, in which class should collisions between objects of different types be handled?With the implementation we just developed, if object 1 and object 2 collide and object 1 happens to be theleft-hand argument to processCollision, the collision will be handled inside the class for object 1. If object 2happens to be the left-hand argument to processCollision, however, the collision will be handled inside theclass for object 2. Does this make sense? Wouldn't it be better to design things so that collisions betweenobjects of types A and B are handled by neither A nor B but instead in some neutral location outside bothclasses?

If we move the collision-processing functions out of our classes, we can give clients header files that containclass definitions without any hit or collide functions. We can then structure our implementation file forprocessCollision as follows:

#include "SpaceShip.h"

#include "SpaceStation.h"

#include "Asteroid.h"

namespace { // unnamed namespace ? see below

// primary collision-processing functions

void shipAsteroid(GameObject& spaceShip,

GameObject& asteroid);

void shipStation(GameObject& spaceShip,

GameObject& spaceStation);

void asteroidStation(GameObject& asteroid,

GameObject& spaceStation);

...

// secondary collision-processing functions that just

// implement symmetry: swap the parameters and call a

// primary function

void asteroidShip(GameObject& asteroid,

Page 409: Effective C++ and More Effective C++

GameObject& spaceShip)

{ shipAsteroid(spaceShip, asteroid); }

void stationShip(GameObject& spaceStation,

GameObject& spaceShip)

{ shipStation(spaceShip, spaceStation); }

void stationAsteroid(GameObject& spaceStation,

GameObject& asteroid)

{ asteroidStation(asteroid, spaceStation); }

...

// see below for a description of these types/functions

typedef void (*HitFunctionPtr)(GameObject&, GameObject&);

typedef map< pair<string,string>, HitFunctionPtr > HitMap;

pair<string,string> makeStringPair(const char *s1,

const char *s2);

HitMap * initializeCollisionMap();

HitFunctionPtr lookup(const string& class1,

const string& class2);

} // end namespace

void processCollision(GameObject& object1,

GameObject& object2)

{

HitFunctionPtr phf = lookup(typeid(object1).name(),

typeid(object2).name());

if (phf) phf(object1, object2);

else throw UnknownCollision(object1, object2);

}

Note the use of the unnamed namespace to contain the functions used to implement processCollision. Everythingin such an unnamed namespace is private to the current translation unit (essentially the current file) ? it's just likethe functions were declared static at file scope. With the advent of namespaces, however, statics at file scopehave been deprecated, so you should accustom yourself to using unnamed namespaces as soon as your compilerssupport them.

Conceptually, this implementation is the same as the one that used member functions, but there are some minordifferences. First, HitFunctionPtr is now a typedef for a pointer to a non-member function. Second, the exceptionclass CollisionWithUnknownObject has been renamed UnknownCollision and modified to take two objectsinstead of one. Finally, lookup must now take two type names and perform both parts of the double-dispatch.This means our collision map must now hold three pieces of information: two types names and aHitFunctionPtr.

As fate would have it, the standard map class is defined to hold only two pieces of information. We can finessethat problem by using the standard pair template, which lets us bundle the two type names together as a singleobject. initializeCollisionMap, along with its makeStringPair helper function, then looks like this:

// we use this function to create pair<string,string>

// objects from two char* literals. It's used in

// initializeCollisionMap below. Note how this function

// enables the return value optimization (see Item 20).

namespace { // unnamed namespace again ? see below

pair<string,string> makeStringPair(const char *s1,

const char *s2)

Page 410: Effective C++ and More Effective C++

{ return pair<string,string>(s1, s2); }

} // end namespace

namespace { // still the unnamed namespace ? see below

HitMap * initializeCollisionMap()

{

HitMap *phm = new HitMap;

(*phm)[makeStringPair("SpaceShip","Asteroid")] =

&shipAsteroid;

(*phm)[makeStringPair("SpaceShip", "SpaceStation")] =

&shipStation;

...

return phm;

}

} // end namespace

lookup must also be modified to work with the pair<string, string> objects that now comprise the firstcomponent of the collision map:

namespace { // I explain this below ? trust me

HitFunctionPtr lookup(const string& class1,

const string& class2)

{

static auto_ptr<HitMap>

collisionMap(initializeCollisionMap());

// see below for a description of make_pair

HitMap::iterator mapEntry=

collisionMap->find(make_pair(class1, class2));

if (mapEntry == collisionMap->end()) return 0;

return (*mapEntry).second;

}

} // end namespace

This is almost exactly what we had before. The only real difference is the use of the make_pair function in thisstatement:

HitMap::iterator mapEntry=

collisionMap->find(make_pair(class1, class2));

make_pair is just a convenience function (template) in the standard library (see Item E49 and Item 35) thatsaves us the trouble of specifying the types when constructing a pair object. We could just as well have writtenthe statement like this:

HitMap::iterator mapEntry=

collisionMap->find(pair<string,string>(class1, class2));

This calls for more typing, however, and specifying the types for the pair is redundant (they're the same as thetypes of class1 and class2), so the make_pair form is more commonly used.

Because makeStringPair, initializeCollisionMap, and lookup were declared inside an unnamed namespace, each

Page 411: Effective C++ and More Effective C++

must be implemented within the same namespace. That's why the implementations of the functions above are inthe unnamed namespace (for the same translation unit as their declarations): so the linker will correctlyassociate their definitions (i.e., their implementations) with their earlier declarations.

We have finally achieved our goals. If new subclasses of GameObject are added to our hierarchy, existingclasses need not recompile (unless they wish to use the new classes). We have no tangle of RTTI-based switchor if-then-else conditionals to maintain. The addition of new classes to the hierarchy requires only well-definedand localized changes to our system: the addition of one or more map insertions in initializeCollisionMap andthe declarations of the new collision-processing functions in the unnamed namespace associated with theimplementation of processCollision. It may have been a lot of work to get here, but at least the trip wasworthwhile. Yes? Yes?

Maybe.

Inheritance and Emulated Virtual Function Tables

There is one final problem we must confront. (If, at this point, you are wondering if there will always be onefinal problem to confront, you have truly come to appreciate the difficulty of designing an implementationmechanism for virtual functions.) Everything we've done will work fine as long as we never need to allowinheritance-based type conversions when calling collision-processing functions. But suppose we develop agame in which we must sometimes distinguish between commercial space ships and military space ships. Wecould modify our hierarchy as follows, where we've heeded the guidance of Item 33 and made the concreteclasses CommercialShip and MilitaryShip inherit from the newly abstract class SpaceShip:

Page 412: Effective C++ and More Effective C++

Suppose commercial and military ships behave identically when they collide with something. Then we'd expectto be able to use the same collision-processing functions we had before CommercialShip and MilitaryShip wereadded. In particular, if a MilitaryShip object and an Asteroid collided, we'd expect

void shipAsteroid(GameObject& spaceShip,

GameObject& asteroid);

to be called. It would not be. Instead, an UnknownCollision exception would be thrown. That's because lookupwould be asked to find a function corresponding to the type names "MilitaryShip" and "Asteroid," and no suchfunction would be found in collisionMap. Even though a MilitaryShip can be treated like a SpaceShip, lookuphas no way of knowing that.

Furthermore, there is no easy way of telling it. If you need to implement double-dispatching and you need tosupport inheritance-based parameter conversions such as these, your only practical recourse is to fall back onthe double-virtual-function-call mechanism we examined earlier. That implies you'll also have to put up witheverybody recompiling when you add to your inheritance hierarchy, but that's just the way life is sometimes.

Initializing Emulated Virtual Function Tables (Reprise)

That's really all there is to say about double-dispatching, but it would be unpleasant to end the discussion onsuch a downbeat note, and unpleasantness is, well, unpleasant. Instead, let's conclude by outlining an alternativeapproach to initializing collisionMap.

As things stand now, our design is entirely static. Once we've registered a function for processing collisionsbetween two types of objects, that's it; we're stuck with that function forever. What if we'd like to add, remove,or change collision-processing functions as the game proceeds? There's no way to do it.

But there can be. We can turn the concept of a map for storing collision-processing functions into a class thatoffers member functions allowing us to modify the contents of the map dynamically. For example:

class CollisionMap {

public:

typedef void (*HitFunctionPtr)(GameObject&, GameObject&);

void addEntry(const string& type1,

const string& type2,

HitFunctionPtr collisionFunction,

bool symmetric = true); // see below

void removeEntry(const string& type1,

const string& type2);

HitFunctionPtr lookup(const string& type1,

const string& type2);

// this function returns a reference to the one and only

// map ? see Item 26

static CollisionMap& theCollisionMap();

private:

Page 413: Effective C++ and More Effective C++

// these functions are private to prevent the creation

// of multiple maps ? see Item 26

CollisionMap();

CollisionMap(const CollisionMap&);

};

This class lets us add entries to the map, remove them from it, and look up the collision-processing functionassociated with a particular pair of type names. It also uses the techniques of Item 26 to limit the number ofCollisionMap objects to one, because there is only one map in our system. (More complex games with multiplemaps are easy to imagine.) Finally, it allows us to simplify the addition of symmetric collisions to the map (i.e.,collisions in which the effect of an object of type T1 hitting an object of type T2 are the same as that of an objectof type T2 hitting an object of type T1) by automatically adding the implied map entry when addEntry is calledwith the optional parameter symmetric set to true.

With the CollisionMap class, each client wishing to add an entry to the map does so directly:

void shipAsteroid(GameObject& spaceShip,

GameObject& asteroid);

CollisionMap::theCollisionMap().addEntry("SpaceShip",

"Asteroid",

&shipAsteroid);

void shipStation(GameObject& spaceShip,

GameObject& spaceStation);

CollisionMap::theCollisionMap().addEntry("SpaceShip",

"SpaceStation",

&shipStation);

void asteroidStation(GameObject& asteroid,

GameObject& spaceStation);

CollisionMap::theCollisionMap().addEntry("Asteroid",

"SpaceStation",

&asteroidStation);

...

Care must be taken to ensure that these map entries are added to the map before any collisions occur that wouldcall the associated functions. One way to do this would be to have constructors in GameObject subclasses checkto make sure the appropriate mappings had been added each time an object was created. Such an approachwould exact a small performance penalty at runtime. An alternative would be to create aRegisterCollisionFunction class:

class RegisterCollisionFunction {

public:

RegisterCollisionFunction(

const string& type1,

const string& type2,

CollisionMap::HitFunctionPtr collisionFunction,

bool symmetric = true)

{

CollisionMap::theCollisionMap().addEntry(type1, type2,

collisionFunction,

symmetric);

}

};

Clients could then use global objects of this type to automatically register the functions they need:

RegisterCollisionFunction cf1("SpaceShip", "Asteroid",

&shipAsteroid);

RegisterCollisionFunction cf2("SpaceShip", "SpaceStation",

Page 414: Effective C++ and More Effective C++

&shipStation);

RegisterCollisionFunction cf3("Asteroid", "SpaceStation",

&asteroidStation);

...

int main(int argc, char * argv[])

{

...

}

Because these objects are created before main is invoked, the functions their constructors register are alsoadded to the map before main is called. If, later, a new derived class is added

class Satellite: public GameObject { ... };

and one or more new collision-processing functions are written, void satelliteShip(GameObject& satellite,

GameObject& spaceShip);

void satelliteAsteroid(GameObject& satellite,

GameObject& asteroid);

these new functions can be similarly added to the map without disturbing existing code: RegisterCollisionFunction cf4("Satellite", "SpaceShip",

&satelliteShip);

RegisterCollisionFunction cf5("Satellite", "Asteroid",

&satelliteAsteroid);

This doesn't change the fact that there's no perfect way to implement multiple dispatch, but it does make it easyto provide data for a map-based implementation if we decide such an approach is the best match for our needs.

Back to Item 30: Proxy classes Continue to Miscellany

11 It turns out that it's not so predictable after all. °The C++ standard doesn't specify the return value oftype_info::name, and different implementations behave differently. (Given a class SpaceShip, for example, oneimplementation's type_info::name returns "class SpaceShip".) A better design would identify a class by theaddress of its associated type_info object, because that is guaranteed to be unique. HitMap would then bedeclared to be of type map<const type_info*, HitFunctionPtr>. Return

Page 415: Effective C++ and More Effective C++

Back to Item 31: Making functions virtual with respect to more than one object Continue to Item 32: Program in the future tense

Miscellany

We thus arrive at the organizational back of the bus, the chapter containing the guidelines no one else wouldhave. We begin with two Items on C++ software development that describe how to design systems thataccommodate change. One of the strengths of the object-oriented approach to systems building is its support forchange, and these Items describe specific steps you can take to fortify your software against the slings andarrows of a world that refuses to stand still.

We then examine how to combine C and C++ in the same program. This necessarily leads to consideration ofextralinguistic issues, but C++ exists in the real world, so sometimes we must confront such things.

Finally, I summarize changes to the °C++ language standard since publication of the de facto reference. Iespecially cover the sweeping changes that have been made in the standard library (see also Item E49). If youhave not been following the standardization process closely, you are probably in for some surprises -- many ofthem quite pleasant.

Back to Item 31: Making functions virtual with respect to more than one object Continue to Item 32: Program in the future tense

Page 416: Effective C++ and More Effective C++

Back to Miscellany Continue to Item 33: Make non-leaf classes abstract

Item 32: Program in the future tense.

Things change.

As software developers, we may not know much, but we do know that things will change. We don't necessarilyknow what will change, how the changes will be brought about, when the changes will occur, or why they willtake place, but we do know this: things will change.

Good software adapts well to change. It accommodates new features, it ports to new platforms, it adjusts to newdemands, it handles new inputs. Software this flexible, this robust, and this reliable does not come about byaccident. It is designed and implemented by programmers who conform to the constraints of today while keepingin mind the probable needs of tomorrow. This kind of software ? software that accepts change gracefully ? iswritten by people who program in the future tense.

To program in the future tense is to accept that things will change and to be prepared for it. It is to recognize thatnew functions will be added to libraries, that new overloadings will occur, and to watch for the potentiallyambiguous function calls that might result (see Item E26). It is to acknowledge that new classes will be added tohierarchies, that present-day derived classes may be tomorrow's base classes, and to prepare for thatpossibility. It is to accept that new applications will be written, that functions will be called in new contexts,and to write those functions so they continue to perform correctly. It is to remember that the programmerscharged with software maintenance are typically not the code's original developers, hence to design andimplement in a fashion that facilitates comprehension, modification, and enhancement by others.

One way to do this is to express design constraints in C++ instead of (or in addition to) comments or otherdocumentation. For example, if a class is designed to never have derived classes, don't just put a comment in theheader file above the class, use C++ to prevent derivation; Item 26 shows you how. If a class requires that allinstances be on the heap, don't just tell clients that, enforce the restriction by applying the approach of Item 27. Ifcopying and assignment make no sense for a class, prevent those operations by declaring the copy constructorand the assignment operator private (see Item E27). C++ offers great power, flexibility, and expressiveness. Usethese characteristics of the language to enforce the design decisions in your programs.

Given that things will change, write classes that can withstand the rough-and-tumble world of softwareevolution. Avoid "demand-paged" virtual functions, whereby you make no functions virtual unless somebodycomes along and demands that you do it. Instead, determine the meaning of a function and whether it makessense to let it be redefined in derived classes. If it does, declare it virtual, even if nobody redefines it rightaway. If it doesn't, declare it nonvirtual, and don't change it later just because it would be convenient forsomeone; make sure the change makes sense in the context of the entire class and the abstraction it represents(see Item E36).

Handle assignment and copy construction in every class, even if "nobody ever does those things." Just becausethey don't do them now doesn't mean they won't do them in the future (see Item E18). If these functions aredifficult to implement, declare them private (see Item E27). That way no one will inadvertently callcompiler-generated functions that do the wrong thing (as often happens with default assignment operators andcopy constructors ? see Item E11).

Adhere to the principle of least astonishment: strive to provide classes whose operators and functions have anatural syntax and an intuitive semantics. Preserve consistency with the behavior of the built-in types: when indoubt, do as the ints do.

Recognize that anything somebody can do, they will do. They'll throw exceptions, they'll assign objects tothemselves, they'll use objects before giving them values, they'll give objects values and never use them, they'llgive them huge values, they'll give them tiny values, they'll give them null values. In general, if it will compile,somebody will do it. As a result, make your classes easy to use correctly and hard to use incorrectly. Accept thatclients will make mistakes, and design your classes so you can prevent, detect, or correct such errors (see, forexample, Item 33 and Item E46).

Page 417: Effective C++ and More Effective C++

Strive for portable code. It's not much harder to write portable programs than to write unportable ones, and onlyrarely will the difference in performance be significant enough to justify unportable constructs (see Item 16).Even programs designed for custom hardware often end up being ported, because stock hardware generallyachieves an equivalent level of performance within a few years. Writing portable code allows you to switchplatforms easily, to enlarge your client base, and to brag about supporting open systems. It also makes it easierto recover if you bet wrong in the operating system sweepstakes.

Design your code so that when changes are necessary, the impact is localized. Encapsulate as much as you can;make implementation details private (e.g., Item E20). Where applicable, use unnamed namespaces or file-staticobjects and functions (see Item 31). Try to avoid designs that lead to virtual base classes, because such classesmust be initialized by every class derived from them ? even those derived indirectly (see Item 4 and Item E43).Avoid RTTI-based designs that make use of cascading if-then-else statements (see Item 31 again, then see ItemE39 for good measure). Every time the class hierarchy changes, each set of statements must be updated, and ifyou forget one, you'll receive no warning from your compilers.

These are well known and oft-repeated exhortations, but most programmers are still stuck in the present tense.As are many authors, unfortunately. Consider this advice by a well-regarded C++ expert: You need a virtual destructor whenever someone deletes a B* that actually points to a D.

Here B is a base class and D is a derived class. In other words, this author suggests that if your program lookslike this, you don't need a virtual destructor in B:

class B { ... }; // no virtual dtor needed

class D: public B { ... };

B *pb = new D;

However, the situation changes if you add this statement:

delete pb; // NOW you need the virtual

// destructor in B

The implication is that a minor change to client code ? the addition of a delete statement ? can result in the needto change the class definition for B. When that happens, all B's clients must recompile. Following this author'sadvice, then, the addition of a single statement in one function can lead to extensive code recompilation andrelinking for all clients of a library. This is anything but effective software design.

On the same topic, a different author writes: If a public base class does not have a virtual destructor, no derived class nor members of a derived class shouldhave a destructor.

In other words, this is okay,

class string { // from the standard C++ library

public:

~string();

};

class B { ... }; // no data members with dtors,

// no virtual dtor needed

but if a new class is derived from B, things change: class D: public B {

string name; // NOW ~B needs to be virtual

};

Again, a small change to the way B is used (here, the addition of a derived class that contains a member with a

Page 418: Effective C++ and More Effective C++

destructor) may necessitate extensive recompilation and relinking by clients. But small changes in softwareshould have small impacts on systems. This design fails that test.

The same author writes: If a multiple inheritance hierarchy has any destructors, every base class should have a virtual destructor.

In all these quotations, note the present-tense thinking. How do clients manipulate pointers now? What classmembers have destructors now? What classes in the hierarchy have destructors now?

Future-tense thinking is quite different. Instead of asking how a class is used now, it asks how the class is designed to be used. Future-tense thinking says, if a class is designed to be used as a base class (even if it's notused as one now), it should have a virtual destructor (see Item E14). Such classes behave correctly both nowand in the future, and they don't affect other library clients when new classes derive from them. (At least, theyhave no effect as far as their destructor is concerned. If additional changes to the class are required, other clientsmay be affected.)

A commercial class library (one that predates the string specification in the C++ library standard) contains astring class with no virtual destructor. The vendor's explanation? We didn't make the destructor virtual, because we didn't want String to have a vtbl. We have no intention of everhaving a String*, so this is not a problem. We are well aware of the difficulties this could cause.

Is this present-tense or future-tense thinking?

Certainly the vtbl issue is a legitimate technical concern (see Item 24 and Item E14). The implementation of mostString classes contains only a single char* pointer inside each String object, so adding a vptr to each Stringwould double the size of those objects. It is easy to understand why a vendor would be unwilling to do that,especially for a highly visible, heavily used class like String. The performance of such a class might easily fallwithin the 20% of a program that makes a difference (see Item 16).

Still, the total memory devoted to a string object ? the memory for the object itself plus the heap memory neededto hold the string's value ? is typically much greater than just the space needed to hold a char* pointer. From thisperspective, the overhead imposed by a vptr is less significant. Nevertheless, it is a legitimate technicalconsideration. (Certainly the °ISO/ANSI standardization committee seems to think so: the standard string typehas a nonvirtual destructor.)

Somewhat more troubling is the vendor's remark, "We have no intention of ever having a String*, so this is not aproblem." That may be true, but their String class is part of a library they make available to thousands ofdevelopers. That's a lot of developers, each with a different level of experience with C++, each doing somethingunique. Do those developers understand the consequences of there being no virtual destructor in String? Are theylikely to know that because String has no virtual destructor, deriving new classes from String is a high-riskventure? Is this vendor confident their clients will understand that in the absence of a virtual destructor, deletingobjects through String* pointers will not work properly and RTTI operations on pointers and references toStrings may return incorrect information? Is this class easy to use correctly and hard to use incorrectly?

This vendor should provide documentation for its String class that makes clear the class is not designed forderivation, but what if programmers overlook the caveat or flat-out fail to read the documentation?

An alternative would be to use C++ itself to prohibit derivation. Item 26 describes how to do this by limitingobject creation to the heap and then using auto_ptr objects to manipulate the heap objects. The interface forString creation would then be both unconventional and inconvenient, requiring this,

auto_ptr<String> ps(String::makeString("Future tense C++"));

... // treat ps as a pointer to

// a String object, but don't

// worry about deleting it

instead of this, String s("Future tense C++");

Page 419: Effective C++ and More Effective C++

but perhaps the reduction in the risk of improperly behaving derived classes would be worth the syntacticinconvenience. (For String, this is unlikely to be the case, but for other classes, the trade-off might well be worthit.)

There is a need, of course, for present-tense thinking. The software you're developing has to work with currentcompilers; you can't afford to wait until the latest language features are implemented. It has to run on thehardware you currently support and it must do so under configurations your clients have available; you can'tforce your customers to upgrade their systems or modify their operating environment. It has to offer acceptableperformance now; promises of smaller, faster programs some years down the line don't generally warm thecockles of potential customers' hearts. And the software you're working on must be available "soon," whichoften means some time in the recent past. These are important constraints. You cannot ignore them.

Future-tense thinking simply adds a few additional considerations: Provide complete classes (see Item E18), even if some parts aren't currently used. When new demands are

made on your classes, you're less likely to have to go back and modify them. Design your interfaces to facilitate common operations and prevent common errors (see Item E46). Make

the classes easy to use correctly, hard to use incorrectly. For example, prohibit copying and assignment forclasses where those operations make no sense (see Item E27). Prevent partial assignments (see Item 33).

If there is no great penalty for generalizing your code, generalize it. For example, if you are writing analgorithm for tree traversal, consider generalizing it to handle any kind of directed acyclic graph.

Future tense thinking increases the reusability of the code you write, enhances its maintainability, makes it morerobust, and facilitates graceful change in an environment where change is a certainty. It must be balanced againstpresent-tense constraints. Too many programmers focus exclusively on current needs, however, and in doing sothey sacrifice the long-term viability of the software they design and implement. Be different. Be a renegade.Program in the future tense.

Back to Miscellany Continue to Item 33: Make non-leaf classes abstract

Page 420: Effective C++ and More Effective C++

Back to Item 32: Program in the future tense Continue to Item 34: Understand how to combine C++ and C in the same program

Item 33: Make non-leaf classes abstract.

Suppose you're working on a project whose software deals with animals. Within this software, most animalscan be treated pretty much the same, but two kinds of animals ? lizards and chickens ? require special handling.That being the case, the obvious way to relate the classes for animals, lizards, and chickens is like this:

The Animal class embodies the features shared by all the creatures you deal with, and the Lizard and Chickenclasses specialize Animal in ways appropriate for lizards and chickens, respectively.

Here's a sketch of the definitions for these classes: class Animal {

public:

Animal& operator=(const Animal& rhs);

...

};

class Lizard: public Animal {

public:

Lizard& operator=(const Lizard& rhs);

...

};

class Chicken: public Animal {

public:

Chicken& operator=(const Chicken& rhs);

...

};

Only the assignment operators are shown here, but that's more than enough to keep us busy for a while. Considerthis code:

Lizard liz1;

Lizard liz2;

Animal *pAnimal1 = &liz1;

Animal *pAnimal2 = &liz2;

...

*pAnimal1 = *pAnimal2;

There are two problems here. First, the assignment operator invoked on the last line is that of the Animal class,even though the objects involved are of type Lizard. As a result, only the Animal part of liz1 will be modified.This is a partial assignment. After the assignment, liz1's Animal members have the values they got from liz2, butliz1's Lizard members remain unchanged.

Page 421: Effective C++ and More Effective C++

The second problem is that real programmers write code like this. It's not uncommon to make assignments toobjects through pointers, especially for experienced C programmers who have moved to C++. That being thecase, we'd like to make the assignment behave in a more reasonable fashion. As Item 32 points out, our classesshould be easy to use correctly and difficult to use incorrectly, and the classes in the hierarchy above are easy touse incorrectly.

One approach to the problem is to make the assignment operators virtual. If Animal::operator= were virtual, theassignment would invoke the Lizard assignment operator, which is certainly the correct one to call. However,look what happens if we declare the assignment operators virtual:

class Animal {

public:

virtual Animal& operator=(const Animal& rhs);

...

};

class Lizard: public Animal {

public:

virtual Lizard& operator=(const Animal& rhs);

...

};

class Chicken: public Animal {

public:

virtual Chicken& operator=(const Animal& rhs);

...

};

Due to relatively recent changes to the language, we can customize the return value of the assignment operatorsso that each returns a reference to the correct class, but the rules of C++ force us to declare identical parametertypes for a virtual function in every class in which it is declared. That means the assignment operator for theLizard and Chicken classes must be prepared to accept any kind of Animal object on the right-hand side of anassignment. That, in turn, means we have to confront the fact that code like the following is legal:

Lizard liz;

Chicken chick;

Animal *pAnimal1 = &liz;

Animal *pAnimal2 = &chick;

...

*pAnimal1 = *pAnimal2; // assign a chicken to

// a lizard!

This is a mixed-type assignment: a Lizard is on the left and a Chicken is on the right. Mixed-type assignmentsaren't usually a problem in C++, because the language's strong typing generally renders them illegal. By makingAnimal's assignment operator virtual, however, we opened the door to such mixed-type operations.

This puts us in a difficult position. We'd like to allow same-type assignments through pointers, but we'd like toforbid mixed-type assignments through those same pointers. In other words, we want to allow this,

Animal *pAnimal1 = &liz1;

Animal *pAnimal2 = &liz2;

...

*pAnimal1 = *pAnimal2; // assign a lizard to a lizard

but we want to prohibit this: Animal *pAnimal1 = &liz;

Animal *pAnimal2 = &chick;

...

*pAnimal1 = *pAnimal2; // assign a chicken to a lizard

Page 422: Effective C++ and More Effective C++

Distinctions such as these can be made only at runtime, because sometimes assigning *pAnimal2 to *pAnimal1is valid, sometimes it's not. We thus enter the murky world of type-based runtime errors. In particular, we needto signal an error inside operator= if we're faced with a mixed-type assignment, but if the types are the same, wewant to perform the assignment in the usual fashion.

We can use a dynamic_cast (see Item 2) to implement this behavior. Here's how to do it for Lizard's assignmentoperator:

Lizard& Lizard::operator=(const Animal& rhs)

{

// make sure rhs is really a lizard

const Lizard& rhs_liz = dynamic_cast<const Lizard&>(rhs);

proceed with a normal assignment of rhs_liz to *this;

}

This function assigns rhs to *this only if rhs is really a Lizard. If it's not, the function propagates the bad_castexception that dynamic_cast throws when the cast fails. (Actually, the type of the exception is std::bad_cast,because the components of the standard library, including the exceptions thrown by the standard components, arein the namespace std. For an overview of the standard library, see Item E49 and Item 35.)

Even without worrying about exceptions, this function seems needlessly complicated and expensive ? thedynamic_cast must consult a type_info structure; see Item 24 ? in the common case where one Lizard object isassigned to another:

Lizard liz1, liz2;

...

liz1 = liz2; // no need to perform a

// dynamic_cast: this

// assignment must be valid

We can handle this case without paying for the complexity or cost of a dynamic_cast by adding to Lizard theconventional assignment operator:

class Lizard: public Animal {

public:

virtual Lizard& operator=(const Animal& rhs);

Lizard& operator=(const Lizard& rhs); // add this

...

};

Lizard liz1, liz2;

...

liz1 = liz2; // calls operator= taking

// a const Lizard&

Animal *pAnimal1 = &liz1;

Animal *pAnimal2 = &liz2;

...

*pAnimal1 = *pAnimal2; // calls operator= taking

// a const Animal&

In fact, given this latter operator=, it's simplicity itself to implement the former one in terms of it: Lizard& Lizard::operator=(const Animal& rhs)

{

Page 423: Effective C++ and More Effective C++

return operator=(dynamic_cast<const Lizard&>(rhs));

}

This function attempts to cast rhs to be a Lizard. If the cast succeeds, the normal class assignment operator iscalled. Otherwise, a bad_cast exception is thrown.

Frankly, all this business of checking types at runtime and using dynamic_casts makes me nervous. For one thing,some compilers still lack support for dynamic_cast, so code that uses it, though theoretically portable, is notnecessarily portable in practice. More importantly, it requires that clients of Lizard and Chicken be prepared tocatch bad_cast exceptions and do something sensible with them each time they perform an assignment. In myexperience, there just aren't that many programmers who are willing to program that way. If they don't, it's notclear we've gained a whole lot over our original situation where we were trying to guard against partialassignments.

Given this rather unsatisfactory state of affairs regarding virtual assignment operators, it makes sense to regroupand try to find a way to prevent clients from making problematic assignments in the first place. If suchassignments are rejected during compilation, we don't have to worry about them doing the wrong thing.

The easiest way to prevent such assignments is to make operator= private in Animal. That way, lizards can beassigned to lizards and chickens can be assigned to chickens, but partial and mixed-type assignments areforbidden:

class Animal {

private:

Animal& operator=(const Animal& rhs); // this is now

... // private

};

class Lizard: public Animal {

public:

Lizard& operator=(const Lizard& rhs);

...

};

class Chicken: public Animal {

public:

Chicken& operator=(const Chicken& rhs);

...

};

Lizard liz1, liz2;

...

liz1 = liz2; // fine

Chicken chick1, chick2;

...

chick1 = chick2; // also fine

Animal *pAnimal1 = &liz1;

Animal *pAnimal2 = &chick1;

...

*pAnimal1 = *pAnimal2; // error! attempt to call

// private Animal::operator=

Unfortunately, Animal is a concrete class, and this approach also makes assignments between Animal objectsillegal:

Animal animal1, animal2;

...

animal1 = animal2; // error! attempt to call

Page 424: Effective C++ and More Effective C++

// private Animal::operator=

Moreover, it makes it impossible to implement the Lizard and Chicken assignment operators correctly, becauseassignment operators in derived classes are responsible for calling assignment operators in their base classes(see Item E16):

Lizard& Lizard::operator=(const Lizard& rhs)

{

if (this == &rhs) return *this;

Animal::operator=(rhs); // error! attempt to call

// private function. But

// Lizard::operator= must

// call this function to

... // assign the Animal parts

} // of *this!

We can solve this latter problem by declaring Animal::operator= protected, but the conundrum of allowingassignments between Animal objects while preventing partial assignments of Lizard and Chicken objects throughAnimal pointers remains. What's a poor programmer to do?

The easiest thing is to eliminate the need to allow assignments between Animal objects, and the easiest way todo that is to make Animal an abstract class. As an abstract class, Animal can't be instantiated, so there will beno need to allow assignments between Animals. Of course, this leads to a new problem, because our originaldesign for this system presupposed that Animal objects were necessary. There is an easy way around thisdifficulty. Instead of making Animal itself abstract, we create a new class ? AbstractAnimal, say ? consisting ofthe common features of Animal, Lizard, and Chicken objects, and we make that class abstract. Then we haveeach of our concrete classes inherit from AbstractAnimal. The revised hierarchy looks like this,

and the class definitions are as follows: class AbstractAnimal {

protected:

AbstractAnimal& operator=(const AbstractAnimal& rhs);

public:

virtual ~AbstractAnimal() = 0; // see below

...

};

class Animal: public AbstractAnimal {

Page 425: Effective C++ and More Effective C++

public:

Animal& operator=(const Animal& rhs);

...

};

class Lizard: public AbstractAnimal {

public:

Lizard& operator=(const Lizard& rhs);

...

};

class Chicken: public AbstractAnimal {

public:

Chicken& operator=(const Chicken& rhs);

...

};

This design gives you everything you need. Homogeneous assignments are allowed for lizards, chickens, andanimals; partial assignments and heterogeneous assignments are prohibited; and derived class assignmentoperators may call the assignment operator in the base class. Furthermore, none of the code written in terms ofthe Animal, Lizard, or Chicken classes requires modification, because these classes continue to exist and tobehave as they did before AbstractAnimal was introduced. Sure, such code has to be recompiled, but that's asmall price to pay for the security of knowing that assignments that compile will behave intuitively andassignments that would behave unintuitively won't compile.

For all this to work, AbstractAnimal must be abstract ? it must contain at least one pure virtual function. In mostcases, coming up with a suitable function is not a problem, but on rare occasions you may find yourself facingthe need to create a class like AbstractAnimal in which none of the member functions would naturally bedeclared pure virtual. In such cases, the conventional technique is to make the destructor a pure virtual function;that's what's shown above. In order to support polymorphism through pointers correctly, base classes needvirtual destructors anyway (see Item E14), so the only cost associated with making such destructors pure virtualis the inconvenience of having to implement them outside their class definitions. (For an example, see page 195.)

(If the notion of implementing a pure virtual function strikes you as odd, you just haven't been getting out enough.Declaring a function pure virtual doesn't mean it has no implementation, it means

the current class is abstract, and any concrete class inheriting from the current class must declare the function as a "normal" virtual function

(i.e., without the "=0").

True, most pure virtual functions are never implemented, but pure virtual destructors are a special case. They must be implemented, because they are called whenever a derived class destructor is invoked. Furthermore,they often perform useful tasks, such as releasing resources (see Item 9) or logging messages. Implementing purevirtual functions may be uncommon in general, but for pure virtual destructors, it's not just common, it'smandatory.)

You may have noticed that this discussion of assignment through base class pointers is based on the assumptionthat concrete base classes like Animal contain data members. If there are no data members, you might point out,there is no problem, and it would be safe to have a concrete class inherit from a second, dataless, concreteclass.

One of two situations applies to your data-free would-be concrete base class: either it might have data membersin the future or it might not. If it might have data members in the future, all you're doing is postponing theproblem until the data members are added, in which case you're merely trading short-term convenience forlong-term grief (see also Item 32). Alternatively, if the base class should truly never have any data members, thatsounds very much like it should be an abstract class in the first place. What use is a concrete base class withoutdata?

Replacement of a concrete base class like Animal with an abstract base class like AbstractAnimal yieldsbenefits far beyond simply making the behavior of operator= easier to understand. It also reduces the chances

Page 426: Effective C++ and More Effective C++

that you'll try to treat arrays polymorphically, the unpleasant consequences of which are examined in Item 3. Themost significant benefit of the technique, however, occurs at the design level, because replacing concrete baseclasses with abstract base classes forces you to explicitly recognize the existence of useful abstractions. That is,it makes you create new abstract classes for useful concepts, even if you aren't aware of the fact that the usefulconcepts exist.

If you have two concrete classes C1 and C2 and you'd like C2 to publicly inherit from C1, you should transformthat two-class hierarchy into a three-class hierarchy by creating a new abstract class A and having both C1 andC2 publicly inherit from it:

The primary value of this transformation is that it forces you to identify the abstract class A. Clearly, C1 and C2have something in common; that's why they're related by public inheritance (see Item E35). With thistransformation, you must identify what that something is. Furthermore, you must formalize the something as aclass in C++, at which point it becomes more than just a vague something, it achieves the status of a formal abstraction, one with well-defined member functions and well-defined semantics.

All of which leads to some worrisome thinking. After all, every class represents some kind of abstraction, soshouldn't we create two classes for every concept in our hierarchy, one being abstract (to embody the abstractpart of the abstraction) and one being concrete (to embody the object-generation part of the abstraction)? No. Ifyou do, you'll end up with a hierarchy with too many classes. Such a hierarchy is difficult to understand, hard tomaintain, and expensive to compile. That is not the goal of object-oriented design.

The goal is to identify useful abstractions and to force them ? and only them ? into existence as abstract classes.But how do you identify useful abstractions? Who knows what abstractions might prove useful in the future?Who can predict who's going to want to inherit from what?

Page 427: Effective C++ and More Effective C++

Well, I don't know how to predict the future uses of an inheritance hierarchy, but I do know one thing: the needfor an abstraction in one context may be coincidental, but the need for an abstraction in more than one context isusually meaningful. Useful abstractions, then, are those that are needed in more than one context. That is, theycorrespond to classes that are useful in their own right (i.e., it is useful to have objects of that type) and that arealso useful for purposes of one or more derived classes.

This is precisely why the transformation from concrete base class to abstract base class is useful: it forces theintroduction of a new abstract class only when an existing concrete class is about to be used as a base class, i.e.,when the class is about to be (re)used in a new context. Such abstractions are useful, because they have, throughdemonstrated need, shown themselves to be so.

The first time a concept is needed, we can't justify the creation of both an abstract class (for the concept) and aconcrete class (for the objects corresponding to that concept), but the second time that concept is needed, we can justify the creation of both the abstract and the concrete classes. The transformation I've described simplymechanizes this process, and in so doing it forces designers and programmers to represent explicitly thoseabstractions that are useful, even if the designers and programmers are not consciously aware of the usefulconcepts. It also happens to make it a lot easier to bring sanity to the behavior of assignment operators.

Let's consider a brief example. Suppose you're working on an application that deals with moving informationbetween computers on a network by breaking it into packets and transmitting them according to some protocol.All we'll consider here is the class or classes for representing packets. We'll assume such classes make sensefor this application.

Suppose you deal with only a single kind of transfer protocol and only a single kind of packet. Perhaps you'veheard that other protocols and packet types exist, but you've never supported them, nor do you have any plans tosupport them in the future. Should you make an abstract class for packets (for the concept that a packetrepresents) as well as a concrete class for the packets you'll actually be using? If you do, you could hope to addnew packet types later without changing the base class for packets. That would save you from having torecompile packet-using applications if you add new packet types. But that design requires two classes, and rightnow you need only one (for the particular type of packets you use). Is it worth complicating your design now toallow for future extension that may never take place?

There is no unequivocally correct choice to be made here, but experience has shown it is nearly impossible todesign good classes for concepts we do not understand well. If you create an abstract class for packets, howlikely are you to get it right, especially since your experience is limited to only a single packet type? Rememberthat you gain the benefit of an abstract class for packets only if you can design that class so that future classescan inherit from it without its being changed in any way. (If it needs to be changed, you have to recompile allpacket clients, and you've gained nothing.)

It is unlikely you could design a satisfactory abstract packet class unless you were well versed in many differentkinds of packets and in the varied contexts in which they are used. Given your limited experience in this case,my advice would be not to define an abstract class for packets, adding one later only if you find a need to inheritfrom the concrete packet class.

The transformation I've described here is a way to identify the need for abstract classes, not the way. There aremany other ways to identify good candidates for abstract classes; books on object-oriented analysis are filledwith them. It's not the case that the only time you should introduce abstract classes is when you find yourselfwanting to have a concrete class inherit from another concrete class. However, the desire to relate two concreteclasses by public inheritance is usually indicative of a need for a new abstract class.

As is often the case in such matters, brash reality sometimes intrudes on the peaceful ruminations of theory.Third-party C++ class libraries are proliferating with gusto, and what are you to do if you find yourself wantingto create a concrete class that inherits from a concrete class in a library to which you have only read access?

You can't modify the library to insert a new abstract class, so your choices are both limited and unappealing: Derive your concrete class from the existing concrete class, and put up with the assignment-related

problems we examined at the beginning of this Item. You'll also have to watch out for the array-relatedpitfalls described in Item 3.

Try to find an abstract class higher in the library hierarchy that does most of what you need, then inherit

Page 428: Effective C++ and More Effective C++

from that class. Of course, there may not be a suitable class, and even if there is, you may have toduplicate a lot of effort that has already been put into the implementation of the concrete class whosefunctionality you'd like to extend.

Implement your new class in terms of the library class you'd like to inherit from (see Items E40 and E42).For example, you could have an object of the library class as a data member, then reimplement the libraryclass's interface in your new class:

class Window { // this is the library class

public:

virtual void resize(int newWidth, int newHeight);

virtual void repaint() const;

int width() const;

int height() const;

};

class SpecialWindow { // this is the class you

public: // wanted to have inherit

... // from Window

// pass-through implementations of nonvirtual functions

int width() const { return w.width(); }

int height() const { return w.height(); }

// new implementations of "inherited" virtual functions

virtual void resize(int newWidth, int newHeight);

virtual void repaint() const;

private:

Window w;

};

This strategy requires that you be prepared to update your class each time the library vendor updates theclass on which you're dependent. It also requires that you be willing to forgo the ability to redefine virtualfunctions declared in the library class, because you can't redefine virtual functions unless you inheritthem.

Make do with what you've got. Use the concrete class that's in the library and modify your software so thatthe class suffices. Write non-member functions to provide the functionality you'd like to add to the class,but can't. The resulting software may not be as clear, as efficient, as maintainable, or as extensible asyou'd like, but at least it will get the job done.

None of these choices is particularly attractive, so you have to apply some engineering judgment and choose thepoison you find least unappealing. It's not much fun, but life's like that sometimes. To make things easier foryourself (and the rest of us) in the future, complain to the vendors of libraries whose designs you find wanting.With luck (and a lot of comments from clients), those designs will improve as time goes on.

Still, the general rule remains: non-leaf classes should be abstract. You may need to bend the rule when workingwith outside libraries, but in code over which you have control, adherence to it will yield dividends in the formof increased reliability, robustness, comprehensibility, and extensibility throughout your software.

Back to Item 32: Program in the future tense Continue to Item 34: Understand how to combine C++ and C in the same program

Page 429: Effective C++ and More Effective C++

Back to Item 33: Make non-leaf classes abstract Continue to Item 35: Familiarize yourself with the language standard

Item 34: Understand how to combine C++ and C in the same program.

In many ways, the things you have to worry about when making a program out of some components in C++ andsome in C are the same as those you have to worry about when cobbling together a C program out of object filesproduced by more than one C compiler. There is no way to combine such files unless the different compilersagree on implementation-dependent features like the size of ints and doubles, the mechanism by whichparameters are passed from caller to callee, and whether the caller or the callee orchestrates the passing. Thesepragmatic aspects of mixed-compiler software development are quite properly ignored by °languagestandardization efforts, so the only reliable way to know that object files from compiler A and compiler B canbe safely combined in a program is to obtain assurances from the vendors of A and B that their products producecompatible output. This is as true for programs made up of C++ and C as it is for all-C++ or all-C programs, sobefore you try to mix C++ and C in the same program, make sure your C++ and C compilers generate compatibleobject files.

Having done that, there are four other things you need to consider: name mangling, initialization of statics,dynamic memory allocation, and data structure compatibility.

Name Mangling

Name mangling, as you may know, is the process through which your C++ compilers give each function in yourprogram a unique name. In C, this process is unnecessary, because you can't overload function names, but nearlyall C++ programs have at least a few functions with the same name. (Consider, for example, the iostreamlibrary, which declares several versions of operator<< and operator>>.) Overloading is incompatible with mostlinkers, because linkers generally take a dim view of multiple functions with the same name. Name mangling is aconcession to the realities of linkers; in particular, to the fact that linkers usually insist on all function namesbeing unique.

As long as you stay within the confines of C++, name mangling is not likely to concern you. If you have afunction name drawLine that a compiler mangles into xyzzy, you'll always use the name drawLine, and you'llhave little reason to care that the underlying object files happen to refer to xyzzy.

It's a different story if drawLine is in a C library. In that case, your C++ source file probably includes a headerfile that contains a declaration like this,

void drawLine(int x1, int y1, int x2, int y2);

and your code contains calls to drawLine in the usual fashion. Each such call is translated by your compilersinto a call to the mangled name of that function, so when you write this,

drawLine(a, b, c, d); // call to unmangled function name

your object files contain a function call that corresponds to this: xyzzy(a, b, c, d); // call to mangled function mame

But if drawLine is a C function, the object file (or archive or dynamically linked library, etc.) that contains thecompiled version of drawLine contains a function called drawLine; no name mangling has taken place. Whenyou try to link the object files comprising your program together, you'll get an error, because the linker is lookingfor a function called xyzzy, and there is no such function.

To solve this problem, you need a way to tell your C++ compilers not to mangle certain function names. Younever want to mangle the names of functions written in other languages, whether they be in C, assembler,FORTRAN, Lisp, Forth, or what-have-you. (Yes, what-have-you would include COBOL, but then what wouldyou have?) After all, if you call a C function named drawLine, it's really called drawLine, and your object codeshould contain a reference to that name, not to some mangled version of that name.

To suppress name mangling, use C++'s extern "C" directive:

Page 430: Effective C++ and More Effective C++

// declare a function called drawLine; don't mangle

// its name

extern "C"

void drawLine(int x1, int y1, int x2, int y2);

Don't be drawn into the trap of assuming that where there's an extern "C", there must be an extern "Pascal" andan extern "FORTRAN" as well. There's not, at least not in °the standard. The best way to view extern "C" is notas an assertion that the associated function is written in C, but as a statement that the function should be called asif it were written in C. (Technically, extern "C" means the function has C linkage, but what that means is far fromclear. One thing it always means, however, is that name mangling is suppressed.)

For example, if you were so unfortunate as to have to write a function in assembler, you could declare it extern"C", too:

// this function is in assembler ? don't mangle its name

extern "C" void twiddleBits(unsigned char bits);

You can even declare C++ functions extern "C". This can be useful if you're writing a library in C++ that you'dlike to provide to clients using other programming languages. By suppressing the name mangling of your C++function names, your clients can use the natural and intuitive names you choose instead of the mangled namesyour compilers would otherwise generate:

// the following C++ function is designed for use outside

// C++ and should not have its name mangled

extern "C" void simulate(int iterations);

Often you'll have a slew of functions whose names you don't want mangled, and it would be a pain to precedeeach with extern "C". Fortunately, you don't have to. extern "C" can also be made to apply to a whole set offunctions. Just enclose them all in curly braces:

extern "C" { // disable name mangling for

// all the following functions

void drawLine(int x1, int y1, int x2, int y2);

void twiddleBits(unsigned char bits);

void simulate(int iterations);

...

}

This use of extern "C" simplifies the maintenance of header files that must be used with both C++ and C. Whencompiling for C++, you'll want to include extern "C", but when compiling for C, you won't. By taking advantageof the fact that the preprocessor symbol __cplusplus is defined only for C++ compilations, you can structureyour polyglot header files as follows:

#ifdef __cplusplus

extern "C" {

#endif

void drawLine(int x1, int y1, int x2, int y2);

void twiddleBits(unsigned char bits);

void simulate(int iterations);

...

#ifdef __cplusplus

}

#endif

There is, by the way, no such thing as a "standard" name mangling algorithm. Different compilers are free to

Page 431: Effective C++ and More Effective C++

mangle names in different ways, and different compilers do. This is a good thing. If all compilers manglednames the same way, you might be lulled into thinking they all generated compatible code. The way things arenow, if you try to mix object code from incompatible C++ compilers, there's a good chance you'll get an errorduring linking, because the mangled names won't match up. This implies you'll probably have othercompatibility problems, too, and it's better to find out about such incompatibilities sooner than later,

Initialization of Statics

Once you've mastered name mangling, you need to deal with the fact that in C++, lots of code can get executedbefore and after main. In particular, the constructors of static class objects and objects at global, namespace, andfile scope are usually called before the body of main is executed. This process is known as static initialization(see Item E47). This is in direct opposition to the way we normally think about C++ and C programs, in whichwe view main as the entry point to execution of the program. Similarly, objects that are created through staticinitialization must have their destructors called during static destruction; that process typically takes place aftermain has finished executing.

To resolve the dilemma that main is supposed to be invoked first, yet objects need to be constructed before mainis executed, many compilers insert a call to a special compiler-written function at the beginning of main, and itis this special function that takes care of static initialization. Similarly, compilers often insert a call to anotherspecial function at the end of main to take care of the destruction of static objects. Code generated for main oftenlooks as if main had been written like this:

int main(int argc, char *argv[])

{

performStaticInitialization(); // generated by the

// implementation

the statements you put in main go here;

performStaticDestruction(); // generated by the

// implementation

}

Now don't take this too literally. The functions performStaticInitialization and performStaticDestruction usuallyhave much more cryptic names, and they may even be generated inline, in which case you won't see anyfunctions for them in your object files. The important point is this: if a C++ compiler adopts this approach to theinitialization and destruction of static objects, such objects will be neither initialized nor destroyed unless mainis written in C++. Because this approach to static initialization and destruction is common, you should try towrite main in C++ if you write any part of a software system in C++.

Sometimes it would seem to make more sense to write main in C ? say if most of a program is in C and C++ isjust a support library. Nevertheless, there's a good chance the C++ library contains static objects (if it doesn'tnow, it probably will in the future ? see Item 32), so it's still a good idea to write main in C++ if you possiblycan. That doesn't mean you need to rewrite your C code, however. Just rename the main you wrote in C to berealMain, then have the C++ version of main call realMain:

extern "C" // implement this

int realMain(int argc, char *argv[]); // function in C

int main(int argc, char *argv[]) // write this in C++

{

return realMain(argc, argv);

}

If you do this, it's a good idea to put a comment above main explaining what is going on.

If you cannot write main in C++, you've got a problem, because there is no other portable way to ensure thatconstructors and destructors for static objects are called. This doesn't mean all is lost, it just means you'll haveto work a little harder. Compiler vendors are well acquainted with this problem, so almost all provide someextralinguistic mechanism for initiating the process of static initialization and static destruction. For informationon how this works with your compilers, dig into your compilers' documentation or contact their vendors.

Page 432: Effective C++ and More Effective C++

Dynamic Memory Allocation

That brings us to dynamic memory allocation. The general rule is simple: the C++ parts of a program use newand delete (see Item 8), and the C parts of a program use malloc (and its variants) and free. As long as memorythat came from new is deallocated via delete and memory that came from malloc is deallocated via free, all iswell. Calling free on a newed pointer yields undefined behavior, however, as does deleteing a mallocedpointer. The only thing to remember, then, is to segregate rigorously your news and deletes from your mallocsand frees.

Sometimes this is easier said than done. Consider the humble (but handy) strdup function, which, though standardin neither C nor C++, is nevertheless widely available:

char * strdup(const char *ps); // return a copy of the

// string pointed to by ps

If a memory leak is to be avoided, the memory allocated inside strdup must be deallocated by strdup's caller.But how is the memory to be deallocated? By using delete? By calling free? If the strdup you're calling is from aC library, it's the latter. If it was written for a C++ library, it's probably the former. What you need to do aftercalling strdup, then, varies not only from system to system, but also from compiler to compiler. To reduce suchportability headaches, try to avoid calling functions that are neither in the standard library (see Item E49 andItem 35) nor available in a stable form on most computing platforms.

Data Structure Compatibility

Which brings us at long last to passing data between C++ and C programs. There's no hope of making Cfunctions understand C++ features, so the level of discourse between the two languages must be limited to thoseconcepts that C can express. Thus, it should be clear there's no portable way to pass objects or to pass pointersto member functions to routines written in C. C does understand normal pointers, however, so, provided yourC++ and C compilers produce compatible output, functions in the two languages can safely exchange pointers toobjects and pointers to non-member or static functions. Naturally, structs and variables of built-in types (e.g.,ints, chars, etc.) can also freely cross the C++/C border.

Because the rules governing the layout of a struct in C++ are consistent with those of C, it is safe to assume thata structure definition that compiles in both languages is laid out the same way by both compilers. Such structscan be safely passed back and forth between C++ and C. If you add nonvirtual functions to the C++ version ofthe struct, its memory layout should not change, so objects of a struct (or class) containing only non-virtualfunctions should be compatible with their C brethren whose structure definition lacks only the member functiondeclarations. Adding virtual functions ends the game, because the addition of virtual functions to a class causesobjects of that type to use a different memory layout (see Item 24). Having a struct inherit from another struct (orclass) usually changes its layout, too, so structs with base structs (or classes) are also poor candidates forexchange with C functions.

From a data structure perspective, it boils down to this: it is safe to pass data structures from C++ to C and fromC to C++ provided the definition of those structures compiles in both C++ and C. Adding nonvirtual memberfunctions to the C++ version of a struct that's otherwise compatible with C will probably not affect itscompatibility, but almost any other change to the struct will.

Summary

If you want to mix C++ and C in the same program, remember the following simple guidelines: Make sure the C++ and C compilers produce compatible object files. Declare functions to be used by both languages extern "C". If at all possible, write main in C++. Always use delete with memory from new; always use free with memory from malloc. Limit what you pass between the two languages to data structures that compile under C; the C++ version of

structs may contain non-virtual member functions. Back to Item 33: Make non-leaf classes abstract

Continue to Item 35: Familiarize yourself with the language standard

Page 433: Effective C++ and More Effective C++

Back to Item 34: Understand how to combine C++ and C in the same program Continue to Recommended Reading

Item 35: Familiarize yourself with °the language standard.

Since its publication in 1990, °The Annotated C++ Reference Manual (see page 285) has been the definitivereference for working programmers needing to know what is in C++ and what is not. In the years since the ARM(as it's fondly known) came out, the °ISO/ANSI committee standardizing the language has changed (primarilyextended) the language in ways both big and small. As a definitive reference, the ARM no longer suffices.

The post-ARM changes to C++ significantly affect how good programs are written. As a result, it is importantfor C++ programmers to be familiar with the primary ways in which the C++ specified by the standard differsfrom that described by the ARM.

The °ISO/ANSI standard for C++ is what vendors will consult when implementing compilers, what authors willexamine when preparing books, and what programmers will look to for definitive answers to questions aboutC++. Among the most important changes to C++ since the ARM are the following:

New features have been added: RTTI, namespaces, bool, the mutable and explicit keywords, the abilityto overload operators for enums, and the ability to initialize constant integral static class members withina class definition.

Templates have been extended: member templates are now allowed, there is a standard syntax forforcing template instantiations, non-type arguments are now allowed in function templates, and classtemplates may themselves be used as template arguments.

Exception handling has been refined: exception specifications are now more rigorously checked duringcompilation, and the unexpected function may now throw a bad_exception object.

Memory allocation routines have been modified: operator new[] and operator delete[] have beenadded, the operators new/new[] now throw an exception if memory can't be allocated, and there are nowalternative versions of the operators new/new[] that return 0 when an allocation fails (see Item E7).

New casting forms have been added: static_cast, dynamic_cast, const_cast, and reinterpret_cast. Language rules have been refined: redefinitions of virtual functions need no longer have a return type

that exactly matches that of the function they redefine, and the lifetime of temporary objects has beendefined precisely.

Almost all these changes are described in °The Design and Evolution of C++ (see page 285). Current C++textbooks (those written after 1994) should include them, too. (If you find one that doesn't, reject it.) In addition, More Effective C++ (that's this book) contains examples of how to use most of these new features. If you'recurious about something on this list, try looking it up in the index.

The changes to C++ proper pale in comparison to what's happened to the standard library. Furthermore, theevolution of the standard library has not been as well publicized as that of the language. The Design andEvolution of C++, for example, makes almost no mention of the standard library. The books that do discuss thelibrary are sometimes out of date, because the library changed quite substantially in 1994.

The capabilities of the standard library can be broken down into the following general categories (see also ItemE49):

Support for the standard C library. Fear not, C++ still remembers its roots. Some minor tweaks havebrought the C++ version of the C library into conformance with C++'s stricter type checking, but for allintents and purposes, everything you know and love (or hate) about the C library continues to be knowableand lovable (or hateable) in C++, too.

Support for strings. As Chair of the working group for the standard C++ library, Mike Vilot was told, "Ifthere isn't a standard string type, there will be blood in the streets!" (Some people get so emotional.) Calmyourself and put away those hatchets and truncheons ? the standard C++ library has strings.

Support for localization. Different cultures use different character sets and follow different conventionswhen displaying dates and times, sorting strings, printing monetary values, etc. Localization support withinthe standard library facilitates the development of programs that accommodate such cultural differences.

Support for I/O. The iostream library remains part of the C++ standard, but the committee has tinkeredwith it a bit. Though some classes have been eliminated (notably iostream and fstream) and some havebeen replaced (e.g., string-based stringstreams replace char*-based strstreams, which are now

Page 434: Effective C++ and More Effective C++

deprecated), the basic capabilities of the standard iostream classes mirror those of the implementationsthat have existed for several years.

Support for numeric applications. Complex numbers, long a mainstay of examples in C++ texts, havefinally been enshrined in the standard library. In addition, the library contains special array classes(valarrays) that restrict aliasing. These arrays are eligible for more aggressive optimization than arebuilt-in arrays, especially on multiprocessing architectures. The library also provides a few commonlyuseful numeric functions, including partial sum and adjacent difference.

Support for general-purpose containers and algorithms. Contained within the standard C++ library is aset of class and function templates collectively known as the Standard Template Library (STL). The STLis the most revolutionary part of the standard C++ library. I summarize its features below.

Before I describe the STL, though, I must dispense with two idiosyncrasies of the standard C++ library you needto know about.

First, almost everything in the library is a template. In this book, I may have referred to the standard string class,but in fact there is no such class. Instead, there is a class template called basic_string that represents sequencesof characters, and this template takes as a parameter the type of the characters making up the sequences. Thisallows for strings to be made up of chars, wide chars, Unicode chars, whatever.

What we normally think of as the string class is really the template instantiation basic_string<char>. Because itsuse is so common, the standard library provides a typedef:

typedef basic_string<char> string;

Even this glosses over many details, because the basic_string template takes three arguments; all but the firsthave default values. To really understand the string type, you must face this full, unexpurgated declaration ofbasic_string:

template<class charT,

class traits = string_char_traits<charT>,

class Allocator = allocator>

class basic_string;

You don't need to understand this gobbledygook to use the string type, because even though string is a typedeffor The Template Instantiation from Hell, it behaves as if it were the unassuming non-template class the typedefmakes it appear to be. Just tuck away in the back of your mind the fact that if you ever need to customize thetypes of characters that go into strings, or if you want to fine-tune the behavior of those characters, or if you wantto seize control over the way memory for strings is allocated, the basic_string template allows you to do thesethings.

The approach taken in the design of the string type ? generalize it and make the generalization a template ? isrepeated throughout the standard C++ library. IOstreams? They're templates; a type parameter defines the type ofcharacter making up the streams. Complex numbers? Also templates; a type parameter defines how thecomponents of the numbers should be stored. Valarrays? Templates; a type parameter specifies what's in eacharray. And of course the STL consists almost entirely of templates. If you are not comfortable with templates,now would be an excellent time to start making serious headway toward that goal.

The other thing to know about the standard library is that virtually everything it contains is inside the namespacestd. To use things in the standard library without explicitly qualifying their names, you'll have to employ a usingdirective or (preferably) using declarations (see Item E28). Fortunately, this syntactic administrivia isautomatically taken care of when you #include the appropriate headers.

The Standard Template Library

The biggest news in the standard C++ library is the STL, the Standard Template Library. (Since almosteverything in the C++ library is a template, the name STL is not particularly descriptive. Nevertheless, this isthe name of the containers and algorithms portion of the library, so good name or bad, this is what we use.)

The STL is likely to influence the organization of many ? perhaps most ? C++ libraries, so it's important that you

Page 435: Effective C++ and More Effective C++

be familiar with its general principles. They are not difficult to understand. The STL is based on threefundamental concepts: containers, iterators, and algorithms. Containers hold collections of objects. Iterators arepointer-like objects that let you walk through STL containers just as you'd use pointers to walk through built-inarrays. Algorithms are functions that work on STL containers and that use iterators to help them do their work.

It is easiest to understand the STL view of the world if we remind ourselves of the C++ (and C) rules for arrays.There is really only one rule we need to know: a pointer to an array can legitimately point to any element of thearray or to one element beyond the end of the array. If the pointer points to the element beyond the end of thearray, it can be compared only to other pointers to the array; the results of dereferencing it are undefined.

We can take advantage of this rule to write a function to find a particular value in an array. For an array ofintegers, our function might look like this:

int * find(int *begin, int *end, int value)

{

while (begin != end && *begin != value) ++begin;

return begin;

}

This function looks for value in the range between begin and end (excluding end ? end points to one beyond theend of the array) and returns a pointer to the first occurrence of value in the array; if none is found, it returnsend.

Returning end seems like a funny way to signal a fruitless search. Wouldn't 0 (the null pointer) be better?Certainly null seems more natural, but that doesn't make it "better." The find function must return somedistinctive pointer value to indicate the search failed, and for this purpose, the end pointer is as good as the nullpointer. In addition, as we'll soon see, the end pointer generalizes to other types of containers better than the nullpointer.

Frankly, this is probably not the way you'd write the find function, but it's not unreasonable, and it generalizesastonishingly well. If you followed this simple example, you have mastered most of the ideas on which the STLis founded.

You could use the find function like this: int values[50];

...

int *firstFive = find(values, // search the range

values+50, // values[0] - values[49]

5); // for the value 5

if (firstFive != values+50) { // did the search succeed?

... // yes

}

else {

... // no, the search failed

}

You can also use find to search subranges of the array:

int *firstFive = find(values, // search the range

values+10, // values[0] - values[9]

5); // for the value 5

int age = 36;

...

Page 436: Effective C++ and More Effective C++

int *firstValue = find(values+10, // search the range

values+20, // values[10] - values[19]

age); // for the value in age

There's nothing inherent in the find function that limits its applicability to arrays of ints, so it should really be atemplate:

template<class T>

T * find(T *begin, T *end, const T& value)

{

while (begin != end && *begin != value) ++begin;

return begin;

}

In the transformation to a template, notice how we switched from pass-by-value for value topass-by-reference-to-const. That's because now that we're passing arbitrary types around, we have to worryabout the cost of pass-by-value. Each by-value parameter costs us a call to the parameter's constructor anddestructor every time the function is invoked. We avoid these costs by using pass-by-reference, which involvesno object construction or destruction (see Item E22).

This template is nice, but it can be generalized further. Look at the operations on begin and end. The only onesused are comparison for inequality, dereferencing, prefix increment (see Item 6), and copying (for the function'sreturn value ? see Item 19). These are all operations we can overload, so why limit find to using pointers? Whynot allow any object that supports these operations to be used in addition to pointers? Doing so would free thefind function from the built-in meaning of pointer operations. For example, we could define a pointer-like objectfor a linked list whose prefix increment operator moved us to the next element in the list.

This is the concept behind STL iterators. Iterators are pointer-like objects designed for use with STLcontainers. They are first cousins to the smart pointers of Item 28, but smart pointers tend to be more ambitiousin what they do than do STL iterators. From a technical viewpoint, however, they are implemented using thesame techniques.

Embracing the notion of iterators as pointer-like objects, we can replace the pointers in find with iterators, thusrewriting find like this:

template<class Iterator, class T>

Iterator find(Iterator begin, Iterator end, const T& value)

{

while (begin != end && *begin != value) ++begin;

return begin;

}

Congratulations! You have just written part of the Standard Template Library. The STL contains dozens ofalgorithms that work with containers and iterators, and find is one of them.

Containers in STL include bitset, vector, list, deque, queue, priority_queue, stack, set, and map, and you canapply find to any of these container types:

list<char> charList; // create STL list object

// for holding chars

...

// find the first occurrence of 'x' in charList

list<char>::iterator it = find(charList.begin(),

charList.end(),

'x');

"Whoa!", I hear you cry, "This doesn't look anything like it did in the array examples above!" Ah, but it does;you just have to know what to look for.

To call find for a list object, you need to come up with iterators that point to the first element of the list and to

Page 437: Effective C++ and More Effective C++

one past the last element of the list. Without some help from the list class, this is a difficult task, because youhave no idea how a list is implemented. Fortunately, list (like all STL containers) obliges by providing themember functions begin and end. These member functions return the iterators you need, and it is those iteratorsthat are passed into the first two parameters of find above.

When find is finished, it returns an iterator object that points to the found element (if there is one) or tocharList.end() (if there's not). Because you know nothing about how list is implemented, you also know nothingabout how iterators into lists are implemented. How, then, are you to know what type of object is returned byfind? Again, the list class, like all STL containers, comes to the rescue: it provides a typedef, iterator, that is thetype of iterators into lists. Since charList is a list of chars, the type of an iterator into such a list islist<char>::iterator, and that's what's used in the example above. (Each STL container class actually defines twoiterator types, iterator and const_iterator. The former acts like a normal pointer, the latter like apointer-to-const.)

Exactly the same approach can be used with the other STL containers. Furthermore, C++ pointers are STLiterators, so the original array examples work with the STL find function, too:

int values[50];

...

int *firstFive = find(values, values+50, 5); // fine, calls

// STL find

At its core, STL is very simple. It is just a collection of class and function templates that adhere to a set ofconventions. The STL collection classes provide functions like begin and end that return iterator objects of typesdefined by the classes. The STL algorithm functions move through collections of objects by using iteratorobjects over STL collections. STL iterators act like pointers. That's really all there is to it. There's no biginheritance hierarchy, no virtual functions, none of that stuff. Just some class and function templates and a set ofconventions to which they all subscribe.

Which leads to another revelation: STL is extensible. You can add your own collections, algorithms, anditerators to the STL family. As long as you follow the STL conventions, the standard STL collections will workwith your algorithms and your collections will work with the standard STL algorithms. Of course, yourtemplates won't be part of the standard C++ library, but they'll be built on the same principles and will be just asreusable.

There is much more to the C++ library than I've described here. Before you can use the library effectively, youmust learn more about it than I've had room to summarize, and before you can write your own STL-complianttemplates, you must learn more about the conventions of the STL. The standard C++ library is far richer than theC library, and the time you take to familiarize yourself with it is time well spent (see also Item E49).Furthermore, the design principles embodied by the library ? those of generality, extensibility, customizability,efficiency, and reusability ? are well worth learning in their own right. By studying the standard C++ library,you not only increase your knowledge of the ready-made components available for use in your software, youlearn how to apply the features of C++ more effectively, and you gain insight into how to design better librariesof your own.

Back to Item 34: Understand how to combine C++ and C in the same program Continue to Recommended Reading

Page 438: Effective C++ and More Effective C++

Back to Item 35: : Familiarize yourself with the language standard Continue to An auto_ptr Implementation

Recommended Reading

So your appetite for information on C++ remains unsated. Fear not, there's more ? much more. In the sectionsthat follow, I put forth my recommendations for further reading on C++. It goes without saying that suchrecommendations are both subjective and selective, but in view of the litigious age in which we live, it'sprobably a good idea to say it anyway.

Books

There are hundreds ? possibly thousands ? of books on C++, and new contenders join the fray with greatfrequency. I haven't seen all these books, much less read them, but my experience has been that while somebooks are very good, some of them, well, some of them aren't.

What follows is the list of books I find myself consulting when I have questions about software development inC++. Other good books are available, I'm sure, but these are the ones I use, the ones I can truly recommend.

A good place to begin is with the books that describe the language itself. Unless you are crucially dependent onthe nuances of the °official standards documents, I suggest you do, too. °The Annotated C++ Reference Manual, Margaret A. Ellis and Bjarne Stroustrup, Addison-Wesley, 1990,ISBN 0-201-51459-1.

°The Design and Evolution of C++, Bjarne Stroustrup, Addison-Wesley, 1994, ISBN 0-201-54330-3.

These books contain not just a description of what's in the language, they also explain the rationale behind thedesign decisions ? something you won't find in the official standard documents. The Annotated C++ ReferenceManual is now incomplete (several language features have been added since it was published ? see Item 35)and is in some cases out of date, but it is still the best reference for the core parts of the language, includingtemplates and exceptions. The Design and Evolution of C++ covers most of what's missing in The AnnotatedC++ Reference Manual; the only thing it lacks is a discussion of the Standard Template Library (again, see Item35). These books are not tutorials, they're references, but you can't truly understand C++ unless you understandthe material in these books.

For a more general reference on the language, the standard library, and how to apply it, there is no better placeto look than the book by the man responsible for C++ in the first place: °The C++ Programming Language (Third Edition), Bjarne Stroustrup, Addison-Wesley, 1997, ISBN0-201-88954-4.

Stroustrup has been intimately involved in the language's design, implementation, application, andstandardization since its inception, and he probably knows more about it than anybody else does. Hisdescriptions of language features make for dense reading, but that's primarily because they contain so muchinformation. The chapters on the standard C++ library provide a good introduction to this crucial aspect ofmodern C++.

If you're ready to move beyond the language itself and are interested in how to apply it effectively, you mightconsider my other book on the subject: °Effective C++, Second Edition: 50 Specific Ways to Improve Your Programs and Designs, Scott Meyers,Addison-Wesley, 1998, ISBN 0-201-92488-9.

That book is organized similarly to this one, but it covers different (arguably more fundamental) material.

A book pitched at roughly the same level as my Effective C++ books, but covering different topics, is °C++ Strategies and Tactics, Robert Murray, Addison-Wesley, 1993, ISBN 0-201-56382-7.

Murray's book is especially strong on the fundamentals of template design, a topic to which he devotes twochapters. He also includes a chapter on the important topic of migrating from C development to C++development. Much of my discussion on reference counting (see Item 29) is based on the ideas in C++

Page 439: Effective C++ and More Effective C++

Strategies and Tactics.

If you're the kind of person who likes to learn proper programming technique by reading code, the book for youis °C++ Programming Style, Tom Cargill, Addison-Wesley, 1992, ISBN 0-201-56365-7.

Each chapter in this book starts with some C++ software that has been published as an example of how to dosomething correctly. Cargill then proceeds to dissect ? nay, vivisect ? each program, identifying likely troublespots, poor design choices, brittle implementation decisions, and things that are just plain wrong. He theniteratively rewrites each example to eliminate the weaknesses, and by the time he's done, he's produced codethat is more robust, more maintainable, more efficient, and more portable, and it still fulfills the originalproblem specification. Anybody programming in C++ would do well to heed the lessons of this book, but it isespecially important for those involved in code inspections.

One topic Cargill does not discuss in C++ Programming Style is exceptions. He turns his critical eye to thislanguage feature in the following article, however, which demonstrates why writing exception-safe code is moredifficult than most programmers realize: "Exception Handling: A False Sense of Security," °C++ Report, Volume 6, Number 9, November-December1994, pages 21-24.

If you are contemplating the use of exceptions, read this article before you proceed.

Once you've mastered the basics of C++ and are ready to start pushing the envelope, you must familiarizeyourself with °Advanced C++: Programming Styles and Idioms, James Coplien, Addison-Wesley, 1992, ISBN0-201-54855-0.

I generally refer to this as "the LSD book," because it's purple and it will expand your mind. Coplien coverssome straightforward material, but his focus is really on showing you how to do things in C++ you're notsupposed to be able to do. You want to construct objects on top of one another? He shows you how. You want tobypass strong typing? He gives you a way. You want to add data and functions to classes as your programs arerunning? He explains how to do it. Most of the time, you'll want to steer clear of the techniques he describes, butsometimes they provide just the solution you need for a tricky problem you're facing. Furthermore, it'silluminating just to see what kinds of things can be done with C++. This book may frighten you, it may dazzleyou, but when you've read it, you'll never look at C++ the same way again.

If you have anything to do with the design and implementation of C++ libraries, you would be foolhardy tooverlook °Designing and Coding Reusable C++, Martin D. Carroll and Margaret A. Ellis, Addison-Wesley, 1995, ISBN0-201-51284-X.

Carroll and Ellis discuss many practical aspects of library design and implementation that are simply ignored byeverybody else. Good libraries are small, fast, extensible, easily upgraded, graceful during templateinstantiation, powerful, and robust. It is not possible to optimize for each of these attributes, so one must maketrade-offs that improve some aspects of a library at the expense of others. Designing and Coding Reusable C++examines these trade-offs and offers down-to-earth advice on how to go about making them.

Regardless of whether you write software for scientific and engineering applications, you owe yourself a lookat °Scientific and Engineering C++, John J. Barton and Lee R. Nackman, Addison-Wesley, 1994, ISBN0-201-53393-6.

The first part of the book explains C++ for FORTRAN programmers (now there's an unenviable task), but thelatter parts cover techniques that are relevant in virtually any domain. The extensive material on templates isclose to revolutionary; it's probably the most advanced that's currently available, and I suspect that when you'veseen the miracles these authors perform with templates, you'll never again think of them as little more thansouped-up macros.

Finally, the emerging discipline of patterns in object-oriented software development (see page 123) is

Page 440: Effective C++ and More Effective C++

described in °Design Patterns: Elements of Reusable Object-Oriented Software, Erich Gamma, Richard Helm, RalphJohnson, and John Vlissides, Addison-Wesley, 1995, ISBN 0-201-63361-2.

This book provides an overview of the ideas behind patterns, but its primary contribution is a catalogue of 23fundamental patterns that are useful in many application areas. A stroll through these pages will almost surelyreveal a pattern you've had to invent yourself at one time or another, and when you find one, you're almostcertain to discover that the design in the book is superior to the ad-hoc approach you came up with. The namesof the patterns here have already become part of an emerging vocabulary for object-oriented design; failure toknow these names may soon be hazardous to your ability to communicate with your colleagues. A particularstrength of the book is its emphasis on designing and implementing software so that future evolution is gracefullyaccommodated (see Items 32 and 33).

Design Patterns is also available as a CD-ROM: °Design Patterns CD: Elements of Reusable Object-Oriented Software, Erich Gamma, Richard Helm, RalphJohnson, and John Vlissides, Addison-Wesley, 1998, ISBN 0-201-63498-8.

Magazines

For hard-core C++ programmers, there's really only one game in town: °C++ Report, SIGS Publications, New York, NY.

The magazine has made a conscious decision to move away from its "C++ only" roots, but the increasedcoverage of domain- and system-specific programming issues is worthwhile in its own right, and the material onC++, if occasionally a bit off the deep end, continues to be the best available.

If you're more comfortable with C than with C++, or if you find the C++ Report's material too extreme to beuseful, you may find the articles in this magazine more to your taste: °C/C++ Users Journal, Miller Freeman, Inc., Lawrence, KS.

As the name suggests, this covers both C and C++. The articles on C++ tend to assume a weaker backgroundthan those in the C++ Report. In addition, the editorial staff keeps a tighter rein on its authors than does theReport, so the material in the magazine tends to be relatively mainstream. This helps filter out ideas on thelunatic fringe, but it also limits your exposure to techniques that are truly cutting-edge.

Usenet Newsgroups

Three Usenet newsgroups are devoted to C++. The general-purpose anything-goes newsgroup is °comp.lang.c++ . The postings there run the gamut from detailed explanations of advanced programmingtechniques to rants and raves by those who love or hate C++ to undergraduates the world over asking for helpwith the homework assignments they neglected until too late. Volume in the newsgroup is extremely high. Unlessyou have hours of free time on your hands, you'll want to employ a filter to help separate the wheat from thechaff. Get a good filter ? there's a lot of chaff.

In November 1995, a moderated version of comp.lang.c++ was created. Named °comp.lang.c++.moderated, thisnewsgroup is also designed for general discussion of C++ and related issues, but the moderators aim to weedout implementation-specific questions and comments, questions covered in the extensive °on-line FAQ("Frequently Asked Questions" list), flame wars, and other matters of little interest to most C++ practitioners.

A more narrowly focused newsgroup is °comp.std.c++, which is devoted to a discussion of °the C++ standarditself. Language lawyers abound in this group, but it's a good place to turn if your picky questions about C++ gounanswered in the references otherwise available to you. The newsgroup is moderated, so the signal-to-noiseratio is quite good; you won't see any pleas for homework assistance here.

Back to Item 35: : Familiarize yourself with the language standard Continue to An auto_ptr Implementation

Page 441: Effective C++ and More Effective C++

Back to Recommended Reading Continue to Books' Index

An auto_ptr Implementation

Items 9, 10, 26, 31 and 32 attest to the remarkable utility of the auto_ptr template. Unfortunately, few compilerscurrently ship with a "correct" implementation.1 Items 9 and 28 sketch how you might write one yourself, but it'snice to have more than a sketch when embarking on real-world projects.

Below are two presentations of an implementation for auto_ptr. The first presentation documents the classinterface and implements all the member functions outside the class definition. The second implements eachmember function within the class definition. Stylistically, the second presentation is inferior to the first, becauseit fails to separate the class interface from its implementation. However, auto_ptr yields simple classes, and thesecond presentation brings that out much more clearly than does the first.

Here is auto_ptr with its interface documented: template<class T>

class auto_ptr {

public:

explicit auto_ptr(T *p = 0); // see Item 5 for a

// description of "explicit"

template<class U> // copy constructor member

auto_ptr(auto_ptr<U>& rhs); // template (see Item 28):

// initialize a new auto_ptr

// with any compatible

// auto_ptr

~auto_ptr();

template<class U> // assignment operator

auto_ptr<T>& // member template (see

operator=(auto_ptr<U>& rhs); // Item 28): assign from any

// compatible auto_ptr

T& operator*() const; // see Item 28

T* operator->() const; // see Item 28

T* get() const; // return value of current

// dumb pointer

T* release(); // relinquish ownership of

// current dumb pointer and

// return its value

void reset(T *p = 0); // delete owned pointer;

// assume ownership of p

private:

T *pointee;

template<class U> // make all auto_ptr classes

friend class auto_ptr<U>; // friends of one another

};

template<class T>

inline auto_ptr<T>::auto_ptr(T *p)

: pointee(p)

{}

template<class T>

inline auto_ptr<T>::auto_ptr(auto_ptr<U>& rhs)

: pointee(rhs.release())

{}

template<class T>

inline auto_ptr<T>::~auto_ptr()

{ delete pointee; }

template<class T>

template<class U>

inline auto_ptr<T>& auto_ptr<T>::operator=(auto_ptr<U>& rhs)

{

if (this != &rhs) reset(rhs.release());

return *this;

}

template<class T>

Page 442: Effective C++ and More Effective C++

inline T& auto_ptr<T>::operator*() const

{ return *pointee; }

template<class T>

inline T* auto_ptr<T>::operator->() const

{ return pointee; }

template<class T>

inline T* auto_ptr<T>::get() const

{ return pointee; }

template<class T>

inline T* auto_ptr<T>::release()

{

T *oldPointee = pointee;

pointee = 0;

return oldPointee;

}

template<class T>

inline void auto_ptr<T>::reset(T *p)

{

if (pointee != p) {

delete pointee;

pointee = p;

}

}

Here is auto_ptr with all the functions defined in the class definition. As you can see, there's no brain surgerygoing on here:

template<class T>

class auto_ptr {

public:

explicit auto_ptr(T *p = 0): pointee(p) {}

template<class U>

auto_ptr(auto_ptr<U>& rhs): pointee(rhs.release()) {}

~auto_ptr() { delete pointee; }

template<class U>

auto_ptr<T>& operator=(auto_ptr<U>& rhs)

{

if (this != &rhs) reset(rhs.release());

return *this;

}

T& operator*() const { return *pointee; }

T* operator->() const { return pointee; }

T* get() const { return pointee; }

T* release()

{

T *oldPointee = pointee;

pointee = 0;

return oldPointee;

}

void reset(T *p = 0)

{

if (pointee != p) {

delete pointee;

pointee = p;

}

}

private:

T *pointee;

template<class U> friend class auto_ptr<U>;

};

If your compilers don't yet support explicit, you may safely #define it out of existence:

Page 443: Effective C++ and More Effective C++

#define explicit

This won't make auto_ptr any less functional, but it will render it slightly less safe. For details, see Item 5.

If your compilers lack support for member templates, you can use the non-template auto_ptr copy constructorand assignment operator described in Item 28. This will make your auto_ptrs less convenient to use, but there is,alas, no way to approximate the behavior of member templates. If member templates (or other language features,for that matter) are important to you, let your compiler vendors know. The more customers ask for new languagefeatures, the sooner vendors will implement them.

Back to Recommended Reading Continue to Books' Index

1 This is primarily because the specification for auto_ptr as for years been a moving target. The finalspecification was adopted only in November 1997. For details, consult °the auto_ptr information at this book'sWWW Site. Note that the auto_ptr described here omits a few details present in the official version, such as thefact that auto_ptr is in the std namespace (see Item 35) and that its member functions promise not to throwexceptions. Return