Uploaded by xoo23638

Effective C++ Digital Collection (Scott Meyers) (2005)

advertisement
Effective C++
Digital Collection
140 Ways to Improve Your Programming
Upper Saddle River, NJ • Boston • Indianapolis • San Francisco
New York • Toronto • Montreal • London • Munich • Paris • Madrid
Capetown • Sydney • Tokyo • Singapore • Mexico City
Note from the Publisher
The Effective C++ Digital Collection includes three bestselling C++ eBooks:
• Effective C++: 55 Specific Ways to Improve Your Programs and Designs,
Third Edition
• More Effective C++: 35 New Ways to Improve Your Programs and Designs
• Effective STL: 50 Specific Ways to Improve Your Use of the Standard
Template Library
By combining these seminal works into one eBook collection, you have easy
access to the pragmatic, down-to-earth advice and proven wisdom that Scott
Meyers is known for. This collection is essential reading for anyone working
with C++ and STL.
To simplify access, we’ve appended “A” to pages of Effective C++, “B” to pages
of More Effective C++, and “C” to pages of Effective STL. This enabled us to
produce a single, comprehensive table of contents and dedicated indexes so
that you can easily link to the topics you want and navigate between the
books. We hope you find this collection useful!
—The editorial and production teams at Addison-Wesley
Many of the designations used by manufacturers and sellers to distinguish their products are claimed as
trademarks. Where those designations appear in this book, and the publisher was aware of a trademark
claim, the designations have been printed with initial capital letters or in all capitals.
The author and publisher have taken care in the preparation of this book, but make no expressed or
implied warranty of any kind and assume no responsibility for errors or omissions. No liability is
assumed for incidental or consequential damages in connection with or arising out of the use of the
information or programs contained herein.
The publisher offers excellent discounts on this book when ordered in quantity for bulk purchases or
special sales, which may include electronic versions and/or custom covers and content particular to your
business, training goals, marketing focus, and branding interests. For more information, please contact:
U.S. Corporate and Government Sales
(800) 382-3419
corpsales@pearsontechgroup.com
For sales outside the United States, please contact:
International Sales
international@pearson.com
Visit us on the Web: informit.com/aw
Copyright © 2012 Pearson Education, Inc.
All rights reserved. Printed in the United States of America. This publication is protected by copyright,
and permission must be obtained from the publisher prior to any prohibited reproduction, storage in a
retrieval system, or transmission in any form or by any means, electronic, mechanical, photocopying,
recording, or likewise. To obtain permission to use material from this work, please submit a written
request to Pearson Education, Inc., Permissions Department, One Lake Street, Upper Saddle River, New
Jersey 07458, or you may fax your request to (201) 236-3290.
ISBN-13: 978-0-13-297919-1
ISBN-10:
0-13-297919-5
Second release, September 2012
This page intentionally left blank
Contents
Effective C++
Introduction
Chapter 1: Accustoming Yourself to C++
Item
Item
Item
Item
1:
2:
3:
4:
View C++ as a federation of languages.
Prefer consts, enums, and inlines to #defines.
Use const whenever possible.
Make sure that objects are initialized before
they’re used.
Chapter 2: Constructors, Destructors,
and Assignment Operators
Item 5:
Item 6:
Know what functions C++ silently writes and calls.
Explicitly disallow the use of compiler-generated
functions you do not want.
Item 7: Declare destructors virtual in polymorphic base
classes.
Item 8: Prevent exceptions from leaving destructors.
Item 9: Never call virtual functions during construction
or destruction.
Item 10: Have assignment operators return a reference
to *this.
Item 11: Handle assignment to self in operator=.
Item 12: Copy all parts of an object.
1A
11A
11A
13A
17A
26A
34A
34A
37A
40A
44A
48A
52A
53A
57A
Chapter 3: Resource Management
Item 13: Use objects to manage resources.
Item 14: Think carefully about copying behavior in resourcemanaging classes.
Item 15: Provide access to raw resources in resourcemanaging classes.
Item 16: Use the same form in corresponding uses of new
and delete.
Item 17: Store newed objects in smart pointers in standalone
statements.
Chapter 4: Designs and Declarations
61A
61A
66A
69A
73A
75A
78A
Item 18: Make interfaces easy to use correctly and hard to
use incorrectly.
78A
Item 19: Treat class design as type design.
84A
86A
Item 20: Prefer pass-by-reference-to-const to pass-by-value.
Item 21: Don’t try to return a reference when you must
return an object.
90A
94A
Item 22: Declare data members private.
Item 23: Prefer non-member non-friend functions to member
functions.
98A
Item 24: Declare non-member functions when type
conversions should apply to all parameters.
102A
106A
Item 25: Consider support for a non-throwing swap.
Chapter 5: Implementations
Item
Item
Item
Item
Item
Item
26:
27:
28:
29:
30:
31:
Postpone variable definitions as long as possible.
Minimize casting.
Avoid returning “handles” to object internals.
Strive for exception-safe code.
Understand the ins and outs of inlining.
Minimize compilation dependencies between files.
Chapter 6: Inheritance and
Object-Oriented Design
Item 32: Make sure public inheritance models “is-a.”
Item 33: Avoid hiding inherited names.
Item 34: Differentiate between inheritance of interface and
inheritance of implementation.
Item 35: Consider alternatives to virtual functions.
113A
113A
116A
123A
127A
134A
140A
149A
150A
156A
161A
169A
Item 36: Never redefine an inherited non-virtual function.
Item 37: Never redefine a function’s inherited default
parameter value.
Item 38: Model “has-a” or “is-implemented-in-terms-of”
through composition.
Item 39: Use private inheritance judiciously.
Item 40: Use multiple inheritance judiciously.
178A
180A
184A
187A
192A
Chapter 7: Templates and Generic Programming 199A
Item 41: Understand implicit interfaces and compile-time
polymorphism.
Item 42: Understand the two meanings of typename.
Item 43: Know how to access names in templatized base
classes.
Item 44: Factor parameter-independent code out of
templates.
Item 45: Use member function templates to accept “all
compatible types.”
Item 46: Define non-member functions inside templates
when type conversions are desired.
Item 47: Use traits classes for information about types.
Item 48: Be aware of template metaprogramming.
Chapter 8: Customizing new and delete
Item 49: Understand the behavior of the new-handler.
Item 50: Understand when it makes sense to replace new
and delete.
Item 51: Adhere to convention when writing new and delete.
Item 52: Write placement delete if you write placement new.
Chapter 9: Miscellany
Item 53: Pay attention to compiler warnings.
Item 54: Familiarize yourself with the standard library,
including TR1.
Item 55: Familiarize yourself with Boost.
199A
203A
207A
212A
218A
222A
226A
233A
239A
240A
247A
252A
256A
262A
262A
263A
269A
Appendix A: Beyond Effective C++
273A
Appendix B: Item Mappings Between Second
and Third Editions
277A
Index
280A
More Effective C++
Introduction
1B
Basics
9B
Item
Item
Item
Item
1:
2:
3:
4:
Distinguish between pointers and references.
Prefer C++-style casts.
Never treat arrays polymorphically.
Avoid gratuitous default constructors.
Operators
Item 5:
Item 6:
Item 7:
Item 8:
Be wary of user-defined conversion functions.
Distinguish between prefix and postfix forms of
increment and decrement operators.
Never overload &&, ||, or ,.
Understand the different meanings of new and delete.
Exceptions
Item
Item
Item
Item
9:
10:
11:
12:
Use destructors to prevent resource leaks.
Prevent resource leaks in constructors.
Prevent exceptions from leaving destructors.
Understand how throwing an exception differs from
passing a parameter or calling a virtual function.
Item 13: Catch exceptions by reference.
Item 14: Use exception specifications judiciously.
Item 15: Understand the costs of exception handling.
Efficiency
Item
Item
Item
Item
Item
Item
Item
Item
Item
16:
17:
18:
19:
20:
21:
22:
23:
24:
Remember the 80-20 rule.
Consider using lazy evaluation.
Amortize the cost of expected computations.
Understand the origin of temporary objects.
Facilitate the return value optimization.
Overload to avoid implicit type conversions.
Consider using op= instead of stand-alone op.
Consider alternative libraries.
Understand the costs of virtual functions, multiple
inheritance, virtual base classes, and RTTI.
9B
12B
16B
19B
24B
24B
31B
35B
38B
44B
45B
50B
58B
61B
68B
72B
78B
81B
82B
85B
93B
98B
101B
105B
107B
110B
113B
Techniques
Item
Item
Item
Item
Item
Item
Item
25:
26:
27:
28:
29:
30:
31:
123B
Virtualizing constructors and non-member functions.
Limiting the number of objects of a class.
Requiring or prohibiting heap-based objects.
Smart pointers.
Reference counting.
Proxy classes.
Making functions virtual with respect to more
than one object.
Miscellany
123B
130B
145B
159B
183B
213B
228B
252B
Item 32: Program in the future tense.
Item 33: Make non-leaf classes abstract.
Item 34: Understand how to combine C++ and C in the
same program.
Item 35: Familiarize yourself with the language standard.
252B
258B
270B
277B
Recommended Reading
285B
An auto_ptr Implementation
291B
General Index
295B
Index of Example Classes, Functions,
and Templates
313B
Effective STL
Introduction
Chapter 1: Containers
Item 1:
Item 2:
Item 3:
Item 4:
Item 5:
Choose your containers with care.
Beware the illusion of container-independent code.
Make copying cheap and correct for objects in
containers.
Call empty instead of checking size() against zero.
Prefer range member functions to their single-element
counterparts.
1C
11C
11C
15C
20C
23C
24C
Item 6:
Item 7:
Be alert for C++’s most vexing parse.
33C
When using containers of newed pointers, remember to
delete the pointers before the container is destroyed. 36C
40C
Item 8: Never create containers of auto_ptrs.
Item 9: Choose carefully among erasing options.
43C
Item 10: Be aware of allocator conventions and restrictions.
48C
Item 11: Understand the legitimate uses of custom allocators. 54C
Item 12: Have realistic expectations about the thread safety
of STL containers.
58C
Chapter 2: vector and string
Item
Item
Item
Item
Item
Item
13:
14:
15:
16:
17:
18:
63C
Prefer vector and string to dynamically allocated arrays.
Use reserve to avoid unnecessary reallocations.
Be aware of variations in string implementations.
Know how to pass vector and string data to legacy APIs.
Use “the swap trick” to trim excess capacity.
Avoid using vector<bool>.
Chapter 3: Associative Containers
Item 19: Understand the difference between equality and
equivalence.
Item 20: Specify comparison types for associative containers
of pointers.
Item 21: Always have comparison functions return false for
equal values.
Item 22: Avoid in-place key modification in set and multiset.
Item 23: Consider replacing associative containers with
sorted vectors.
Item 24: Choose carefully between map::operator[] and
map::insert when efficiency is important.
Item 25: Familiarize yourself with the nonstandard hashed
containers.
Chapter 4: Iterators
Item 26: Prefer iterator to const_iterator, reverse_iterator, and
const_reverse_iterator.
Item 27: Use distance and advance to convert a container’s
const_iterators to iterators.
63C
66C
68C
74C
77C
79C
83C
83C
88C
92C
95C
100C
106C
111C
116C
116C
120C
Item 28: Understand how to use a reverse_iterator’s base
iterator.
Item 29: Consider istreambuf_iterators for character-bycharacter input.
Chapter 5: Algorithms
Item 30: Make sure destination ranges are big enough.
Item 31: Know your sorting options.
Item 32: Follow remove-like algorithms by erase if you really
want to remove something.
Item 33: Be wary of remove-like algorithms on containers of
pointers.
Item 34: Note which algorithms expect sorted ranges.
Item 35: Implement simple case-insensitive string
comparisons via mismatch or lexicographical_compare.
Item 36: Understand the proper implementation of copy_if.
Item 37: Use accumulate or for_each to summarize ranges.
Chapter 6: Functors, Functor Classes,
Functions, etc.
Item
Item
Item
Item
38:
39:
40:
41:
Design functor classes for pass-by-value.
Make predicates pure functions.
Make functor classes adaptable.
Understand the reasons for ptr_fun, mem_fun,
and mem_fun_ref.
Item 42: Make sure less<T> means operator<.
Chapter 7: Programming with the STL
Item 43: Prefer algorithm calls to hand-written loops.
Item 44: Prefer member functions to algorithms with the
same names.
Item 45: Distinguish among count, find, binary_search,
lower_bound, upper_bound, and equal_range.
Item 46: Consider function objects instead of functions as
algorithm parameters.
Item 47: Avoid producing write-only code.
Item 48: Always #include the proper headers.
Item 49: Learn to decipher STL-related compiler diagnostics.
Item 50: Familiarize yourself with STL-related web sites.
123C
126C
128C
129C
133C
139C
143C
146C
150C
154C
156C
162C
162C
166C
169C
173C
177C
181C
181C
190C
192C
201C
206C
209C
210C
217C
Bibliography
225C
Appendix A: Locales and Case-Insensitive
String Comparisons
229C
Appendix B: Remarks on Microsoft’s
STL Platforms
239C
Index
245C
Effective C++
Third Edition
Praise for Effective C++, Third Edition
“Scott Meyers’ book, Effective C++, Third Edition, is distilled programming experience —
experience that you would otherwise have to learn the hard way. This book is a great
resource that I recommend to everybody who writes C++ professionally.”
— Peter Dulimov, ME, Engineer, Ranges and Assessing Unit, NAVSYSCOM,
Australia
“The third edition is still the best book on how to put all of the pieces of C++ together
in an efficient, cohesive manner. If you claim to be a C++ programmer, you must read
this book.”
— Eric Nagler, Consultant, Instructor, and author of Learning C++
“The first edition of this book ranks among the small (very small) number of books
that I credit with significantly elevating my skills as a ‘professional’ software developer. Like the others, it was practical and easy to read, but loaded with important
advice. Effective C++, Third Edition, continues that tradition. C++ is a very powerful
programming language. If C gives you enough rope to hang yourself, C++ is a hardware store with lots of helpful people ready to tie knots for you. Mastering the points
discussed in this book will definitely increase your ability to effectively use C++ and
reduce your stress level.”
— Jack W. Reeves, Chief Executive Officer, Bleading Edge Software Technologies
“Every new developer joining my team has one assignment — to read this book.”
— Michael Lanzetta, Senior Software Engineer
“I read the first edition of Effective C++ about nine years ago, and it immediately
became my favorite book on C++. In my opinion, Effective C++, Third Edition, remains
a mustread today for anyone who wishes to program effectively in C++. We would live
in a better world if C++ programmers had to read this book before writing their first
line of professional C++ code.”
— Danny Rabbani, Software Development Engineer
“I encountered the first edition of Scott Meyers’ Effective C++ as a struggling programmer in the trenches, trying to get better at what I was doing. What a lifesaver! I found
Meyers’ advice was practical, useful, and effective, fulfilling the promise of the title
100 percent. The third edition brings the practical realities of using C++ in serious
development projects right up to date, adding chapters on the language’s very latest
issues and features. I was delighted to still find myself learning something interesting
and new from the latest edition of a book I already thought I knew well.”
— Michael Topic, Technical Program Manager
“From Scott Meyers, the guru of C++, this is the definitive guide for anyone who
wants to use C++ safely and effectively, or is transitioning from any other OO language to C++. This book has valuable information presented in a clear, concise,
entertaining, and insightful manner.”
— Siddhartha Karan Singh, Software Developer
“This should be the second book on C++ that any developer should read, after a general introductory text. It goes beyond the how and what of C++ to address the why
and wherefore. It helped me go from knowing the syntax to understanding the philosophy of C++ programming.”
— Timothy Knox, Software Developer
“This is a fantastic update of a classic C++ text. Meyers covers a lot of new ground in this
volume, and every serious C++ programmer should have a copy of this new edition.”
— Jeffrey Somers, Game Programmer
“Effective C++, Third Edition, covers the things you should be doing when writing code
and does a terrific job of explaining why those things are important. Think of it as
best practices for writing C++.”
— Jeff Scherpelz, Software Development Engineer
“As C++ embraces change, Scott Meyers’ Effective C++, Third Edition, soars to remain
in perfect lock-step with the language. There are many fine introductory books on
C++, but exactly one second book stands head and shoulders above the rest, and
you’re holding it. With Scott guiding the way, prepare to do some soaring of your own!”
— Leor Zolman, C++ Trainer and Pundit, BD Software
“This book is a must-have for both C++ veterans and newbies. After you have finished
reading it, it will not collect dust on your bookshelf — you will refer to it all the time.”
— Sam Lee, Software Developer
“Reading this book transforms ordinary C++ programmers into expert C++ programmers, step-by-step, using 55 easy-to-read items, each describing one technique or tip.”
— Jeffrey D. Oldham, Ph.D., Software Engineer, Google
“Scott Meyers’ Effective C++ books have long been required reading for new and experienced C++ programmers alike. This new edition, incorporating almost a decade’s
worth of C++ language development, is his most content-packed book yet. He does
not merely describe the problems inherent in the language, but instead he provides
unambiguous and easy-to-follow advice on how to avoid the pitfalls and write ‘effective C++.’ I expect every C++ programmer to have read it.”
— Philipp K. Janert, Ph.D., Software Development Manager
“Each previous edition of Effective C++ has been the must-have book for developers
who have used C++ for a few months or a few years, long enough to stumble into
the traps latent in this rich language. In this third edition, Scott Meyers extensively
refreshes his sound advice for the modern world of new language and library features
and the programming styles that have evolved to use them. Scott’s engaging writing
style makes it easy to assimilate his guidelines on your way to becoming an effective
C++ developer.”
— David Smallberg, Instructor, DevelopMentor; Lecturer, Computer Science, UCLA
“Effective C++ has been completely updated for twenty-first-century C++ practice and
can continue to claim to be the first second book for all C++ practitioners.”
— Matthew Wilson, Ph.D., author of Imperfect C++
The Addison-Wesley
Professional Computing Series
Visit informit.com/series/professionalcomputing
for a complete list of available publications.
The Addison-Wesley Professional Computing Series was created in 1990 to
provide serious programmers and networking professionals with well-written
and practical reference books. There are few places to turn for accurate and
authoritative books on current and cutting-edge technology. We hope that our
books will help you understand the state of the art in programming languages,
operating systems, and networks.
Consulting Editor Brian W. Kernighan
Make sure to connect with us!
informit.com/socialconnect
Effective C++
Third Edition
55 Specific Ways to Improve Your Programs and Designs
Scott Meyers
▲
▼▼
ADDISON-WESLEY
Boston • San Francisco • New York • Toronto • Montreal
London • Munich • Paris • Madrid
Capetown • Sydney • Tokyo • Singapore • Mexico City
Many of the designations used by manufacturers and sellers to distinguish their products are claimed as
trademarks. Where those designations appear in this book, and the publisher was aware of a trademark
claim, the designations have been printed with initial capital letters or in all capitals.
The author and publisher have taken care in the preparation of this book, but make no expressed or
implied warranty of any kind and assume no responsibility for errors or omissions. No liability is
assumed for incidental or consequential damages in connection with or arising out of the use of the
information or programs contained herein.
The publisher offers excellent discounts on this book when ordered in quantity for bulk purchases or
special sales, which may include electronic versions and/or custom covers and content particular to your
business, training goals, marketing focus, and branding interests. For more information, please contact:
U.S. Corporate and Government Sales
(800) 382-3419
corpsales@pearsontechgroup.com
For sales outside the U.S., please contact:
International Sales
international@pearsoned.com
Visit us on the Web: www.awprofessional.com
Library of Congress Control Number: 2005924388
Copyright © 2005 Pearson Education, Inc.
All rights reserved. Printed in the United States of America. This publication is protected by copyright,
and permission must be obtained from the publisher prior to any prohibited reproduction, storage in a
retrieval system, or transmission in any form or by any means, electronic, mechanical, photocopying,
recording, or likewise. For information regarding permissions, write to:
Pearson Education, Inc.
Rights and Contracts Department
One Lake Street
Upper Saddle River, NJ 07458
ISBN 0-321-33487-6
Text printed in the United States on recycled paper at Courier in Westford, Massachusetts.
First printing, May 2005
For Nancy,
without whom nothing
would be much worth doing
Wisdom and beauty form a very rare combination.
— Petronius Arbiter
Satyricon, XCIV
This page intentionally left blank
And in memory of Persephone,
1995–2004
This page intentionally left blank
Preface
I wrote the original edition of Effective C++ in 1991. When the time
came for a second edition in 1997, I updated the material in important
ways, but, because I didn’t want to confuse readers familiar with the
first edition, I did my best to retain the existing structure: 48 of the
original 50 Item titles remained essentially unchanged. If the book
were a house, the second edition was the equivalent of freshening
things up by replacing carpets, paint, and light fixtures.
For the third edition, I tore the place down to the studs. (There were
times I wished I’d gone all the way to the foundation.) The world of
C++ has undergone enormous change since 1991, and the goal of this
book — to identify the most important C++ programming guidelines in
a small, readable package — was no longer served by the Items I’d established nearly 15 years earlier. In 1991, it was reasonable to assume that C++ programmers came from a C background. Now,
programmers moving to C++ are just as likely to come from Java or
C#. In 1991, inheritance and object-oriented programming were new
to most programmers. Now they’re well-established concepts, and exceptions, templates, and generic programming are the areas where
people need more guidance. In 1991, nobody had heard of design patterns. Now it’s hard to discuss software systems without referring to
them. In 1991, work had just begun on a formal standard for C++.
Now that standard is eight years old, and work has begun on the next
version.
To address these changes, I wiped the slate as clean as I could and
asked myself, “What are the most important pieces of advice for practicing C++ programmers in 2005?” The result is the set of Items in this
new edition. The book has new chapters on resource management
and on programming with templates. In fact, template concerns are
woven throughout the text, because they affect almost everything in
C++. The book also includes new material on programming in the
presence of exceptions, on applying design patterns, and on using the
xii
Preface
Effective C++
new TR1 library facilities. (TR1 is described in Item 54.) It acknowledges that techniques and approaches that work well in singlethreaded systems may not be appropriate in multithreaded systems.
Well over half the material in the book is new. However, most of the
fundamental information in the second edition continues to be important, so I found a way to retain it in one form or another. (You’ll find a
mapping between the second and third edition Items in Appendix B.)
I’ve worked hard to make this book as good as I can, but I have no illusions that it’s perfect. If you feel that some of the Items in this book
are inappropriate as general advice; that there is a better way to accomplish a task examined in the book; or that one or more of the technical discussions is unclear, incomplete, or misleading, please tell me.
If you find an error of any kind — technical, grammatical, typographical, whatever — please tell me that, too. I’ll gladly add to the acknowledgments in later printings the name of the first person to bring each
problem to my attention.
Even with the number of Items expanded to 55, the set of guidelines
in this book is far from exhaustive. But coming up with good rules —
ones that apply to almost all applications almost all the time — is
harder than it might seem. If you have suggestions for additional
guidelines, I would be delighted to hear about them.
I maintain a list of changes to this book since its first printing, including bug fixes, clarifications, and technical updates. The list is available at the Effective C++ Errata web page, http://aristeia.com/BookErrata/
ec++3e-errata.html. If you’d like to be notified when I update the list, I
encourage you to join my mailing list. I use it to make announcements
likely to interest people who follow my professional work. For details,
consult http://aristeia.com/MailingList/.
SCOTT DOUGLAS MEYERS
http://aristeia.com/
STAFFORD, OREGON
APRIL 2005
Acknowledgments
Effective C++ has existed for fifteen years, and I started learning C++
about three years before I wrote the book. The “Effective C++ project”
has thus been under development for nearly two decades. During that
time, I have benefited from the insights, suggestions, corrections, and,
occasionally, dumbfounded stares of hundreds (thousands?) of people. Each has helped improve Effective C++. I am grateful to them all.
I’ve given up trying to keep track of where I learned what, but one general source of information has helped me as long as I can remember:
the Usenet C++ newsgroups, especially comp.lang.c++.moderated and
comp.std.c++. Many of the Items in this book — perhaps most — have
benefited from the vetting of technical ideas at which the participants
in these newsgroups excel.
Regarding new material in the third edition, Steve Dewhurst worked
with me to come up with an initial set of candidate Items. In Item 11,
the idea of implementing operator= via copy-and-swap came from Herb
Sutter’s writings on the topic, e.g., Item 13 of his Exceptional C++ (Addison-Wesley, 2000). RAII (see Item 13) is from Bjarne Stroustrup’s
The C++ Programming Language (Addison-Wesley, 2000). The idea behind Item 17 came from the “Best Practices” section of the Boost
shared_ptr web page, http://boost.org/libs/smart_ptr/shared_ptr.htm#BestPractices and was refined by Item 21 of Herb Sutter’s More Exceptional
C++ (Addison-Wesley, 2002). Item 29 was strongly influenced by Herb
Sutter’s extensive writings on the topic, e.g., Items 8-19 of Exceptional
C++, Items 17–23 of More Exceptional C++, and Items 11–13 of Exceptional C++ Style (Addison-Wesley, 2005); David Abrahams helped me
better understand the three exception safety guarantees. The NVI idiom in Item 35 is from Herb Sutter’s column, “Virtuality,” in the September 2001 C/C++ Users Journal. In that same Item, the Template
Method and Strategy design patterns are from Design Patterns (Addison-Wesley, 1995) by Erich Gamma, Richard Helm, Ralph Johnson,
and John Vlissides. The idea of using the NVI idiom in Item 37 came
xiv
Acknowledgments
Effective C++
from Hendrik Schober. David Smallberg contributed the motivation
for writing a custom set implementation in Item 38. Item 39’s observation that the EBO generally isn’t available under multiple inheritance
is from David Vandevoorde’s and Nicolai M. Josuttis’ C++ Templates
(Addison-Wesley, 2003). In Item 42, my initial understanding about
typename came from Greg Comeau’s C++ and C FAQ (http://www.comeaucomputing.com/techtalk/#typename), and Leor Zolman helped me
realize that my understanding was incorrect. (My fault, not Greg’s.)
The essence of Item 46 is from Dan Saks’ talk, “Making New Friends.”
The idea at the end of Item 52 that if you declare one version of operator new, you should declare them all, is from Item 22 of Herb Sutter’s
Exceptional C++ Style. My understanding of the Boost review process
(summarized in Item 55) was refined by David Abrahams.
Everything above corresponds to who or where I learned about something, not necessarily to who or where the thing was invented or first
published.
My notes tell me that I also used information from Steve Clamage, Antoine Trux, Timothy Knox, and Mike Kaelbling, though, regrettably,
the notes fail to tell me how or where.
Drafts of the first edition were reviewed by Tom Cargill, Glenn Carroll,
Tony Davis, Brian Kernighan, Jak Kirman, Doug Lea, Moises Lejter,
Eugene Santos, Jr., John Shewchuk, John Stasko, Bjarne Stroustrup,
Barbara Tilly, and Nancy L. Urbano. I received suggestions for
improvements that I was able to incorporate in later printings from
Nancy L. Urbano, Chris Treichel, David Corbin, Paul Gibson, Steve
Vinoski, Tom Cargill, Neil Rhodes, David Bern, Russ Williams, Robert
Brazile, Doug Morgan, Uwe Steinmüller, Mark Somer, Doug Moore,
David Smallberg, Seth Meltzer, Oleg Shteynbuk, David Papurt, Tony
Hansen, Peter McCluskey, Stefan Kuhlins, David Braunegg, Paul
Chisholm, Adam Zell, Clovis Tondo, Mike Kaelbling, Natraj Kini, Lars
Nyman, Greg Lutz, Tim Johnson, John Lakos, Roger Scott, Scott
Frohman, Alan Rooks, Robert Poor, Eric Nagler, Antoine Trux, Cade
Roux, Chandrika Gokul, Randy Mangoba, and Glenn Teitelbaum.
Drafts of the second edition were reviewed by Derek Bosch, Tim
Johnson, Brian Kernighan, Junichi Kimura, Scott Lewandowski, Laura
Michaels, David Smallberg, Clovis Tondo, Chris Van Wyk, and Oleg
Zabluda. Later printings benefited from comments from Daniel
Steinberg, Arunprasad Marathe, Doug Stapp, Robert Hall, Cheryl
Ferguson, Gary Bartlett, Michael Tamm, Kendall Beaman, Eric Nagler,
Max Hailperin, Joe Gottman, Richard Weeks, Valentin Bonnard, Jun
He, Tim King, Don Maier, Ted Hill, Mark Harrison, Michael Rubenstein,
Mark Rodgers, David Goh, Brenton Cooper, Andy Thomas-Cramer,
Effective C++
Acknowledgments
xv
Antoine Trux, John Wait, Brian Sharon, Liam Fitzpatrick, Bernd Mohr,
Gary Yee, John O'Hanley, Brady Patterson, Christopher Peterson,
Feliks Kluzniak, Isi Dunietz, Christopher Creutzi, Ian Cooper, Carl
Harris, Mark Stickel, Clay Budin, Panayotis Matsinopoulos, David
Smallberg, Herb Sutter, Pajo Misljencevic, Giulio Agostini, Fredrik
Blomqvist, Jimmy Snyder, Byrial Jensen, Witold Kuzminski, Kazunobu
Kuriyama, Michael Christensen, Jorge Yáñez Teruel, Mark Davis, Marty
Rabinowitz, Ares Lagae, and Alexander Medvedev.
An early partial draft of this edition was reviewed by Brian Kernighan,
Angelika Langer, Jesse Laeuchli, Roger E. Pedersen, Chris Van Wyk,
Nicholas Stroustrup, and Hendrik Schober. Reviewers for a full draft
were Leor Zolman, Mike Tsao, Eric Nagler, Gene Gutnik, David
Abrahams, Gerhard Kreuzer, Drosos Kourounis, Brian Kernighan,
Andrew Kirmse, Balog Pal, Emily Jagdhar, Eugene Kalenkovich, Mike
Roze, Enrico Carrara, Benjamin Berck, Jack Reeves, Steve Schirripa,
Martin Fallenstedt, Timothy Knox, Yun Bai, Michael Lanzetta, Philipp
Janert, Guido Bartolucci, Michael Topic, Jeff Scherpelz, Chris Nauroth,
Nishant Mittal, Jeff Somers, Hal Moroff, Vincent Manis, Brandon
Chang, Greg Li, Jim Meehan, Alan Geller, Siddhartha Singh, Sam Lee,
Sasan Dashtinezhad, Alex Marin, Steve Cai, Thomas Fruchterman,
Cory Hicks, David Smallberg, Gunavardhan Kakulapati, Danny
Rabbani, Jake Cohen, Hendrik Schober, Paco Viciana, Glenn Kennedy,
Jeffrey D. Oldham, Nicholas Stroustrup, Matthew Wilson, Andrei
Alexandrescu, Tim Johnson, Leon Matthews, Peter Dulimov, and Kevlin
Henney. Drafts of some individual Items were reviewed by Herb Sutter
and Attila F. Feher.
Reviewing an unpolished (possibly incomplete) manuscript is demanding work, and doing it under time pressure only makes it harder.
I continue to be grateful that so many people have been willing to undertake it for me.
Reviewing is harder still if you have no background in the material being discussed and are expected to catch every problem in the manuscript. Astonishingly, some people still choose to be copy editors.
Chrysta Meadowbrooke was the copy editor for this book, and her very
thorough work exposed many problems that eluded everyone else.
Leor Zolman checked all the code examples against multiple compilers
in preparation for the full review, then did it again after I revised the
manuscript. If any errors remain, I’m responsible for them, not Leor.
Karl Wiegers and especially Tim Johnson offered rapid, helpful feedback on back cover copy.
xvi
Acknowledgments
Effective C++
Since publication of the first printing, I have incorporated revisions
suggested by Jason Ross, Robert Yokota, Bernhard Merkle, Attila
Feher, Gerhard Kreuzer, Marcin Sochacki, J. Daniel Smith, Idan
Lupinsky, G. Wade Johnson, Clovis Tondo, Joshua Lehrer, T. David
Hudson, Phillip Hellewell, Thomas Schell, Eldar Ronen, Ken
Kobayashi, Cameron Mac Minn, John Hershberger, Alex Dumov,
Vincent Stojanov, Andrew Henrick, Jiongxiong Chen, Balbir Singh,
Fraser Ross, Niels Dekker, Harsh Gaurav Vangani, Vasily Poshehonov,
Yukitoshi Fujimura, Alex Howlett, Ed Ji Xihuang. Mike Rizzi, Balog
Pal, David Solomon, Tony Oliver, Martin Rottinger, Miaohua, and
Brian Johnson.
John Wait, my editor for the first two editions of this book, foolishly
signed up for another tour of duty in that capacity. His assistant,
Denise Mickelsen, adroitly handled my frequent pestering with a
pleasant smile. (At least I think she’s been smiling. I’ve never actually
seen her.) Julie Nahil drew the short straw and hence became my
production manager. She handled the overnight loss of six weeks in
the production schedule with remarkable equanimity. John Fuller
(her boss) and Marty Rabinowitz (his boss) helped out with production
issues, too. Vanessa Moore’s official job was to help with FrameMaker
issues and PDF preparation, but she also added the entries to
Appendix B and formatted it for printing on the inside cover. Solveig
Haugland helped with index formatting. Sandra Schroeder and Chuti
Prasertsith were responsible for cover design, though Chuti seems to
have been the one who had to rework the cover each time I said, “But
what about this photo with a stripe of that color...?” Chanda LearyCoutu got tapped for the heavy lifting in marketing.
During the months I worked on the manuscript, the TV series Buffy
the Vampire Slayer often helped me “de-stress” at the end of the day.
Only with great restraint have I kept Buffyspeak out of the book.
Kathy Reed taught me programming in 1971, and I’m gratified that we
remain friends to this day. Donald French hired me and Moises Lejter
to create C++ training materials in 1989 (an act that led to my really
knowing C++), and in 1991 he engaged me to present them at Stratus
Computer. The students in that class encouraged me to write what ultimately became the first edition of this book. Don also introduced me
to John Wait, who agreed to publish it.
My wife, Nancy L. Urbano, continues to encourage my writing, even
after seven book projects, a CD adaptation, and a dissertation. She
has unbelievable forbearance. I couldn’t do what I do without her.
From start to finish, our dog, Persephone, has been a companion
without equal. Sadly, for much of this project, her companionship has
taken the form of an urn in the office. We really miss her.
Introduction
Learning the fundamentals of a programming language is one thing;
learning how to design and implement effective programs in that language is something else entirely. This is especially true of C++, a language boasting an uncommon range of power and expressiveness.
Properly used, C++ can be a joy to work with. An enormous variety of
designs can be directly expressed and efficiently implemented. A judiciously chosen and carefully crafted set of classes, functions, and
templates can make application programming easy, intuitive, efficient,
and nearly error-free. It isn’t unduly difficult to write effective C++
programs, if you know how to do it. Used without discipline, however,
C++ can lead to code that is incomprehensible, unmaintainable, inextensible, inefficient, and just plain wrong.
The purpose of this book is to show you how to use C++ effectively. I
assume you already know C++ as a language and that you have some
experience in its use. What I provide here is a guide to using the language so that your software is comprehensible, maintainable, portable, extensible, efficient, and likely to behave as you expect.
The advice I proffer falls into two broad categories: general design
strategies, and the nuts and bolts of specific language features. The
design discussions concentrate on how to choose between different
approaches to accomplishing something in C++. How do you choose
between inheritance and templates? Between public and private inheritance? Between private inheritance and composition? Between
member and non-member functions? Between pass-by-value and
pass-by-reference? It’s important to make these decisions correctly at
the outset, because a poor choice may not become apparent until
much later in the development process, at which point rectifying it is
often difficult, time-consuming, and expensive.
Even when you know exactly what you want to do, getting things just
right can be tricky. What’s the proper return type for assignment operators? When should a destructor be virtual? How should operator
2A
Introduction
Effective C++
new behave when it can’t find enough memory? It’s crucial to sweat
details like these, because failure to do so almost always leads to unexpected, possibly mystifying program behavior. This book will help
you avoid that.
This is not a comprehensive reference for C++. Rather, it’s a collection
of 55 specific suggestions (I call them Items) for how you can improve
your programs and designs. Each Item stands more or less on its own,
but most also contain references to other Items. One way to read the
book, then, is to start with an Item of interest, then follow its references to see where they lead you.
The book isn’t an introduction to C++, either. In Chapter 2, for example, I’m eager to tell you all about the proper implementations of constructors, destructors, and assignment operators, but I assume you
already know or can go elsewhere to find out what these functions do
and how they are declared. A number of C++ books contain information such as that.
The purpose of this book is to highlight those aspects of C++ programming that are often overlooked. Other books describe the different
parts of the language. This book tells you how to combine those parts
so you end up with effective programs. Other books tell you how to get
your programs to compile. This book tells you how to avoid problems
that compilers won’t tell you about.
At the same time, this book limits itself to standard C++. Only features in the official language standard have been used here. Portability is a key concern in this book, so if you’re looking for platformdependent hacks and kludges, this is not the place to find them.
Another thing you won’t find in this book is the C++ Gospel, the One
True Path to perfect C++ software. Each of the Items in this book provides guidance on how to develop better designs, how to avoid common problems, or how to achieve greater efficiency, but none of the
Items is universally applicable. Software design and implementation is
a complex task, one colored by the constraints of the hardware, the
operating system, and the application, so the best I can do is provide
guidelines for creating better programs.
If you follow all the guidelines all the time, you are unlikely to fall into
the most common traps surrounding C++, but guidelines, by their nature, have exceptions. That’s why each Item has an explanation. The
explanations are the most important part of the book. Only by understanding the rationale behind an Item can you determine whether it
applies to the software you are developing and to the unique constraints under which you toil.
Effective C++
Introduction
3A
The best use of this book is to gain insight into how C++ behaves, why
it behaves that way, and how to use its behavior to your advantage.
Blind application of the Items in this book is clearly inappropriate, but
at the same time, you probably shouldn’t violate any of the guidelines
without a good reason.
Terminology
There is a small C++ vocabulary that every programmer should understand. The following terms are important enough that it is worth making sure we agree on what they mean.
A declaration tells compilers about the name and type of something,
but it omits certain details. These are declarations:
extern int x;
std::size_t numDigits(int number);
class Widget;
template<typename T>
class GraphNode;
// object declaration
// function declaration
// class declaration
// template declaration
// (see Item 42 for info on
// the use of “typename”)
Note that I refer to the integer x as an “object,” even though it’s of
built-in type. Some people reserve the name “object” for variables of
user-defined type, but I’m not one of them. Also note that the function
numDigits’ return type is std::size_t, i.e., the type size_t in namespace
std. That namespace is where virtually everything in C++’s standard library is located. However, because C’s standard library (the one from
C89, to be precise) can also be used in C++, symbols inherited from C
(such as size_t) may exist at global scope, inside std, or both, depending on which headers have been #included. In this book, I assume that
C++ headers have been #included, and that’s why I refer to std::size_t
instead of just size_t. When referring to components of the standard library in prose, I typically omit references to std, relying on you to recognize that things like size_t, vector, and cout are in std. In example
code, I always include std, because real code won’t compile without it.
size_t, by the way, is just a typedef for some unsigned type that C++
uses when counting things (e.g., the number of characters in a char*based string, the number of elements in an STL container, etc.). It’s
also the type taken by the operator[] functions in vector, deque, and
string, a convention we’ll follow when defining our own operator[] functions in Item 3.
Each function’s declaration reveals its signature, i.e., its parameter
and return types. A function’s signature is the same as its type. In the
4A
Introduction
Effective C++
case of numDigits, the signature is std::size_t (int), i.e., “function taking
an int and returning a std::size_t.” The official C++ definition of “signature” excludes the function’s return type, but in this book, it’s more
useful to have the return type be considered part of the signature.
A definition provides compilers with the details a declaration omits.
For an object, the definition is where compilers set aside memory for
the object. For a function or a function template, the definition provides the code body. For a class or a class template, the definition lists
the members of the class or template:
int x;
std::size_t numDigits(int number)
{
std::size_t digitsSoFar = 1;
// object definition
// function definition.
// (This function returns
// the number of digits
// in its parameter.)
while ((number /= 10) != 0) ++digitsSoFar;
return digitsSoFar;
}
class Widget {
public:
Widget();
~Widget();
...
};
template<typename T>
class GraphNode {
public:
GraphNode();
~GraphNode();
...
};
// class definition
// template definition
Initialization is the process of giving an object its first value. For objects generated from structs and classes, initialization is performed by
constructors. A default constructor is one that can be called without
any arguments. Such a constructor either has no parameters or has a
default value for every parameter:
class A {
public:
A();
};
class B {
public:
explicit B(int x = 0, bool b = true);
};
// default constructor
// default constructor; see below
// for info on “explicit”
Effective C++
class C {
public:
explicit C(int x);
};
Introduction
5A
// not a default constructor
The constructors for classes B and C are declared explicit here. That
prevents them from being used to perform implicit type conversions,
though they may still be used for explicit type conversions:
void doSomething(B bObject);
B bObj1;
doSomething(bObj1);
B bObj2(28);
doSomething(28);
doSomething(B(28));
// a function taking an object of
// type B
// an object of type B
// fine, passes a B to doSomething
// fine, creates a B from the int 28
// (the bool defaults to true)
// error! doSomething takes a B,
// not an int, and there is no
// implicit conversion from int to B
// fine, uses the B constructor to
// explicitly convert (i.e., cast) the
// int to a B for this call. (See
// Item 27 for info on casting.)
Constructors declared explicit are usually preferable to non-explicit
ones, because they prevent compilers from performing unexpected
(often unintended) type conversions. Unless I have a good reason for
allowing a constructor to be used for implicit type conversions, I
declare it explicit. I encourage you to follow the same policy.
Please note how I’ve highlighted the cast in the example above.
Throughout this book, I use such highlighting to call your attention to
material that is particularly noteworthy. (I also highlight chapter
numbers, but that’s just because I think it looks nice.)
The copy constructor is used to initialize an object with a different
object of the same type, and the copy assignment operator is used
to copy the value from one object to another of the same type:
class Widget {
public:
Widget();
Widget(const Widget& rhs);
Widget& operator=(const Widget& rhs);
...
};
Widget w1;
Widget w2(w1);
w1 = w2;
// default constructor
// copy constructor
// copy assignment operator
// invoke default constructor
// invoke copy constructor
// invoke copy
// assignment operator
6A
Introduction
Effective C++
Read carefully when you see what appears to be an assignment, because the “=” syntax can also be used to call the copy constructor:
Widget w3 = w2;
// invoke copy constructor!
Fortunately, copy construction is easy to distinguish from copy assignment. If a new object is being defined (such as w3 in the statement
above), a constructor has to be called; it can’t be an assignment. If no
new object is being defined (such as in the “w1 = w2” statement above),
no constructor can be involved, so it’s an assignment.
The copy constructor is a particularly important function, because it
defines how an object is passed by value. For example, consider this:
bool hasAcceptableQuality(Widget w);
...
Widget aWidget;
if (hasAcceptableQuality(aWidget)) ...
The parameter w is passed to hasAcceptableQuality by value, so in the
call above, aWidget is copied into w. The copying is done by Widget’s
copy constructor. Pass-by-value means “call the copy constructor.”
(However, it’s generally a bad idea to pass user-defined types by value.
Pass-by-reference-to-const is typically a better choice. For details, see
Item 20.)
The STL is the Standard Template Library, the part of C++’s standard
library devoted to containers (e.g., vector, list, set, map, etc.), iterators
(e.g., vector<int>::iterator, set<string>::iterator, etc.), algorithms (e.g.,
for_each, find, sort, etc.), and related functionality. Much of that related
functionality has to do with function objects: objects that act like
functions. Such objects come from classes that overload operator(), the
function call operator. If you’re unfamiliar with the STL, you’ll want to
have a decent reference available as you read this book, because the
STL is too useful for me not to take advantage of it. Once you’ve used
it a little, you’ll feel the same way.
Programmers coming to C++ from languages like Java or C# may be
surprised at the notion of undefined behavior. For a variety of reasons, the behavior of some constructs in C++ is literally not defined:
you can’t reliably predict what will happen at runtime. Here are two
examples of code with undefined behavior:
int *p = 0;
std::cout << *p;
// p is a null pointer
// dereferencing a null pointer
// yields undefined behavior
Effective C++
char name[] = "Darla";
char c = name[10];
Introduction
7A
// name is an array of size 6 (don’t
// forget the trailing null!)
// referring to an invalid array index
// yields undefined behavior
To emphasize that the results of undefined behavior are not predictable and may be very unpleasant, experienced C++ programmers often say that programs with undefined behavior can erase your hard
drive. It’s true: a program with undefined behavior could erase your
hard drive. But it’s not probable. More likely is that the program will
behave erratically, sometimes running normally, other times crashing, still other times producing incorrect results. Effective C++ programmers do their best to steer clear of undefined behavior. In this
book, I point out a number of places where you need to be on the lookout for it.
Another term that may confuse programmers coming to C++ from another language is interface. Java and the .NET languages offer Interfaces as a language element, but there is no such thing in C++,
though Item 31 discusses how to approximate them. When I use the
term “interface,” I’m generally talking about a function’s signature,
about the accessible elements of a class (e.g., a class’s “public interface,” “protected interface,” or “private interface”), or about the expressions that must be valid for a template’s type parameter (see
Item 41). That is, I’m talking about interfaces as a fairly general design idea.
A client is someone or something that uses the code (typically the interfaces) you write. A function’s clients, for example, are its users: the
parts of the code that call the function (or take its address) as well as
the humans who write and maintain such code. The clients of a class
or a template are the parts of the software that use the class or template, as well as the programmers who write and maintain that code.
When discussing clients, I typically focus on programmers, because
programmers can be confused, misled, or annoyed by bad interfaces.
The code they write can’t be.
You may not be used to thinking about clients, but I’ll spend a good
deal of time trying to convince you to make their lives as easy as you
can. After all, you are a client of the software other people develop.
Wouldn’t you want those people to make things easy for you? Besides,
at some point you’ll almost certainly find yourself in the position of being your own client (i.e., using code you wrote), and at that point,
you’ll be glad you kept client concerns in mind when developing your
interfaces.
8A
Introduction
Effective C++
In this book, I often gloss over the distinction between functions and
function templates and between classes and class templates. That’s
because what’s true about one is often true about the other. In situations where this is not the case, I distinguish among classes, functions, and the templates that give rise to classes and functions.
When referring to constructors and destructors in code comments, I
sometimes use the abbreviations ctor and dtor.
Naming Conventions
I have tried to select meaningful names for objects, classes, functions,
templates, etc., but the meanings behind some of my names may not
be immediately apparent. Two of my favorite parameter names, for
example, are lhs and rhs. They stand for “left-hand side” and “righthand side,” respectively. I often use them as parameter names for
functions implementing binary operators, e.g., operator== and operator*. For example, if a and b are objects representing rational numbers,
and if Rational objects can be multiplied via a non-member operator*
function (as Item 24 explains is likely to be the case), the expression
a*b
is equivalent to the function call
operator*(a, b)
In Item 24, I declare operator* like this:
const Rational operator*(const Rational& lhs, const Rational& rhs);
As you can see, the left-hand operand, a, is known as lhs inside the
function, and the right-hand operand, b, is known as rhs.
For member functions, the left-hand argument is represented by the
this pointer, so sometimes I use the parameter name rhs by itself. You
may have noticed this in the declarations for some Widget member
functions on page 5. Which reminds me. I often use the Widget class
in examples. “Widget” doesn’t mean anything. It’s just a name I sometimes use when I need an example class name. It has nothing to do
with widgets in GUI toolkits.
I often name pointers following the rule that a pointer to an object of
type T is called pt, “pointer to T.” Here are some examples:
Widget *pw;
class Airplane;
Airplane *pa;
// pw = ptr to Widget
// pa = ptr to Airplane
Effective C++
class GameCharacter;
GameCharacter *pgc;
Introduction
9A
// pgc = ptr to GameCharacter
I use a similar convention for references: rw might be a reference to a
Widget and ra a reference to an Airplane.
I occasionally use the name mf when I’m talking about member functions.
Threading Considerations
As a language, C++ has no notion of threads — no notion of concurrency of any kind, in fact. Ditto for C++’s standard library. As far as
C++ is concerned, multithreaded programs don’t exist.
And yet they do. My focus in this book is on standard, portable C++,
but I can’t ignore the fact that thread safety is an issue many programmers confront. My approach to dealing with this chasm between
standard C++ and reality is to point out places where the C++ constructs I examine are likely to cause problems in a threaded environment. That doesn’t make this a book on multithreaded programming
with C++. Far from it. Rather, it makes it a book on C++ programming
that, while largely limiting itself to single-threaded considerations, acknowledges the existence of multithreading and tries to point out
places where thread-aware programmers need to take particular care
in evaluating the advice I offer.
If you’re unfamiliar with multithreading or have no need to worry
about it, you can ignore my threading-related remarks. If you are programming a threaded application or library, however, remember that
my comments are little more than a starting point for the issues you’ll
need to address when using C++.
TR1 and Boost
You’ll find references to TR1 and Boost throughout this book. Each
has an Item that describes it in some detail (Item 54 for TR1, Item 55
for Boost), but, unfortunately, these Items are at the end of the book.
(They’re there because it works better that way. Really. I tried them in
a number of other places.) If you like, you can turn to those Items and
read them now, but if you’d prefer to start the book at the beginning
instead of the end, the following executive summary will tide you over:
■
TR1 (“Technical Report 1”) is a specification for new functionality
being added to C++’s standard library. This functionality takes the
form of new class and function templates for things like hash ta-
10A
Introduction
Effective C++
bles, reference-counting smart pointers, regular expressions, and
more. All TR1 components are in the namespace tr1 that’s nested
inside the namespace std.
■
Boost is an organization and a web site (http://boost.org) offering
portable, peer-reviewed, open source C++ libraries. Most TR1
functionality is based on work done at Boost, and until compiler
vendors include TR1 in their C++ library distributions, the Boost
web site is likely to remain the first stop for developers looking for
TR1 implementations. Boost offers more than is available in TR1,
however, so it’s worth knowing about in any case.
Accustoming
Yourself to C++
Chapter 1: Accustoming Yourself to C++
Regardless of your programming background, C++ is likely to take a
little getting used to. It’s a powerful language with an enormous range
of features, but before you can harness that power and make effective
use of those features, you have to accustom yourself to C++’s way of
doing things. This entire book is about how to do that, but some
things are more fundamental than others, and this chapter is about
some of the most fundamental things of all.
Accustoming Yourself to C++
Item 1:
View C++ as a federation of languages.
In the beginning, C++ was just C with some object-oriented features
tacked on. Even C++’s original name, “C with Classes,” reflected this
simple heritage.
As the language matured, it grew bolder and more adventurous,
adopting ideas, features, and programming strategies different from
those of C with Classes. Exceptions required different approaches to
structuring functions (see Item 29). Templates gave rise to new ways
of thinking about design (see Item 41), and the STL defined an
approach to extensibility unlike any most people had ever seen.
Today’s C++ is a multiparadigm programming language, one supporting a combination of procedural, object-oriented, functional, generic,
and metaprogramming features. This power and flexibility make C++
a tool without equal, but can also cause some confusion. All the
“proper usage” rules seem to have exceptions. How are we to make
sense of such a language?
The easiest way is to view C++ not as a single language but as a federation of related languages. Within a particular sublanguage, the rules
tend to be simple, straightforward, and easy to remember. When you
move from one sublanguage to another, however, the rules may
12A
Item 1
Chapter 1
change. To make sense of C++, you have to recognize its primary sublanguages. Fortunately, there are only four:
■
C. Way down deep, C++ is still based on C. Blocks, statements, the
preprocessor, built-in data types, arrays, pointers, etc., all come
from C. In many cases, C++ offers approaches to problems that
are superior to their C counterparts (e.g., see Items 2 (alternatives
to the preprocessor) and 13 (using objects to manage resources)),
but when you find yourself working with the C part of C++, the
rules for effective programming reflect C’s more limited scope: no
templates, no exceptions, no overloading, etc.
■
Object-Oriented C++. This part of C++ is what C with Classes was
all about: classes (including constructors and destructors), encapsulation, inheritance, polymorphism, virtual functions (dynamic
binding), etc. This is the part of C++ to which the classic rules for
object-oriented design most directly apply.
■
Template C++. This is the generic programming part of C++, the
one that most programmers have the least experience with. Template considerations pervade C++, and it’s not uncommon for rules
of good programming to include special template-only clauses
(e.g., see Item 46 on facilitating type conversions in calls to template functions). In fact, templates are so powerful, they give rise
to a completely new programming paradigm, template metaprogramming (TMP). Item 48 provides an overview of TMP, but unless
you’re a hard-core template junkie, you need not worry about it.
The rules for TMP rarely interact with mainstream C++ programming.
■
The STL. The STL is a template library, of course, but it’s a very
special template library. Its conventions regarding containers, iterators, algorithms, and function objects mesh beautifully, but templates and libraries can be built around other ideas, too. The STL
has particular ways of doing things, and when you’re working with
the STL, you need to be sure to follow its conventions.
Keep these four sublanguages in mind, and don’t be surprised when
you encounter situations where effective programming requires that
you change strategy when you switch from one sublanguage to
another. For example, pass-by-value is generally more efficient than
pass-by-reference for built-in (i.e., C-like) types, but when you move
from the C part of C++ to Object-Oriented C++, the existence of userdefined constructors and destructors means that pass-by-referenceto-const is usually better. This is especially the case when working in
Template C++, because there, you don’t even know the type of object
Accustoming Yourself to C++
Item 2
13A
you’re dealing with. When you cross into the STL, however, you know
that iterators and function objects are modeled on pointers in C, so for
iterators and function objects in the STL, the old C pass-by-value rule
applies again. (For all the details on choosing among parameter-passing options, see Item 20.)
C++, then, isn’t a unified language with a single set of rules; it’s a federation of four sublanguages, each with its own conventions. Keep
these sublanguages in mind, and you’ll find that C++ is a lot easier to
understand.
Things to Remember
✦
Rules for effective C++ programming vary, depending on the part of
C++ you are using.
Item 2:
Prefer consts, enums, and inlines to #defines.
This Item might better be called “prefer the compiler to the preprocessor,” because #define may be treated as if it’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 preprocessor before the source code ever gets
to a compiler. As a result, the name ASPECT_RATIO may not get entered
into the 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 were
defined in a header file you didn’t write, you’d have no idea where that
1.653 came from, and you’d waste time tracking it down. This problem
can also crop up in a symbolic debugger, because, again, the name
you’re programming with may not be in the symbol table.
The solution is to replace the macro with a constant:
const double AspectRatio = 1.653;
// uppercase names are usually for
// macros, hence the name change
As a language constant, AspectRatio is definitely seen by compilers and
is certainly entered into their symbol tables. In addition, in the case of
a floating point constant (such as in this example), use of the constant
may yield smaller code than using a #define. That’s because the preprocessor’s blind substitution of the macro name ASPECT_RATIO with
1.653 could result in multiple copies of 1.653 in your object code,
while the use of the constant AspectRatio should never result in more
than one copy.
14A
Item 2
Chapter 1
When replacing #defines with constants, two special cases are worth
mentioning. The first is defining constant pointers. Because constant
definitions are typically put in header files (where many different
source files will include them), it’s important that the pointer be
declared const, 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 complete discussion of the meanings and uses of const, especially in conjunction with pointers, see Item 3. However, it’s worth
reminding you here that string objects are generally preferable to their
char*-based progenitors, so authorName is often better defined this
way:
const std::string authorName("Scott Meyers");
The second special case concerns class-specific constants. To limit
the scope of a constant to a class, you must make it a member, and to
ensure there’s at most one copy of the constant, you must make it a
static member:
class GamePlayer {
private:
static const int NumTurns = 5;
int scores[NumTurns];
...
};
// constant declaration
// use of constant
What you see above is a declaration for NumTurns, not a definition.
Usually, C++ requires that you provide a definition for anything you
use, but class-specific constants that are static and of integral type
(e.g., integers, chars, bools) are an exception. As long as you don’t take
their address, you can declare them and use them without providing a
definition. If you do take the address of a class constant, or if your
compiler incorrectly insists on a definition even if you don’t take the
address, you provide a separate definition like this:
const int GamePlayer::NumTurns;
// definition of NumTurns; see
// below for why no value is given
You put this in an implementation file, not a header file. Because the
initial value of class constants is provided where the constant is
declared (e.g., NumTurns is initialized to 5 when it is declared), no initial value is permitted at the point of definition.
Note, by the way, that there’s no way to create a class-specific constant using a #define, because #defines don’t respect scope. Once a
macro is defined, it’s in force for the rest of the compilation (unless it’s
Accustoming Yourself to C++
Item 2
15A
#undefed somewhere along the line). Which means that not only can’t
#defines be used for class-specific constants, they also can’t be used to
provide any kind of encapsulation, i.e., there is no such thing as a
“private” #define. Of course, const data members can be encapsulated;
NumTurns is.
Older compilers may not accept the syntax above, because it used to
be illegal to provide an initial value for a static class member at its
point of declaration. Furthermore, in-class initialization is allowed
only for integral types and only for constants. In cases where the
above syntax can’t be used, you put the initial value at the point of
definition:
class CostEstimate {
private:
static const double FudgeFactor;
...
};
const double
CostEstimate::FudgeFactor = 1.35;
// declaration of static class
// constant; goes in header file
// definition of static class
// constant; goes in impl. file
This is all you need almost all the time. The only exception is when
you need the value of a class constant during compilation of the class,
such as in the declaration of the array GamePlayer::scores above (where
compilers insist on knowing the size of the array during compilation).
Then the accepted way to compensate for compilers that (incorrectly)
forbid the in-class specification of initial values for static integral class
constants is to use what is affectionately (and non-pejoratively) known
as “the enum hack.” This technique takes advantage of the fact that
the values of an enumerated type can be used where ints are expected,
so GamePlayer could just as well be defined like this:
class GamePlayer {
private:
enum { NumTurns = 5 };
int scores[NumTurns];
...
// “the enum hack” — makes
// NumTurns a symbolic name for 5
// fine
};
The enum hack is worth knowing about for several reasons. First, the
enum hack behaves in some ways more like a #define than a const
does, and sometimes that’s what you want. For example, it’s legal to
take the address of a const, but it’s not legal to take the address of an
enum, and it’s typically not legal to take the address of a #define,
either. If you don’t want to let people get a pointer or reference to one
16A
Item 2
Chapter 1
of your integral constants, an enum is a good way to enforce that constraint. (For more on enforcing design constraints through coding
decisions, consult Item 18.) Also, though good compilers won’t set
aside storage for const objects of integral types (unless you create a
pointer or reference to the object), sloppy compilers may, and you may
not be willing to set aside memory for such objects. Like #defines,
enums never result in that kind of unnecessary memory allocation.
A second reason to know about the enum hack is purely pragmatic.
Lots of code employs it, so you need to recognize it when you see it. In
fact, the enum hack is a fundamental technique of template metaprogramming (see Item 48).
Getting back to the preprocessor, another common (mis)use of the
#define directive is using it to implement macros that look like functions but that don’t incur the overhead of a function call. Here’s a
macro that calls some function f with the greater of the macro’s arguments:
// call f with the maximum of a and b
#define CALL_WITH_MAX(a, b) f((a) > (b) ? (a) : (b))
Macros like this have so many drawbacks, just thinking about them is
painful.
Whenever you write this kind of macro, you have to remember to
parenthesize all the arguments in the macro body. Otherwise you can
run into trouble when somebody calls the macro with an expression.
But even if you get that right, look at the weird things that can happen:
int a = 5, b = 0;
CALL_WITH_MAX(++a, b);
CALL_WITH_MAX(++a, b+10);
// a is incremented twice
// a is incremented once
Here, the number of times that a is incremented before calling f
depends on what it is being compared with!
Fortunately, you don’t need to put up with this nonsense. You can get
all the efficiency of a macro plus all the predictable behavior and type
safety of a regular function by using a template for an inline function
(see Item 30):
template<typename T>
inline void callWithMax(const T& a, const T& b)
{
f(a > b ? a : b);
}
// because we don’t
// know what T is, we
// pass by reference-to// const — see Item 20
This template generates a whole family of functions, each of which
takes two objects of the same type and calls f with the greater of the
Accustoming Yourself to C++
Item 3
17A
two objects. There’s no need to parenthesize parameters inside the
function body, no need to worry about evaluating parameters multiple
times, etc. Furthermore, because callWithMax is a real function, it
obeys scope and access rules. For example, it makes perfect sense to
talk about an inline function that is private to a class. In general,
there’s just no way to do that with a macro.
Given the availability of consts, enums, and inlines, your need for the
preprocessor (especially #define) is reduced, but it’s not eliminated.
#include remains essential, and #ifdef/#ifndef continue to play important roles in controlling compilation. It’s not yet time to retire the preprocessor, but you should definitely give it long and frequent
vacations.
Things to Remember
✦
For simple constants, prefer const objects or enums to #defines.
✦
For function-like macros, prefer inline functions to #defines.
Item 3:
Use const whenever possible.
The wonderful thing about const is that it allows you to specify a
semantic constraint — a particular object should not be modified —
and compilers will enforce that constraint. It allows you to communicate to both compilers and other programmers that a value should
remain invariant. Whenever that is true, you should be sure to say so,
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 constants at global or namespace scope (see Item 2), as well
as for objects declared static at file, function, or block scope. Inside
classes, you can use it for both static and non-static data members.
For pointers, you can specify whether the pointer itself is const, the
data it points to is const, both, or neither:
char greeting[] = "Hello";
char *p = greeting;
const char *p = greeting;
char * const p = greeting;
const char * const p = greeting;
// non-const pointer,
// non-const data
// non-const pointer,
// const data
// const pointer,
// non-const data
// const pointer,
// const data
18A
Item 3
Chapter 1
This syntax isn’t as capricious as it may seem. If the word const
appears to the left of the asterisk, what’s pointed to is constant; if the
word const appears to the right of the asterisk, the pointer itself is constant; if const appears on both sides, both are constant.†
When what’s pointed to is constant, some programmers list const
before the type. Others list it after the type but before the asterisk.
There is no difference in meaning, so the following functions take the
same parameter type:
void f1(const Widget *pw);
void f2(Widget const *pw);
// f1 takes a pointer to a
// constant Widget object
// so does f2
Because both forms exist in real code, you should accustom yourself
to both of them.
STL iterators are modeled on pointers, so an iterator acts much like a
T* pointer. Declaring an iterator const is like declaring a pointer const
(i.e., declaring a T* const pointer): the iterator isn’t allowed to point to
something different, but the thing it points to may be modified. If you
want an iterator that points to something that can’t be modified (i.e.,
the STL analogue of a const T* pointer), you want a const_iterator:
std::vector<int> vec;
...
const std::vector<int>::iterator iter =
vec.begin();
*iter = 10;
++iter;
std::vector<int>::const_iterator cIter =
vec.begin();
*cIter = 10;
++cIter;
// iter acts like a T* const
// OK, changes what iter points to
// error! iter is const
//cIter acts like a const T*
// error! *cIter is const
// fine, changes cIter
Some of the most powerful uses of const stem from its application to
function declarations. Within a function declaration, const can refer to
the function’s return value, to individual parameters, and, for member
functions, to the function as a whole.
Having a function return a constant value often makes it possible to
reduce the incidence of client errors without giving up safety or efficiency. For example, consider the declaration of the operator* function
for rational numbers that is explored in Item 24:
class Rational { ... };
const Rational operator*(const Rational& lhs, const Rational& rhs);
† Some people find it helpful to read pointer declarations right to left, e.g., to read const
char * const p as “p is a constant pointer to constant chars.”
Accustoming Yourself to C++
Item 3
19A
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;
// invoke operator= on the
// result of a*b!
I don’t know why any programmer would want to make an assignment
to the product of two numbers, but I do know that many programmers
have tried to do it without wanting to. All it takes is a simple typo (and
a type that can be implicitly converted to bool):
if (a * b = c) ...
// oops, meant to do a comparison!
Such code would be flat-out illegal if a and b were of a built-in type.
One of the hallmarks of good user-defined types is that they avoid gratuitous incompatibilities with the built-ins (see also Item 18), and
allowing assignments to the product of two numbers seems pretty gratuitous to me. Declaring operator*’s return value const 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, and you should use both whenever you
can. Unless you need to be able to modify a parameter or local object,
be sure to declare it const. It costs you only the effort to type six characters, and it can save you from annoying errors such as the “I meant
to type ‘==’ but I accidently typed ‘=’” mistake we just saw.
const Member Functions
The purpose of const on member functions is to identify which member functions may be invoked on const objects. Such member functions are important for two reasons. First, they make the interface of a
class easier to understand. It’s important to know which functions
may modify an object and which may not. Second, they make it possible to work with const objects. That’s a critical aspect of writing efficient code, because, as Item 20 explains, one of the fundamental ways
to improve a C++ program’s performance is to pass objects by reference-to-const. That technique is viable only if there are const member
functions with which to manipulate the resulting const-qualified
objects.
Many people overlook the fact that member functions differing only in
their constness can be overloaded, but this is an important feature of
C++. Consider a class for representing a block of text:
20A
Item 3
Chapter 1
class TextBlock {
public:
...
const char& operator[](std::size_t position) const
{ return text[position]; }
char& operator[](std::size_t position)
{ return text[position]; }
private:
std::string text;
};
// operator[] for
// const objects
// operator[] for
// non-const objects
TextBlock’s operator[]s can be used like this:
TextBlock tb("Hello");
std::cout << tb[0];
const TextBlock ctb("World");
std::cout << ctb[0];
// calls non-const
// TextBlock::operator[]
// calls const TextBlock::operator[]
Incidentally, const objects most often arise in real programs as a result
of being passed by pointer- or reference-to-const. The example of ctb
above is artificial. This is more realistic:
void print(const TextBlock& ctb)
{
std::cout << ctb[0];
...
}
// in this function, ctb is const
// calls const TextBlock::operator[]
By overloading operator[] and giving the different versions different
return types, you can have const and non-const TextBlocks handled differently:
std::cout << tb[0];
tb[0] = ’x’;
std::cout << ctb[0];
ctb[0] = ’x’;
// fine — reading a
// non-const TextBlock
// fine — writing a
// non-const TextBlock
// fine — reading a
// const TextBlock
// error! — writing a
// const TextBlock
Note that the error here has only to do with the return type of the
operator[] that is called; the calls to 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 type from the const version of
operator[].
Accustoming Yourself to C++
Item 3
21A
Also note that the return type of the non-const operator[] is a reference
to a char — a char itself would not do. If operator[] did return a simple
char, statements like this wouldn’t compile:
tb[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 were legal, the fact that C++
returns objects by value (see Item 20) would mean that a copy of
tb.text[0] would be modified, not tb.text[0] itself, and that’s not the
behavior you want.
Let’s take a brief time-out for philosophy. What does it mean for a
member function to be const? There are two prevailing notions: bitwise
constness (also known as physical constness) and logical constness.
The bitwise const camp believes that a member function is const if and
only if it doesn’t modify any of the object’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 assignments to data members. In fact, bitwise constness is C++’s definition of constness, and a
const member function isn’t allowed to modify any of the non-static
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 member function that modifies what a
pointer points to frequently doesn’t act const. But if only the pointer is
in the object, the function is bitwise const, and compilers won’t complain. That can lead to counterintuitive behavior. For example, suppose we have a TextBlock-like class that stores its data as a char*
instead of a string, because it needs to communicate through a C API
that doesn’t understand string objects.
class CTextBlock {
public:
...
char& operator[](std::size_t position) const
{ return pText[position]; }
// inappropriate (but bitwise
// const) declaration of
// operator[]
private:
char *pText;
};
This class (inappropriately) declares operator[] as a const member
function, even though that function returns a reference to the object’s
internal data (a topic treated in depth in Item 28). Set that aside and
22A
Item 3
Chapter 1
note that operator[]’s implementation doesn’t modify pText in any way.
As a result, compilers will happily generate code for operator[]; it is,
after all, bitwise const, and that’s all compilers check for. But look
what it allows to happen:
const CTextBlock cctb("Hello");
char *pc = &cctb[0];
*pc = ’J’;
// declare constant object
// call the const operator[] to get a
// pointer to cctb’s data
// cctb now has the value “Jello”
Surely there is something wrong when you create a constant object
with a particular value and you invoke only const member functions
on it, yet you still change its value!
This leads to the notion of logical constness. Adherents to this philosophy — and you should be among them — argue that a const member
function might modify some of the bits in the object on which it’s
invoked, but only in ways that clients cannot detect. For example,
your CTextBlock class might want to cache the length of the textblock
whenever it’s requested:
class CTextBlock {
public:
...
std::size_t length() const;
private:
char *pText;
std::size_t textLength;
// last calculated length of textblock
bool lengthIsValid;
// whether length is currently valid
};
std::size_t CTextBlock::length() const
{
if (!lengthIsValid) {
textLength = std::strlen(pText); // error! can’t assign to textLength
lengthIsValid = true;
// and lengthIsValid in a const
}
// member function
return textLength;
}
This implementation of length is certainly not bitwise const — both textLength and lengthIsValid may be modified — yet it seems as though it
should be valid for const CTextBlock objects. Compilers disagree. They
insist on bitwise constness. What to do?
The solution is simple: take advantage of C++’s const-related wiggle
room known as mutable. mutable frees non-static data members from
the constraints of bitwise constness:
Accustoming Yourself to C++
class CTextBlock {
public:
...
std::size_t length() const;
private:
char *pText;
mutable std::size_t textLength;
mutable bool lengthIsValid;
};
std::size_t CTextBlock::length() const
{
if (!lengthIsValid) {
textLength = std::strlen(pText);
lengthIsValid = true;
}
return textLength;
}
Item 3
23A
// these data members may
// always be modified, even in
// const member functions
// now fine
// also fine
Avoiding Duplication in const and Non-const Member Functions
mutable is a nice solution to the bitwise-constness-is-not-what-I-hadin-mind problem, but it doesn’t solve all const-related difficulties. For
example, suppose that operator[] in TextBlock (and CTextBlock) not only
returned a reference to the appropriate character, it also performed
bounds checking, logged access information, maybe even did data
integrity validation. Putting all this in both the const and the non-const
operator[] functions (and not fretting that we now have implicitly inline
functions of nontrivial length — see Item 30) yields this kind of monstrosity:
class TextBlock {
public:
...
const char& operator[](std::size_t position) const
{
...
// do bounds checking
...
// log access data
...
// verify data integrity
return text[position];
}
char& operator[](std::size_t position)
{
...
// do bounds checking
...
// log access data
...
// verify data integrity
return text[position];
}
private:
std::string text;
};
24A
Item 3
Chapter 1
Ouch! Can you say code duplication, along with its attendant compilation time, maintenance, and code-bloat headaches? Sure, it’s possible to move all the code for bounds checking, etc. into a separate
member function (private, naturally) that both versions of operator[]
call, but you’ve still got the duplicated calls to that function and
you’ve still got the duplicated return statement code.
What you really want to do is implement operator[] functionality once
and use it twice. That is, you want to have one version of operator[]
call the other one. And that brings us to casting away constness.
As a general rule, casting is such a bad idea, I’ve devoted an entire
Item to telling you not to do it (Item 27), but code duplication is no
picnic, either. In this case, the const version of operator[] does exactly
what the non-const version does, it just has a const-qualified return
type. Casting away the const on the return value is safe, in this case,
because whoever called the non-const operator[] must have had a nonconst object in the first place. Otherwise they couldn’t have called a
non-const function. So having the non-const operator[] call the const
version is a safe way to avoid code duplication, even though it requires
a cast. Here’s the code, but it may be clearer after you read the explanation that follows:
class TextBlock {
public:
...
const char& operator[](std::size_t position) const
// same as before
{
...
...
...
return text[position];
}
char& operator[](std::size_t position)
// now just calls const op[]
{
return
const_cast<char&>(
// cast away const on
// op[]’s return type;
static_cast<const TextBlock&>(*this) // add const to *this’s type;
[position]
// call const version of op[]
);
}
...
};
Accustoming Yourself to C++
Item 3
25A
As you can see, the code has two casts, not one. We want the nonconst operator[] to call the const one, but if, inside the non-const
operator[], we just call operator[], we’ll recursively call ourselves. That’s
only entertaining the first million or so times. To avoid infinite recursion, we have to specify that we want to call the const operator[], but
there’s no direct way to do that. Instead, we cast *this from its native
type of TextBlock& to const TextBlock&. Yes, we use a cast to add const!
So we have two casts: one to add const to *this (so that our call to
operator[] will call the const version), the second to remove the const
from the const operator[]’s return value.
The cast that adds const is just forcing a safe conversion (from a nonconst object to a const one), so we use a static_cast for that. The one
that removes const can be accomplished only via a const_cast, so we
don’t really have a choice there. (Technically, we do. A C-style cast
would also work, but, as I explain in Item 27, such casts are rarely the
right choice. If you’re unfamiliar with static_cast or const_cast, Item 27
contains an overview.)
On top of everything else, we’re calling an operator in this example, so
the syntax is a little strange. The result may not win any beauty contests, but it has the desired effect of avoiding code duplication by
implementing the non-const version of operator[] in terms of the const
version. Whether achieving that goal is worth the ungainly syntax is
something only you can determine, but the technique of implementing
a non-const member function in terms of its const twin is definitely
worth knowing.
Even more worth knowing is that trying to do things the other way
around — avoiding duplication by having the const version call the
non-const version — is not something you want to do. Remember, a
const member function promises never to change the logical state of
its object, but a non-const member function makes no such promise.
If you were to call a non-const function from a const one, you’d run the
risk that the object you’d promised not to modify would be changed.
That’s why having a const member function call a non-const one is
wrong: the object could be changed. In fact, to get the code to compile,
you’d have to use a const_cast to get rid of the const on *this, a clear
sign of trouble. The reverse calling sequence — the one we used above
— is safe: the non-const member function can do whatever it wants
with an object, so calling a const member function imposes no risk.
That’s why a static_cast works on *this in that case: there’s no constrelated danger.
As I noted at the beginning of this Item, const is a wonderful thing. On
pointers and iterators; on the objects referred to by pointers, iterators,
26A
Item 4
Chapter 1
and references; on function parameters and return types; on local
variables; and on member functions, const is a powerful ally. Use it
whenever you can. You’ll be glad you did.
Things to Remember
✦
Declaring something const helps compilers detect usage errors. const
can be applied to objects at any scope, to function parameters and
return types, and to member functions as a whole.
✦
Compilers enforce bitwise constness, but you should program using
logical constness.
✦
When const and non-const member functions have essentially identical implementations, code duplication can be avoided by having the
non-const version call the const version.
Item 4:
Make sure that objects are initialized before
they’re used.
C++ can seem rather fickle about initializing the values of objects. For
example, if you say this,
int x;
in some contexts, x is guaranteed to be initialized (to zero), but in others, it’s not. If you say this,
class Point {
int x, y;
};
...
Point p;
p’s data members are sometimes guaranteed to be initialized (to zero),
but sometimes they’re not. If you’re coming from a language where
uninitialized objects can’t exist, pay attention, because this is important.
Reading uninitialized values yields undefined behavior. On some platforms, the mere act of reading an uninitialized value can halt your
program. More typically, the result of the read will be semi-random
bits, which will then pollute the object you read the bits into, eventually leading to inscrutable program behavior and a lot of unpleasant
debugging.
Now, there are rules that describe when object initialization is guaranteed to take place and when it isn’t. Unfortunately, the rules are com-
Accustoming Yourself to C++
Item 4
27A
plicated — too complicated to be worth memorizing, in my opinion. In
general, if you’re in the C part of C++ (see Item 1) and initialization
would probably incur a runtime cost, it’s not guaranteed to take
place. If you cross into the non-C parts of C++, things sometimes
change. This explains why an array (from the C part of C++) isn’t necessarily guaranteed to have its contents initialized, but a vector (from
the STL part of C++) is.
The best way to deal with this seemingly indeterminate state of affairs
is to always initialize your objects before you use them. For nonmember objects of built-in types, you’ll need to do this manually. For
example:
int x = 0;
const char * text = "A C-style string";
double d;
std::cin >> d;
// manual initialization of an int
// manual initialization of a
// pointer (see also Item 3)
// “initialization” by reading from
// an input stream
For almost everything else, the responsibility for initialization falls on
constructors. The rule there is simple: make sure that all constructors
initialize everything in the object.
The rule is easy to follow, but it’s important not to confuse assignment
with initialization. Consider a constructor for a class representing
entries in an address book:
class PhoneNumber { ... };
class ABEntry {
// ABEntry = “Address Book Entry”
public:
ABEntry(const std::string& name, const std::string& address,
const std::list<PhoneNumber>& phones);
private:
std::string theName;
std::string theAddress;
std::list<PhoneNumber> thePhones;
int numTimesConsulted;
};
ABEntry::ABEntry(const std::string& name, const std::string& address,
const std::list<PhoneNumber>& phones)
{
theName = name;
// these are all assignments,
theAddress = address;
// not initializations
thePhones = phones;
numTimesConsulted = 0;
}
28A
Item 4
Chapter 1
This will yield ABEntry objects with the values you expect, but it’s still
not the best approach. The rules of C++ stipulate that data members
of an object are initialized before the body of a constructor is entered.
Inside the ABEntry constructor, theName, theAddress, and thePhones
aren’t being initialized, they’re being assigned. Initialization took place
earlier — when their default constructors were automatically called
prior to entering the body of the ABEntry constructor. This isn’t true for
numTimesConsulted, because it’s a built-in type. For it, there’s no guarantee it was initialized at all prior to its assignment.
A better way to write the ABEntry constructor is to use the member initialization list instead of assignments:
ABEntry::ABEntry(const std::string& name, const std::string& address,
const std::list<PhoneNumber>& phones)
: theName(name),
theAddress(address),
// these are now all initializations
thePhones(phones),
numTimesConsulted(0)
{}
// the ctor body is now empty
This constructor yields the same end result as the one above, but it
will often be more efficient. The assignment-based version first called
default constructors to initialize theName, theAddress, and thePhones,
then promptly assigned new values on top of the default-constructed
ones. All the work performed in those default constructions was
therefore wasted. The member initialization list approach avoids that
problem, because the arguments in the initialization list are used as
constructor arguments for the various data members. In this case,
theName is copy-constructed from name, theAddress is copy-constructed from address, and thePhones is copy-constructed from phones.
For most types, a single call to a copy constructor is more efficient —
sometimes much more efficient — than a call to the default constructor followed by a call to the copy assignment operator.
For objects of built-in type like numTimesConsulted, there is no difference in cost between initialization and assignment, but for consistency, it’s often best to initialize everything via member initialization.
Similarly, you can use the member initialization list even when you
want to default-construct a data member; just specify nothing as an
initialization argument. For example, if ABEntry had a constructor taking no parameters, it could be implemented like this:
ABEntry::ABEntry()
: theName(),
theAddress(),
thePhones(),
numTimesConsulted(0)
{}
// call theName’s default ctor;
// do the same for theAddress;
// and for thePhones;
// but explicitly initialize
// numTimesConsulted to zero
Accustoming Yourself to C++
Item 4
29A
Because compilers will automatically call default constructors for data
members of user-defined types when those data members have no initializers on the member initialization list, some programmers consider
the above approach overkill. That’s understandable, but having a policy of always listing every data member on the initialization list avoids
having to remember which data members may go uninitialized if they
are omitted. Because numTimesConsulted is of a built-in type, for
example, leaving it off a member initialization list could open the door
to undefined behavior.
Sometimes the initialization list must be used, even for built-in types.
For example, data members that are const or are references must be
initialized; they can’t be assigned (see also Item 5). To avoid having to
memorize when data members must be initialized in the member initialization list and when it’s optional, the easiest choice is to always
use the initialization list. It’s sometimes required, and it’s often more
efficient than assignments.
Many classes have multiple constructors, and each constructor has
its own member initialization list. If there are many data members
and/or base classes, the existence of multiple initialization lists introduces undesirable repetition (in the lists) and boredom (in the programmers). In such cases, it’s not unreasonable to omit entries in the
lists for data members where assignment works as well as true initialization, moving the assignments to a single (typically private) function
that all the constructors call. This approach can be especially helpful
if the true initial values for the data members are to be read from a file
or looked up in a database. In general, however, true member initialization (via an initialization list) is preferable to pseudo-initialization
via assignment.
One aspect of C++ that isn’t fickle is the order in which an object’s
data is initialized. This order is always the same: base classes are initialized before derived classes (see also Item 12), and within a class,
data members are initialized in the order in which they are declared.
In ABEntry, for example, theName will always be initialized first, theAddress second, thePhones third, and numTimesConsulted last. This is true
even if they are listed in a different order on the member initialization
list (something that’s unfortunately legal). To avoid reader confusion,
as well as the possibility of some truly obscure behavioral bugs,
always list members in the initialization list in the same order as
they’re declared in the class.
Once you’ve taken care of explicitly initializing non-member objects of
built-in types and you’ve ensured that your constructors initialize
their base classes and data members using the member initialization
30A
Item 4
Chapter 1
list, there’s only one more thing to worry about. That thing is — take a
deep breath — the order of initialization of non-local static objects
defined in different translation units.
Let’s pick that phrase apart bit by bit.
A static object is one that exists from the time it’s constructed until the
end of the program. Stack and heap-based objects are thus excluded.
Included are global objects, objects defined at namespace scope,
objects declared static inside classes, objects declared static inside
functions, and objects declared static at file scope. Static objects inside
functions are known as local static objects (because they’re local to a
function), and the other kinds of static objects are known as non-local
static objects. Static objects are destroyed when the program exits,
i.e., their destructors are called when main finishes executing.
A translation unit is the source code giving rise to a single object file.
It’s basically a single source file, plus all of its #include files.
The problem we’re concerned with, then, involves at least two separately compiled source files, each of which contains at least one nonlocal static object (i.e., an object that’s global, at namespace scope, or
static in a class or at file scope). And the actual problem is this: if initialization of a non-local static object in one translation unit uses a
non-local static object in a different translation unit, the object it uses
could be uninitialized, because the relative order of initialization of nonlocal static objects defined in different translation units is undefined.
An example will help. Suppose you have a FileSystem class that makes
files on the Internet look like they’re local. Since your class makes the
world look like a single file system, you might create a special object at
global or namespace scope representing the single file system:
class FileSystem {
public:
...
std::size_t numDisks() const;
...
};
extern FileSystem tfs;
// from your library’s header file
// one of many member functions
// declare object for clients to use
// (“tfs” = “the file system” ); definition
// is in some .cpp file in your library
A FileSystem object is decidedly non-trivial, so use of the tfs object
before it has been constructed would be disastrous.
Now suppose some client creates a class for directories in a file system. Naturally, their class uses the tfs object:
Accustoming Yourself to C++
class Directory {
public:
Directory( params );
...
};
Directory::Directory( params )
{
...
std::size_t disks = tfs.numDisks();
...
}
Item 4
31A
// created by library client
// use the tfs object
Further suppose this client decides to create a single Directory object
for temporary files:
Directory tempDir( params );
// directory for temporary files
Now the importance of initialization order becomes apparent: unless
tfs is initialized before tempDir, tempDir’s constructor will attempt to
use tfs before it’s been initialized. But tfs and tempDir were created by
different people at different times in different source files — they’re
non-local static objects defined in different translation units. How can
you be sure that tfs will be initialized before tempDir?
You can’t. Again, the relative order of initialization of non-local static
objects defined in different translation units is undefined. There is a
reason for this. Determining the “proper” order in which to initialize
non-local static objects is hard. Very hard. Unsolvably hard. In its
most general form — with multiple translation units and non-local
static objects generated through implicit template instantiations
(which may themselves arise via implicit template instantiations) —
it’s not only impossible to determine the right order of initialization,
it’s typically not even worth looking for special cases where it is possible to determine the right order.
Fortunately, a small design change eliminates the problem entirely.
All that has to be done is to move each non-local static object into its
own function, where it’s declared static. These functions return references to the objects they contain. Clients then call the functions
instead of referring to the objects. In other words, non-local static
objects are replaced with local static objects. (Aficionados of design
patterns will recognize this as a common implementation of the Singleton pattern.†)
This approach is founded on C++’s guarantee that local static objects
are initialized when the object’s definition is first encountered during
a call to that function. So if you replace direct accesses to non-local
† Actually, it’s only part of a Singleton implementation. An essential part of Singleton I
ignore in this Item is preventing the creation of multiple objects of a particular type.
32A
Item 4
Chapter 1
static objects with calls to functions that return references to local
static objects, you’re guaranteed that the references you get back will
refer to initialized objects. As a bonus, if you never call a function
emulating a non-local static object, you never incur the cost of constructing and destructing the object, something that can’t be said for
true non-local static objects.
Here’s the technique applied to both tfs and tempDir:
class FileSystem { ... };
// as before
FileSystem& tfs()
// this replaces the tfs object; it could be
{
// static in the FileSystem class
static FileSystem fs;
// define and initialize a local static object
return fs;
// return a reference to it
}
class Directory { ... };
// as before
Directory::Directory( params ) // as before, except references to tfs are
{
// now to tfs()
...
std::size_t disks = tfs().numDisks();
...
}
Directory& tempDir()
// this replaces the tempDir object; it
{
// could be static in the Directory class
static Directory td( params ); // 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 tfs() and tempDir() instead of tfs and tempDir.
That is, they use functions returning references to objects instead of
using the objects themselves.
The reference-returning functions dictated by this scheme are always
simple: define and initialize a local static object on line 1, return it on
line 2. This simplicity makes them excellent candidates for inlining,
especially if they’re called frequently (see Item 30). On the other hand,
the fact that these functions contain static objects makes them problematic in multithreaded systems. Then again, any kind of non-const
static object — local or non-local — is trouble waiting to happen in the
presence of multiple threads. One way to deal with such trouble is to
manually invoke all the reference-returning functions during the single-threaded startup portion of the program. This eliminates initialization-related race conditions.
Of course, the idea of using reference-returning functions to prevent
initialization order problems is dependent on there being a reasonable
Accustoming Yourself to C++
Item 4
33A
initialization order for your objects in the first place. If you have a system where object A must be initialized before object B, but A’s initialization is dependent on B’s having already been initialized, you are
going to have problems, and frankly, you deserve them. If you steer
clear of such pathological scenarios, however, the approach described
here should serve you nicely, at least in single-threaded applications.
To avoid using objects before they’re initialized, then, you need to do
only three things. First, manually initialize non-member objects of
built-in types. Second, use member initialization lists to initialize all
parts of an object. Finally, design around the initialization order
uncertainty that afflicts non-local static objects defined in separate
translation units.
Things to Remember
✦
Manually initialize objects of built-in type, because C++ only sometimes initializes them itself.
✦
In a constructor, prefer use of the member initialization list to assignment inside the body of the constructor. List data members in
the initialization list in the same order they’re declared in the class.
✦
Avoid initialization order problems across translation units by replacing non-local static objects with local static objects.
Constructors,
Destructors, and
Assignment Operators
Chapter 2: Constructors, Destructors, and Assignment Operators
Almost every class you write will have one or more constructors, a
destructor, and a copy assignment operator. Little wonder. These are
your bread-and-butter functions, the ones that control the fundamental operations of bringing a new object into existence and making sure
it’s initialized, getting rid of an object and making sure it’s properly
cleaned up, and giving an object a new value. Making mistakes in
these functions will lead to far-reaching — and unpleasant — repercussions throughout your classes, so it’s vital that you get them right.
In this chapter, I offer guidance on putting together the functions that
comprise the backbone of well-formed classes.
Constructors, Destructors, operator=
Item 5:
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, compilers will declare their
own versions of a copy constructor, a copy assignment operator, and a
destructor. Furthermore, if you declare no constructors at all, compilers will also declare a default constructor for you. All these functions
will be both public and inline (see Item 30). As a result, if you write
class Empty{};
it’s essentially the same as if you’d written this:
class Empty {
public:
Empty() { ... }
Empty(const Empty& rhs) { ... }
~Empty() { ... }
// default constructor
// copy constructor
// destructor — see below
// for whether it’s virtual
Empty& operator=(const Empty& rhs) { ... } // copy assignment operator
};
Constructors, Destructors, operator=
Item 5
35A
These functions are generated only if they are needed, but it doesn’t
take much to need them. The following code will cause each function
to be generated:
Empty e1;
Empty e2(e1);
e2 = e1;
// default constructor;
// destructor
// copy constructor
// copy assignment operator
Given that compilers are writing functions for you, what do the functions do? Well, the default constructor and the destructor primarily
give compilers a place to put “behind the scenes” code such as invocation of constructors and destructors of base classes and non-static
data members. Note that the generated destructor is non-virtual (see
Item 7) unless it’s for a class inheriting from a base class that itself
declares a virtual destructor (in which case the function’s virtualness
comes from the base class).
As for the copy constructor and the copy assignment operator, the
compiler-generated versions simply copy each non-static data member of the source object over to the target object. For example, consider a NamedObject template that allows you to associate names with
objects of type T:
template<typename T>
class NamedObject {
public:
NamedObject(const char *name, const T& value);
NamedObject(const std::string& name, const T& value);
...
private:
std::string nameValue;
T objectValue;
};
Because a constructor is declared in NamedObject, compilers won’t
generate a default constructor. This is important. It means that if
you’ve carefully engineered a class to require constructor arguments,
you don’t have to worry about compilers overriding your decision by
blithely adding a constructor that takes no arguments.
NamedObject declares neither copy constructor nor copy assignment
operator, so compilers will generate those functions (if they are
needed). Look, then, at this use of the copy constructor:
NamedObject<int> no1("Smallest Prime Number", 2);
NamedObject<int> no2(no1);
// calls copy constructor
36A
The
Item 5
copy
constructor
Chapter 2
generated
by
compilers
must
initialize
no2.nameValue and no2.objectValue using no1.nameValue and no1.objectValue, respectively. The type of nameValue is string, and the standard
string type has a copy constructor, so no2.nameValue will 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 int is a built-in
type, so no2.objectValue will be initialized by copying the bits in
no1.objectValue.
The compiler-generated copy assignment operator for NamedObject<int> would behave essentially the same way, but in general, compiler-generated copy assignment operators behave as I’ve described
only when the resulting code is both legal and has a reasonable
chance of making sense. If either of these tests fails, compilers will
refuse to generate an operator= for your class.
For example, suppose NamedObject were defined like this, where
nameValue is a reference to a string and objectValue is a const T:
template<typename T>
class NamedObject {
public:
// this ctor no longer takes a const name, because nameValue
// is now a reference-to-non-const string. The char* constructor
// is gone, because we must have a string to refer to.
NamedObject(std::string& name, const T& value);
...
// as above, assume no
// operator= is declared
private:
std::string& nameValue;
// this is now a reference
const T objectValue;
// this is now const
};
Now consider what should happen here:
std::string newDog("Persephone");
std::string oldDog("Satch");
NamedObject<int> p(newDog, 2);
NamedObject<int> s(oldDog, 36);
p = s;
// when I originally wrote this, our
// dog Persephone was about to
// have her second birthday
// the family dog Satch (from my
// childhood) would be 36 if she
// were still alive
// what should happen to
// the data members in p?
Before the assignment, both p.nameValue and s.nameValue refer to string
objects, though not the same ones. How should the assignment affect
p.nameValue? After the assignment, should p.nameValue refer to the
Constructors, Destructors, operator=
Item 6
37A
string referred to by s.nameValue, i.e., should the reference itself be
modified? If so, that breaks new ground, because C++ doesn’t provide
a way to make a reference refer to a different object. Alternatively,
should the string object to which p.nameValue refers be modified, thus
affecting other objects that hold pointers or references to that string,
i.e., objects not directly involved in the assignment? Is that what the
compiler-generated copy assignment operator should do?
Faced with this conundrum, C++ refuses to compile the code. If you
want to support copy assignment in a class containing a reference
member, you must define the copy assignment operator yourself.
Compilers behave similarly for classes containing const members
(such as objectValue in the modified class above). It’s not legal to modify const members, so compilers are unsure how to treat them during
an implicitly generated assignment function. Finally, compilers reject
implicit copy assignment operators in derived classes that inherit
from base classes declaring the copy assignment operator private.
After all, compiler-generated copy assignment operators for derived
classes are supposed to handle base class parts, too (see Item 12), but
in doing so, they certainly can’t invoke member functions the derived
class has no right to call.
Things to Remember
✦
Compilers may implicitly generate a class’s default constructor, copy
constructor, copy assignment operator, and destructor.
Item 6:
Explicitly disallow the use of compilergenerated functions you do not want.
Real estate agents sell houses, and a software system supporting such
agents would naturally have a class representing homes for sale:
class HomeForSale { ... };
As every real estate agent will be quick to point out, every property is
unique — no two are exactly alike. That being the case, the idea of
making a copy of a HomeForSale object makes little sense. How can
you copy something that’s inherently unique? You’d thus like
attempts to copy HomeForSale objects to not compile:
HomeForSale h1;
HomeForSale h2;
HomeForSale h3(h1);
h1 = h2;
// attempt to copy h1 — should
// not compile!
// attempt to copy h2 — should
// not compile!
38A
Item 6
Chapter 2
Alas, preventing such compilation isn’t completely straightforward.
Usually, if you don’t want a class to support a particular kind of functionality, you simply don’t declare the function that would provide it.
This strategy doesn’t work for the copy constructor and copy assignment operator, because, as Item 5 points out, if you don’t declare
them and somebody tries to call them, compilers declare them for you.
This puts you in a bind. If you don’t declare a copy constructor or a
copy assignment operator, compilers may generate them for you. Your
class thus supports copying. If, on the other hand, you do declare
these functions, your class still supports copying. But the goal here is
to prevent copying!
The key to the solution is that all the compiler generated functions are
public. To prevent these functions from being generated, you must
declare them yourself, but there is nothing that requires that you
declare them public. Instead, declare the copy constructor and the
copy assignment operator private. By declaring a member function
explicitly, you prevent compilers from generating their own version,
and by making the function private, you keep people from calling it.
Mostly. The scheme isn’t foolproof, because member and friend functions can still call your private functions. Unless, that is, you are
clever enough not to define them. Then if somebody inadvertently
calls one, they’ll get an error at link-time. This trick — declaring member functions private and deliberately not implementing them — is so
well established, it’s used to prevent copying in several classes in
C++’s iostreams library. Take a look, for example, at the definitions of
ios_base, basic_ios, and sentry in your standard library implementation.
You’ll find that in each case, both the copy constructor and the copy
assignment operator are declared private and are not defined.
Applying the trick to HomeForSale is easy:
class HomeForSale {
public:
...
private:
...
HomeForSale(const HomeForSale&);
HomeForSale& operator=(const HomeForSale&);
};
// declarations only
You’ll note that I’ve omitted the names of the functions’ parameters.
This isn’t required, it’s just a common convention. After all, the functions will never be implemented, much less used, so what’s the point
in specifying parameter names?
With the above class definition, compilers will thwart client attempts
to copy HomeForSale objects, and if you inadvertently try to do it in a
Constructors, Destructors, operator=
Item 6
39A
member or a friend function, the linker will complain.
It’s possible to move the link-time error up to compile time (always a
good thing — earlier error detection is better than later) by declaring
the copy constructor and copy assignment operator private not in
HomeForSale itself, but in a base class specifically designed to prevent
copying. The base class is simplicity itself:
class Uncopyable {
protected:
Uncopyable() {}
~Uncopyable() {}
private:
Uncopyable(const Uncopyable&);
Uncopyable& operator=(const Uncopyable&);
};
// allow construction
// and destruction of
// derived objects...
// ...but prevent copying
To keep HomeForSale objects from being copied, all we have to do now
is inherit from Uncopyable:
class HomeForSale: private Uncopyable {
...
};
// class no longer
// declares copy ctor or
// copy assign. operator
This works, because compilers will try to generate a copy constructor
and a copy assignment operator if anybody — even a member or friend
function — tries to copy a HomeForSale object. As Item 12 explains, the
compiler-generated versions of these functions will try to call their
base class counterparts, and those calls will be rejected, because the
copying operations are private in the base class.
The implementation and use of Uncopyable include some subtleties,
such as the fact that inheritance from Uncopyable needn’t be public
(see Items 32 and 39) and that Uncopyable’s destructor need not be
virtual (see Item 7). Because Uncopyable contains no data, it’s eligible
for the empty base class optimization described in Item 39, but
because it’s a base class, use of this technique could lead to multiple
inheritance (see Item 40). Multiple inheritance, in turn, can sometimes disable the empty base class optimization (again, see Item 39).
In general, you can ignore these subtleties and just use Uncopyable as
shown, because it works precisely as advertised. You can also use the
version available at Boost (see Item 55). That class is named noncopyable. It’s a fine class, I just find the name a bit un-, er, nonnatural.
Things to Remember
✦
To disallow functionality automatically provided by compilers, declare the corresponding member functions private and give no implementations. Using a base class like Uncopyable is one way to do this.
40A
Item 7
Item 7:
Chapter 2
Declare destructors virtual in polymorphic
base classes.
There are lots of ways to keep track of time, so it would be reasonable
to create a TimeKeeper base class along with derived classes for different approaches to timekeeping:
class TimeKeeper {
public:
TimeKeeper();
~TimeKeeper();
...
};
class AtomicClock: public TimeKeeper { ... };
class WaterClock: public TimeKeeper { ... };
class WristWatch: public TimeKeeper { ... };
Many clients will want access to the time without worrying about the
details of how it’s calculated, so a factory function — a function that
returns a base class pointer to a newly-created derived class object —
can be used to return a pointer to a timekeeping object:
TimeKeeper* getTimeKeeper();
// returns a pointer to a dynamic// ally allocated object of a class
// derived from TimeKeeper
In keeping with the conventions of factory functions, the objects
returned by getTimeKeeper are on the heap, so to avoid leaking memory and other resources, it’s important that each returned object be
properly deleted:
TimeKeeper *ptk = getTimeKeeper(); // get dynamically allocated object
// from TimeKeeper hierarchy
...
// use it
delete ptk;
// release it to avoid resource leak
Item 13 explains that relying on clients to perform the deletion is
error-prone, and Item 18 explains how the interface to the factory
function can be modified to prevent common client errors, but such
concerns are secondary here, because in this Item we address a more
fundamental weakness of the code above: even if clients do everything
right, there is no way to know how the program will behave.
The problem is that getTimeKeeper returns a pointer to a derived class
object (e.g., AtomicClock), that object is being deleted via a base class
pointer (i.e., a TimeKeeper* pointer), and the base class ( TimeKeeper)
has a non-virtual destructor. This is a recipe for disaster, because C++
Constructors, Destructors, operator=
Item 7
41A
specifies that when a derived class object is deleted through a pointer
to a base class with a non-virtual destructor, results are undefined.
What typically happens at runtime is that the derived part of the
object is never destroyed. If getTimeKeeper were to return a pointer to
an AtomicClock object, the AtomicClock part of the object (i.e., the data
members declared in the AtomicClock class) would probably not be
destroyed, nor would the AtomicClock destructor run. However, the
base class part (i.e., the TimeKeeper part) typically would be destroyed,
thus leading to a curious “partially destroyed” object. This is an excellent way to leak resources, corrupt data structures, and spend a lot of
time with a debugger.
Eliminating the problem is simple: give the base class a virtual
destructor. Then deleting a derived class object will do exactly what
you want. It will destroy the entire object, including all its derived
class parts:
class TimeKeeper {
public:
TimeKeeper();
virtual ~TimeKeeper();
...
};
TimeKeeper *ptk = getTimeKeeper();
...
delete ptk;
// now behaves correctly
Base classes like TimeKeeper generally contain virtual functions other
than the destructor, because the purpose of virtual functions is to
allow customization of derived class implementations (see Item 34).
For example, TimeKeeper might have a virtual function, getCurrentTime,
which would be implemented differently in the various derived
classes. Any class with virtual functions should almost certainly have
a virtual destructor.
If a class does not contain virtual functions, that often indicates it is
not meant to be used as a base class. When a class is not intended to
be a base class, making the destructor virtual is usually a bad idea.
Consider a class for representing points in two-dimensional space:
class Point {
public:
Point(int xCoord, int yCoord);
~Point();
private:
int x, y;
};
// a 2D point
42A
Item 7
Chapter 2
If an int occupies 32 bits, a Point object can typically fit into a 64-bit
register. Furthermore, such a Point object can be passed as a 64-bit
quantity to functions written in other languages, such as C or FORTRAN. If Point’s destructor is made virtual, however, the situation
changes.
The implementation of virtual functions requires that objects carry
information that can be used at runtime to determine which virtual
functions should be invoked on the object. This information typically
takes the form of a pointer called a vptr (“virtual table pointer”). The
vptr points to an array of function pointers called a vtbl (“virtual
table”); each class with virtual functions has an associated vtbl. When
a virtual function is invoked on an object, the actual function called is
determined by following 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. What is important is that if the Point class contains a virtual function, objects of that type will increase in size. On a 32-bit architecture,
they’ll go from 64 bits (for the two ints) to 96 bits (for the ints plus the
vptr); on a 64-bit architecture, they may go from 64 to 128 bits,
because pointers on such architectures are 64 bits in size. Addition of
a vptr to Point will thus increase its size by 50–100%! No longer can
Point objects fit in a 64-bit register. Furthermore, Point objects in C++
can no longer look like the same structure declared in another language such as C, because their foreign language counterparts will
lack the vptr. As a result, it is no longer possible to pass Points to and
from functions written in other languages unless you explicitly 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 them virtual. In fact, many people
summarize the situation this way: declare a virtual destructor in a
class if and only if that class contains at least one virtual function.
It is possible to get bitten by the non-virtual destructor problem even
in the complete absence of virtual functions. For example, the standard string type contains no virtual functions, but misguided programmers sometimes use it as a base class anyway:
class SpecialString: public std::string {
...
};
// bad idea! std::string has a
// non-virtual destructor
At first glance, this may look innocuous, but if anywhere in an application you somehow convert a pointer-to-SpecialString into a pointer-to-
Constructors, Destructors, operator=
Item 7
43A
string and you then use delete on the string pointer, you are instantly
transported to the realm of undefined behavior:
SpecialString *pss =new SpecialString("Impending Doom");
std::string *ps;
...
ps = pss;
// SpecialString* ⇒ std::string*
...
delete ps;
// undefined! In practice,
// *ps’s SpecialString resources
// will be leaked, because the
// SpecialString destructor won’t
// be called.
The same analysis applies to any class lacking a virtual destructor,
including all the STL container types (e.g., vector, list, set,
tr1::unordered_map (see Item 54), etc.). If you’re ever tempted to inherit
from a standard container or any other class with a non-virtual
destructor, resist the temptation! (Unfortunately, C++ offers no derivation-prevention mechanism akin to Java’s final classes or C#’s sealed
classes.)
Occasionally it can be convenient to give a class a pure virtual
destructor. Recall that pure virtual functions result in abstract classes
— classes that can’t be instantiated (i.e., you can’t create objects of
that type). Sometimes, however, you have a class that you’d like to be
abstract, but you don’t have any pure virtual functions. What to do?
Well, because an abstract class is intended to be used as a base class,
and because a base class should have a virtual destructor, and
because a pure virtual function yields an abstract class, the solution
is simple: declare a pure virtual destructor in the class you want to be
abstract. Here’s an example:
class AWOV {
public:
virtual ~AWOV() = 0;
};
// AWOV = “Abstract w/o Virtuals”
// declare pure virtual destructor
This class has a pure virtual function, so it’s abstract, and it has a virtual destructor, so you 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 dtor
The way destructors work is that the most derived class’s destructor
is called first, then the destructor of each base class is called. Compil-
44A
Item 8
Chapter 2
ers will generate a call to ~AWOV from its derived classes’ destructors,
so you have to be sure to provide a body for the function. If you don’t,
the linker will complain.
The rule for giving base classes virtual destructors applies only to
polymorphic base classes — to base classes designed to allow the
manipulation of derived class types through base class interfaces.
TimeKeeper is a polymorphic base class, because we expect to be able
to manipulate AtomicClock and WaterClock objects, even if we have only
TimeKeeper pointers to them.
Not all base classes are designed to be used polymorphically. Neither
the standard string type, for example, nor the STL container types are
designed to be base classes at all, much less polymorphic ones. Some
classes are designed to be used as base classes, yet are not designed
to be used polymorphically. Such classes — examples include Uncopyable from Item 6 and input_iterator_tag from the standard library (see
Item 47) — are not designed to allow the manipulation of derived class
objects via base class interfaces. As a result, they don’t need virtual
destructors.
Things to Remember
✦
Polymorphic base classes should declare virtual destructors. If a
class has any virtual functions, it should have a virtual destructor.
✦
Classes not designed to be base classes or not designed to be used
polymorphically should not declare virtual destructors.
Item 8:
Prevent exceptions from leaving destructors.
C++ doesn’t prohibit destructors from emitting exceptions, but it certainly discourages the practice. With good reason. Consider:
class Widget {
public:
...
~Widget() { ... }
};
void doSomething()
{
std::vector<Widget> v;
...
}
// assume this might emit an exception
// v is automatically destroyed here
When the vector v is destroyed, it is responsible for destroying all the
Widgets it contains. Suppose v has ten Widgets in it, and during
destruction of the first one, an exception is thrown. The other nine
Constructors, Destructors, operator=
Item 8
45A
Widgets still have to be destroyed (otherwise any resources they hold
would be leaked), so v should invoke their destructors. But suppose
that during those calls, a second Widget destructor throws an exception. Now there are two simultaneously active exceptions, and that’s
one too many for C++. Depending on the precise conditions under
which such pairs of simultaneously active exceptions arise, program
execution either terminates or yields undefined behavior. In this
example, it yields undefined behavior. It would yield equally undefined
behavior using any other standard library container (e.g., list, set), any
container in TR1 (see Item 54), or even an array. Not that containers
or arrays are required to get into trouble. Premature program termination or undefined behavior can result from destructors emitting
exceptions even without using containers and arrays. C++ does not
like destructors that emit exceptions!
That’s easy enough to understand, but what should you do if your
destructor needs to perform an operation that may fail by throwing an
exception? For example, suppose you’re working with a class for database connections:
class DBConnection {
public:
...
static DBConnection create();
void close();
};
// function to return
// DBConnection objects; params
// omitted for simplicity
// close connection; throw an
// exception if closing fails
To ensure that clients don’t forget to call close on DBConnection objects,
a reasonable idea would be to create a resource-managing class for
DBConnection that calls close in its destructor. Such resource-managing
classes are explored in detail in Chapter 3, but here, it’s enough to
consider what the destructor for such a class would look like:
class DBConn {
public:
...
~DBConn()
{
db.close();
}
private:
DBConnection db;
};
// class to manage DBConnection
// objects
// make sure database connections
// are always closed
That allows clients to program like this:
46A
Item 8
{
DBConn dbc(DBConnection::create());
...
}
Chapter 2
// open a block
// create DBConnection object
// and turn it over to a DBConn
// object to manage
// use the DBConnection object
// via the DBConn interface
// at end of block, the DBConn
// object is destroyed, thus
// automatically calling close on
// the DBConnection object
This is fine as long as the call to close succeeds, but if the call yields
an exception, DBConn’s destructor will propagate that exception, i.e.,
allow it to leave the destructor. That’s a problem, because destructors
that throw mean trouble.
There are two primary ways to avoid the trouble. DBConn’s destructor
could:
■
Terminate the program if close throws, typically by calling abort:
DBConn::~DBConn()
{
try { db.close(); }
catch (...) {
make log entry that the call to close failed;
std::abort();
}
}
This is a reasonable option if the program cannot continue to run
after an error is encountered during destruction. It has the advantage that if allowing the exception to propagate from the destructor
would lead to undefined behavior, this prevents that from happening. That is, calling abort may forestall undefined behavior.
■
Swallow the exception arising from the call to close:
DBConn::~DBConn()
{
try { db.close(); }
catch (...) {
make log entry that the call to close failed;
}
}
In general, swallowing exceptions is a bad idea, because it suppresses important information — something failed! Sometimes,
however, swallowing exceptions is preferable to running the risk of
Constructors, Destructors, operator=
Item 8
47A
premature program termination or undefined behavior. For this to
be a viable option, the program must be able to reliably continue
execution even after an error has been encountered and ignored.
Neither of these approaches is especially appealing. The problem with
both is that the program has no way to react to the condition that led
to close throwing an exception in the first place.
A better strategy is to design DBConn’s interface so that its clients have
an opportunity to react to problems that may arise. For example,
DBConn could offer a close function itself, thus giving clients a chance
to handle exceptions arising from that operation. It could also keep
track of whether its DBConnection had been closed, closing it itself in
the destructor if not. That would prevent a connection from leaking. If
the call to close were to fail in the DBConnection destructor, however,
we’d be back to terminating or swallowing:
class DBConn {
public:
...
void close()
{
db.close();
closed = true;
}
~DBConn()
{
if (!closed) {
try {
db.close();
}
catch (...) {
make log entry that call to close failed;
...
// new function for
// client use
// close the connection
// if the client didn’t
// if closing fails,
// note that and
// terminate or swallow
}
}
}
private:
DBConnection db;
bool closed;
};
Moving the responsibility for calling close from DBConn’s destructor to
DBConn’s client (with DBConn’s destructor containing a “backup” call)
may strike you as an unscrupulous shift of burden. You might even
view it as a violation of Item 18’s advice to make interfaces easy to use
correctly. In fact, it’s neither. If an operation may fail by throwing an
exception and there may be a need to handle that exception, the
exception has to come from some non-destructor function. That’s
48A
Item 9
Chapter 2
because destructors that emit exceptions are dangerous, always running the risk of premature program termination or undefined behavior. In this example, telling clients to call close themselves doesn’t
impose a burden on them; it gives them an opportunity to deal with
errors they would otherwise have no chance to react to. If they don’t
find that opportunity useful (perhaps because they believe that no
error will really occur), they can ignore it, relying on DBConn’s destructor to call close for them. If an error occurs at that point — if close does
throw — they’re in no position to complain if DBConn swallows the
exception or terminates the program. After all, they had first crack at
dealing with the problem, and they chose not to use it.
Things to Remember
✦
Destructors should never emit exceptions. If functions called in a
destructor may throw, the destructor should catch any exceptions,
then swallow them or terminate the program.
✦
If class clients need to be able to react to exceptions thrown during
an operation, the class should provide a regular (i.e., non-destructor) function that performs the operation.
Item 9:
Never call virtual functions during
construction or destruction.
I’ll begin with the recap: you shouldn’t call virtual functions during
construction or destruction, because the calls won’t do what you
think, and if they did, you’d still be unhappy. If you’re a recovering
Java or C# programmer, pay close attention to this Item, because this
is a place where those languages zig, while C++ zags.
Suppose you’ve got a class hierarchy for modeling stock transactions,
e.g., buy orders, sell orders, etc. It’s important that such transactions
be auditable, so each time a transaction object is created, an appropriate entry needs to be created in an audit log. This seems like a reasonable way to approach the problem:
class Transaction {
public:
Transaction();
virtual void logTransaction() const = 0;
...
};
// base class for all
// transactions
// make type-dependent
// log entry
Constructors, Destructors, operator=
Transaction::Transaction()
{
...
logTransaction();
}
class BuyTransaction: public Transaction {
public:
virtual void logTransaction() const;
Item 9
49A
// implementation of
// base class ctor
// as final action, log this
// transaction
// derived class
// how to log trans// actions of this type
...
};
class SellTransaction: public Transaction {
public:
virtual void logTransaction() const;
// derived class
// how to log trans// actions of this type
...
};
Consider what happens when this code is executed:
BuyTransaction b;
Clearly a BuyTransaction constructor will be called, but first, a Transaction constructor must be called; base class parts of derived class
objects are constructed before derived class parts are. The last line of
the Transaction constructor calls the virtual function logTransaction, but
this is where the surprise comes in. The version of logTransaction that’s
called is the one in Transaction, not the one in BuyTransaction — even
though the type of object being created is BuyTransaction. During base
class construction, virtual functions never go down into derived
classes. Instead, the object behaves as if it were of the base type.
Informally speaking, during base class construction, virtual functions
aren’t.
There’s a good reason for this seemingly counterintuitive behavior.
Because base class constructors execute before derived class constructors, derived class data members have not been initialized when
base class constructors run. If virtual functions called during base
class construction went down to derived classes, the derived class
functions would almost certainly refer to local data members, but
those data members would not yet have been initialized. That would
be a non-stop ticket to undefined behavior and late-night debugging
sessions. Calling down to parts of an object that have not yet been initialized is inherently dangerous, so C++ gives you no way to do it.
It’s actually more fundamental than that. During base class construction of a derived class object, the type of the object is that of the base
50A
Item 9
Chapter 2
class. Not only do virtual functions resolve to the base class, but the
parts of the language using runtime type information (e.g.,
dynamic_cast (see Item 27) and typeid) treat the object as a base class
type. In our example, while the Transaction constructor is running to
initialize the base class part of a BuyTransaction object, the object is of
type Transaction. That’s how every part of C++ will treat it, and the
treatment makes sense: the BuyTransaction-specific parts of the object
haven’t been initialized yet, so it’s safest to treat them as if they didn’t
exist. An object doesn’t become a derived class object until execution
of a derived class constructor begins.
The same reasoning applies during destruction. Once a derived class
destructor has run, the object’s derived class data members assume
undefined values, so C++ treats them as if they no longer exist. Upon
entry to the base class destructor, the object becomes a base class
object, and all parts of C++ — virtual functions, dynamic_casts, etc., —
treat it that way.
In the example code above, the Transaction constructor made a direct
call to a virtual function, a clear and easy-to-see violation of this
Item’s guidance. The violation is so easy to see, some compilers issue
a warning about it. (Others don’t. See Item 53 for a discussion of
warnings.) Even without such a warning, the problem would almost
certainly become apparent before runtime, because the logTransaction
function is pure virtual in Transaction. Unless it had been defined
(unlikely, but possible — see Item 34), the program wouldn’t link: the
linker would be unable to find the necessary implementation of Transaction::logTransaction.
It’s not always so easy to detect calls to virtual functions during construction or destruction. If Transaction had multiple constructors, each
of which had to perform some of the same work, it would be good software engineering to avoid code replication by putting the common initialization code, including the call to logTransaction, into a private nonvirtual initialization function, say, init:
class Transaction {
public:
Transaction()
{ init(); }
virtual void logTransaction() const = 0;
...
private:
void init()
{
...
logTransaction();
}
};
// call to non-virtual...
// ...that calls a virtual!
Constructors, Destructors, operator=
Item 9
51A
This code is conceptually the same as the earlier version, but it’s more
insidious, because it will typically compile and link without complaint.
In this case, because logTransaction is pure virtual in Transaction, most
runtime systems will abort the program when the pure virtual is
called (typically issuing a message to that effect). However, if logTransaction were a “normal” virtual function (i.e., not pure virtual) with an
implementation in Transaction, that version would be called, and the
program would merrily trot along, leaving you to figure out why the
wrong version of logTransaction was called when a derived class object
was created. The only way to avoid this problem is to make sure that
none of your constructors or destructors call virtual functions on the
object being created or destroyed and that all the functions they call
obey the same constraint.
But how do you ensure that the proper version of logTransaction is
called each time an object in the Transaction hierarchy is created?
Clearly, calling a virtual function on the object from the Transaction
constructor(s) is the wrong way to do it.
There are different ways to approach this problem. One is to turn logTransaction into a non-virtual function in Transaction, then require that
derived class constructors pass the necessary log information to the
Transaction constructor. That function can then safely call the non-virtual logTransaction. Like this:
class Transaction {
public:
explicit Transaction(const std::string& logInfo);
void logTransaction(const std::string& logInfo) const;
// now a non// virtual func
...
};
Transaction::Transaction(const std::string& logInfo)
{
...
logTransaction(logInfo);
}
class BuyTransaction: public Transaction {
public:
BuyTransaction( parameters )
: Transaction(createLogString( parameters ))
{ ... }
...
private:
static std::string createLogString( parameters );
};
// now a non// virtual call
// pass log info
// to base class
// constructor
52A
Item 10
Chapter 2
In other words, since you can’t use virtual functions to call down from
base classes during construction, you can compensate by having
derived classes pass necessary construction information up to base
class constructors instead.
In this example, note the use of the (private) static function createLogString in BuyTransaction. Using a helper function to create a value to
pass to a base class constructor is often more convenient (and more
readable) than going through contortions in the member initialization
list to give the base class what it needs. By making the function static,
there’s no danger of accidentally referring to the nascent BuyTransaction object’s as-yet-uninitialized data members. That’s important,
because the fact that those data members will be in an undefined
state is why calling virtual functions during base class construction
and destruction doesn’t go down into derived classes in the first place.
Things to Remember
✦
Don’t call virtual functions during construction or destruction, because such calls will never go to a more derived class than that of
the currently executing constructor or destructor.
Item 10: Have assignment operators return a
reference to *this.
One of the interesting things about assignments is that you can chain
them together:
int x, y, z;
x = y = z = 15;
// chain of assignments
Also interesting is that assignment is right-associative, so the above
assignment chain is parsed like this:
x = (y = (z = 15));
Here, 15 is assigned to z, then the result of that assignment (the
updated z) is assigned to y, then the result of that assignment (the
updated y) is assigned to x.
The way this is implemented is that assignment returns a reference to
its left-hand argument, and that’s the convention you should follow
when you implement assignment operators for your classes:
class Widget {
public:
...
Constructors, Destructors, operator=
Item 11
Widget& operator=(const Widget& rhs)
{
...
return *this;
}
...
53A
// return type is a reference to
// the current class
// return the left-hand object
};
This convention applies to all assignment operators, not just the standard form shown above. Hence:
class Widget {
public:
...
Widget& operator+=(const Widget& rhs)
{
...
return *this;
}
Widget& operator=(int rhs)
{
...
return *this;
}
...
};
// the convention applies to
// +=, -=, *=, etc.
// it applies even if the
// operator’s parameter type
// is unconventional
This is only a convention; code that doesn’t follow it will compile. However, the convention is followed by all the built-in types as well as by
all the types in (or soon to be in — see Item 54) the standard library
(e.g., string, vector, complex, tr1::shared_ptr, etc.). Unless you have a
good reason for doing things differently, don’t.
Things to Remember
✦
Have assignment operators return a reference to *this.
Item 11: Handle assignment to self in operator=.
An assignment to self occurs when an object is assigned to itself:
class Widget { ... };
Widget w;
...
w = w;
// assignment to self
This looks silly, but it’s legal, so rest assured that clients will do it.
Besides, assignment isn’t always so recognizable. For example,
54A
Item 11
a[i] = a[j];
Chapter 2
// potential assignment to self
is an assignment to self if i and j have the same value, and
*px = *py;
// potential assignment to self
is an assignment to self if px and py happen to point to the same
thing. These less obvious assignments to self are the result of aliasing:
having more than one way to refer to an object. In general, code that
operates on references or pointers to multiple objects of the same type
needs to consider that the objects might be the same. In fact, the two
objects need not even be declared to be of the same type if they’re from
the same hierarchy, because a base class reference or pointer can
refer or point to an object of a derived class type:
class Base { ... };
class Derived: public Base { ... };
void doSomething(const Base& rb,
Derived* pd);
// rb and *pd might actually be
// the same object
If you follow the advice of Items 13 and 14, you’ll always use objects to
manage resources, and you’ll make sure that the resource-managing
objects behave well when copied. When that’s the case, your assignment operators will probably be self-assignment-safe without your
having to think about it. If you try to manage resources yourself, however (which you’d certainly have to do if you were writing a resourcemanaging class), you can fall into the trap of accidentally releasing a
resource before you’re done using it. For example, suppose you create
a class that holds a raw pointer to a dynamically allocated bitmap:
class Bitmap { ... };
class Widget {
...
private:
Bitmap *pb;
};
// ptr to a heap-allocated object
Here’s an implementation of operator= that looks reasonable on the
surface but is unsafe in the presence of assignment to self. (It’s also
not exception-safe, but we’ll deal with that in a moment.)
Widget&
Widget::operator=(const Widget& rhs)
{
delete pb;
pb = new Bitmap(*rhs.pb);
return *this;
}
// unsafe impl. of operator=
// stop using current bitmap
// start using a copy of rhs’s bitmap
// see Item 10
Constructors, Destructors, operator=
Item 11
55A
The self-assignment problem here is that inside operator=, *this (the
target of the assignment) and rhs could be the same object. When they
are, the delete not only destroys the bitmap for the current object, it
destroys the bitmap for rhs, too. At the end of the function, the Widget
— which should not have been changed by the assignment to self —
finds itself holding a pointer to a deleted object!†
The traditional way to prevent this error is to check for assignment to
self via an identity test at the top of operator=:
Widget& Widget::operator=(const Widget& rhs)
{
if (this == &rhs) return *this;
// identity test: if a self-assignment,
// do nothing
delete pb;
pb = new Bitmap(*rhs.pb);
return *this;
}
This works, but I mentioned above that the previous version of operator= wasn’t just self-assignment-unsafe, it was also exception-unsafe,
and this version continues to have exception trouble. In particular, if
the “new Bitmap” expression yields an exception (either because there
is insufficient memory for the allocation or because Bitmap’s copy constructor throws one), the Widget will end up holding a pointer to a
deleted Bitmap. Such pointers are toxic. You can’t safely delete them.
You can’t even safely read them. About the only safe thing you can do
with them is spend lots of debugging energy figuring out where they
came from.
Happily, making operator= exception-safe typically renders it selfassignment-safe, too. As a result, it’s increasingly common to deal
with issues of self-assignment by ignoring them, focusing instead on
achieving exception safety. Item 29 explores exception safety in depth,
but in this Item, it suffices to observe that in many cases, a careful
ordering of statements can yield exception-safe (and self-assignmentsafe) code. Here, for example, we just have to be careful not to delete
pb until after we’ve copied what it points to:
Widget& Widget::operator=(const Widget& rhs)
{
Bitmap *pOrig = pb;
// remember original pb
pb = new Bitmap(*rhs.pb);
// make pb point to a copy of *pb
delete pOrig;
// delete the original pb
return *this;
}
† Probably. C++ implementations are permitted to change the value of a deleted pointer
(e.g., to null or some other special bit pattern), but I am unaware of any that do.
56A
Item 11
Chapter 2
Now, if “new Bitmap” throws an exception, pb (and the Widget it’s inside
of) remains unchanged. Even without the identity test, this code handles assignment to self, because we make a copy of the original bitmap, delete the original bitmap, then point to the copy we made. It
may not be the most efficient way to handle self-assignment, but it
does work.
If you’re concerned about efficiency, you could put the identity test
back at the top of the function. Before doing that, however, ask yourself how often you expect self-assignments to occur, because the test
isn’t free. It makes the code (both source and object) a bit bigger, and
it introduces a branch into the flow of control, both of which can
decrease runtime speed. The effectiveness of instruction prefetching,
caching, and pipelining can be reduced, for example.
An alternative to manually ordering statements in operator= to make
sure the implementation is both exception- and self-assignment-safe
is to use the technique known as “copy and swap.” This technique is
closely associated with exception safety, so it’s described in Item 29.
However, it’s a common enough way to write operator= that it’s worth
seeing what such an implementation often looks like:
class Widget {
...
void swap(Widget& rhs);
// exchange *this’s and rhs’s data;
...
// see Item 29 for details
};
Widget& Widget::operator=(const Widget& rhs)
{
Widget temp(rhs);
// make a copy of rhs’s data
swap(temp);
// swap *this’s data with the copy’s
return *this;
}
A variation on this theme takes advantage of the facts that (1) a class’s
copy assignment operator may be declared to take its argument by
value and (2) passing something by value makes a copy of it (see
Item 20):
Widget& Widget::operator=(Widget rhs)
{
swap(rhs);
}
return *this;
// rhs is a copy of the object
// passed in — note pass by val
// swap *this’s data with
// the copy’s
Constructors, Destructors, operator=
Item 12
57A
Personally, I worry that this approach sacrifices clarity at the altar of
cleverness, but by moving the copying operation from the body of the
function to construction of the parameter, it’s a fact that compilers
can sometimes generate more efficient code.
Things to Remember
✦
Make sure operator= is well-behaved when an object is assigned to
itself. Techniques include comparing addresses of source and target
objects, careful statement ordering, and copy-and-swap.
✦
Make sure that any function operating on more than one object behaves correctly if two or more of the objects are the same.
Item 12: Copy all parts of an object.
In well-designed object-oriented systems that encapsulate the internal
parts of objects, only two functions copy objects: the aptly named
copy constructor and copy assignment operator. We’ll call these the
copying functions. Item 5 observes that compilers will generate the
copying functions, if needed, and it explains that the compiler-generated versions do precisely what you’d expect: they copy all the data of
the object being copied.
When you declare your own copying functions, you are indicating to
compilers that there is something about the default implementations
you don’t like. Compilers seem to take offense at this, and they retaliate in a curious fashion: they don’t tell you when your implementations are almost certainly wrong.
Consider a class representing customers, where the copying functions
have been manually written so that calls to them are logged:
void logCall(const std::string& funcName);
class Customer {
public:
...
Customer(const Customer& rhs);
Customer& operator=(const Customer& rhs);
...
private:
std::string name;
};
// make a log entry
58A
Item 12
Chapter 2
Customer::Customer(const Customer& rhs)
: name(rhs.name)
// copy rhs’s data
{
logCall("Customer copy constructor");
}
Customer& Customer::operator=(const Customer& rhs)
{
logCall("Customer copy assignment operator");
name = rhs.name;
// copy rhs’s data
return *this;
// see Item 10
}
Everything here looks fine, and in fact everything is fine — until
another data member is added to Customer:
class Date { ... };
class Customer {
public:
...
private:
std::string name;
Date lastTransaction;
};
// for dates in time
// as before
At this point, the existing copying functions are performing a partial
copy: they’re copying the customer’s name, but not its lastTransaction.
Yet most compilers say nothing about this, not even at maximal warning level (see also Item 53). That’s their revenge for your writing the
copying functions yourself. You reject the copying functions they’d
write, so they don’t tell you if your code is incomplete. The conclusion
is obvious: if you add a data member to a class, you need to make
sure that you update the copying functions, too. (You’ll also need to
update all the constructors (see Items 4 and 45) as well as any nonstandard forms of operator= in the class (Item 10 gives an example). If
you forget, compilers are unlikely to remind you.)
One of the most insidious ways this issue can arise is through inheritance. Consider:
class PriorityCustomer: public Customer {
// a derived class
public:
...
PriorityCustomer(const PriorityCustomer& rhs);
PriorityCustomer& operator=(const PriorityCustomer& rhs);
...
private:
int priority;
};
Constructors, Destructors, operator=
Item 12
59A
PriorityCustomer::PriorityCustomer(const PriorityCustomer& rhs)
: priority(rhs.priority)
{
logCall("PriorityCustomer copy constructor");
}
PriorityCustomer&
PriorityCustomer::operator=(const PriorityCustomer& rhs)
{
logCall("PriorityCustomer copy assignment operator");
priority = rhs.priority;
return *this;
}
PriorityCustomer’s copying functions look like they’re copying everything in PriorityCustomer, but look again. Yes, they copy the data member that PriorityCustomer declares, but every PriorityCustomer also
contains a copy of the data members it inherits from Customer, and
those data members are not being copied at all! PriorityCustomer’s copy
constructor specifies no arguments to be passed to its base class constructor (i.e., it makes no mention of Customer on its member initialization list), so the Customer part of the PriorityCustomer object will be
initialized by the Customer constructor taking no arguments — by the
default constructor. (Assuming it has one. If not, the code won’t compile.) That constructor will perform a default initialization for name
and lastTransaction.
The situation is only slightly different for PriorityCustomer’s copy
assignment operator. It makes no attempt to modify its base class
data members in any way, so they’ll remain unchanged.
Any time you take it upon yourself to write copying functions for a
derived class, you must take care to also copy the base class parts.
Those parts are typically private, of course (see Item 22), so you can’t
access them directly. Instead, derived class copying functions must
invoke their corresponding base class functions:
PriorityCustomer::PriorityCustomer(const PriorityCustomer& rhs)
: Customer(rhs),
// invoke base class copy ctor
priority(rhs.priority)
{
logCall("PriorityCustomer copy constructor");
}
PriorityCustomer&
PriorityCustomer::operator=(const PriorityCustomer& rhs)
{
logCall("PriorityCustomer copy assignment operator");
Customer::operator=(rhs);
// assign base class parts
priority = rhs.priority;
return *this;
}
60A
Item 12
Chapter 2
The meaning of “copy all parts” in this Item’s title should now be clear.
When you’re writing a copying function, be sure to (1) copy all local
data members and (2) invoke the appropriate copying function in all
base classes, too.
In practice, the two copying functions will often have similar bodies,
and this may tempt you to try to avoid code duplication by having one
function call the other. Your desire to avoid code duplication is laudable, but having one copying function call the other is the wrong way
to achieve it.
It makes no sense to have the copy assignment operator call the copy
constructor, because you’d be trying to construct an object that
already exists. This is so nonsensical, there’s not even a syntax for it.
There are syntaxes that look like you’re doing it, but you’re not; and
there are syntaxes that do do it in a backwards kind of way, but they
corrupt your object under some conditions. So I’m not going to show
you any of those syntaxes. Simply accept that having the copy assignment operator call the copy constructor is something you don’t want
to do.
Trying things the other way around — having the copy constructor
call the copy assignment operator — is equally nonsensical. A constructor initializes new objects, but an assignment operator applies
only to objects that have already been initialized. Performing an
assignment on an object under construction would mean doing something to a not-yet-initialized object that makes sense only for an initialized object. Nonsense! Don’t try it.
Instead, if you find that your copy constructor and copy assignment
operator have similar code bodies, eliminate the duplication by creating a third member function that both call. Such a function is typically private and is often named init. This strategy is a safe, proven
way to eliminate code duplication in copy constructors and copy
assignment operators.
Things to Remember
✦
Copying functions should be sure to copy all of an object’s data
members and all of its base class parts.
✦
Don’t try to implement one of the copying functions in terms of the
other. Instead, put common functionality in a third function that
both call.
Resource
Management
Chapter 3: Resource Management
A resource is something that, once you’re done using it, you need to
return to the system. If you don’t, bad things happen. In C++ programs, the most commonly used resource is dynamically allocated
memory (if you allocate memory and never deallocate it, you’ve got a
memory leak), but memory is only one of many resources you must
manage. Other common resources include file descriptors, mutex
locks, fonts and brushes in graphical user interfaces (GUIs), database
connections, and network sockets. Regardless of the resource, it’s
important that it be released when you’re finished with it.
Resource Management
Trying to ensure this by hand is difficult under any conditions, but
when you consider exceptions, functions with multiple return paths,
and maintenance programmers modifying software without fully comprehending the impact of their changes, it becomes clear that ad hoc
ways of dealing with resource management aren’t sufficient.
This chapter begins with a straightforward object-based approach to
resource management built on C++’s support for constructors,
destructors, and copying operations. Experience has shown that disciplined adherence to this approach can all but eliminate resource
management problems. The chapter then moves on to Items dedicated
specifically to memory management. These latter Items complement
the more general Items that come earlier, because objects that manage memory have to know how to do it properly.
Item 13: Use objects to manage resources.
Suppose we’re working with a library for modeling investments (e.g.,
stocks, bonds, etc.), where the various investment types inherit from a
root class Investment:
class Investment { ... };
// root class of hierarchy of
// investment types
62A
Item 13
Chapter 3
Further suppose that the way the library provides us with specific
Investment objects is through a factory function (see Item 7):
Investment* createInvestment();
// return ptr to dynamically allocated
// object in the Investment hierarchy;
// the caller must delete it
// (parameters omitted for simplicity)
As the comment indicates, callers of createInvestment are responsible
for deleting the object that function returns when they are done with
it. Consider, then, a function f written to fulfill this obligation:
void f()
{
Investment *pInv = createInvestment();
...
delete pInv;
}
// call factory function
// use pInv
// release object
This looks okay, but there are several ways f could fail to delete the
investment object it gets from createInvestment. There might be a premature return statement somewhere inside the “...” part of the function. If such a return were executed, control would never reach the
delete statement. A similar situation would arise if the uses of createInvestment and delete were in a loop, and the loop was prematurely
exited by a break or goto statement. Finally, some statement inside the
“...” might throw an exception. If so, control would again not get to the
delete. Regardless of how the delete were to be skipped, we’d leak not
only the memory containing the investment object but also any
resources held by that object.
Of course, careful programming could prevent these kinds of errors,
but think about how the code might change over time. As the software
gets maintained, somebody might add a return or continue statement
without fully grasping the repercussions on the rest of the function’s
resource management strategy. Even worse, the “...” part of f might
call a function that never used to throw an exception but suddenly
starts doing so after it has been “improved.” Relying on f always getting to its delete statement simply isn’t viable.
To make sure that the resource returned by createInvestment is always
released, we need to put that resource inside an object whose destructor will automatically release the resource when control leaves f. In
fact, that’s half the idea behind this Item: by putting resources inside
objects, we can rely on C++’s automatic destructor invocation to make
sure that the resources are released. (We’ll discuss the other half of
the idea in a moment.)
Resource Management
Item 13
63A
Many resources are dynamically allocated on the heap, are used only
within a single block or function, and should be released when control
leaves that block or function. The standard library’s auto_ptr is tailormade for this kind of situation. auto_ptr is a pointer-like object (a
smart pointer) whose destructor automatically calls delete on what it
points to. Here’s how to use auto_ptr to prevent f’s potential resource
leak:
void f()
{
std::auto_ptr<Investment> pInv(createInvestment()); // call factory
// function
...
// use pInv as
// before
}
// automatically
// delete pInv via
// auto_ptr’s dtor
This simple example demonstrates the two critical aspects of using
objects to manage resources:
■
Resources are acquired and immediately turned over to resource-managing objects. Above, the resource returned by createInvestment is used to initialize the auto_ptr that will manage it. In
fact, the idea of using objects to manage resources is often called
Resource Acquisition Is Initialization (RAII), because it’s so common
to acquire a resource and initialize a resource-managing object in
the same statement. Sometimes acquired resources are assigned
to resource-managing objects instead of initializing them, but either way, every resource is immediately turned over to a resourcemanaging object at the time the resource is acquired.
■
Resource-managing objects use their destructors to ensure
that resources are released. Because destructors are called automatically when an object is destroyed (e.g., when an object goes
out of scope), resources are correctly released, regardless of how
control leaves a block. Things can get tricky when the act of releasing resources can lead to exceptions being thrown, but that’s a
matter addressed by Item 8, so we’ll not worry about it here.
Because an auto_ptr automatically deletes what it points to when the
auto_ptr is destroyed, it’s important that there never be more than one
auto_ptr pointing to an object. If there were, the object would be
deleted more than once, and that would put your program on the fast
track to undefined behavior. To prevent such problems, auto_ptrs have
an unusual characteristic: copying them (via copy constructor or copy
64A
Item 13
Chapter 3
assignment operator) sets them to null, and the copying pointer
assumes sole ownership of the resource!
std::auto_ptr<Investment>
pInv1(createInvestment());
std::auto_ptr<Investment> pInv2(pInv1);
pInv1 = pInv2;
// pInv1 points to the
// object returned from
// createInvestment
// pInv2 now points to the
// object; pInv1 is now null
// now pInv1 points to the
// object, and pInv2 is null
This odd copying behavior, plus the underlying requirement that
resources managed by auto_ptrs must never have more than one
auto_ptr pointing to them, means that auto_ptrs aren’t the best way to
manage all dynamically allocated resources. For example, STL containers require that their contents exhibit “normal” copying behavior,
so containers of auto_ptr aren’t allowed.
An alternative to auto_ptr is a reference-counting smart pointer (RCSP).
An RCSP is a smart pointer that keeps track of how many objects
point to a particular resource and automatically deletes the resource
when nobody is pointing to it any longer. As such, RCSPs offer behavior that is similar to that of garbage collection. Unlike garbage collection, however, RCSPs can’t break cycles of references (e.g., two
otherwise unused objects that point to one another).
TR1’s tr1::shared_ptr (see Item 54) is an RCSP, so you could write f this
way:
void f()
{
...
std::tr1::shared_ptr<Investment>
pInv(createInvestment());
...
}
// call factory function
// use pInv as before
// automatically delete
// pInv via shared_ptr’s dtor
This code looks almost the same as that employing auto_ptr, but copying shared_ptrs behaves much more naturally:
void f()
{
...
std::tr1::shared_ptr<Investment>
pInv1(createInvestment());
// pInv1 points to the
// object returned from
// createInvestment
Resource Management
std::tr1::shared_ptr<Investment>
pInv2(pInv1);
pInv1 = pInv2;
Item 13
65A
// both pInv1 and pInv2 now
// point to the object
// ditto — nothing has
// changed
...
}
// pInv1 and pInv2 are
// destroyed, and the
// object they point to is
// automatically deleted
Because copying tr1::shared_ptrs works “as expected,” they can be used
in STL containers and other contexts where auto_ptr’s unorthodox
copying behavior is inappropriate.
Don’t be misled, though. This Item isn’t about auto_ptr, tr1::shared_ptr,
or any other kind of smart pointer. It’s about the importance of using
objects to manage resources. auto_ptr and tr1::shared_ptr are just
examples of objects that do that. (For more information on
tr1:shared_ptr, consult Items 14, 18, and 54.)
Both auto_ptr and tr1::shared_ptr use delete in their destructors, not
delete []. (Item 16 describes the difference.) That means that using
auto_ptr or tr1::shared_ptr with dynamically allocated arrays is a bad
idea, though, regrettably, one that will compile:
std::auto_ptr<std::string>
aps(new std::string[10]);
std::tr1::shared_ptr<int> spi(new int[1024]);
// bad idea! the wrong
// delete form will be used
// same problem
You may be surprised to discover that there is nothing like auto_ptr or
tr1::shared_ptr for dynamically allocated arrays in C++, not even in
TR1. That’s because vector and string can almost always replace
dynamically allocated arrays. If you still think it would be nice to have
auto_ptr - and tr1::shared_ptr -like classes for arrays, look to Boost (see
Item 55). There you’ll be pleased to find the boost::scoped_array and
boost::shared_array classes that offer the behavior you’re looking for.
This Item’s guidance to use objects to manage resources suggests that
if you’re releasing resources manually (e.g., using delete other than in
a resource-managing class), you’re doing something wrong. Precanned resource-managing classes like auto_ptr and tr1::shared_ptr
often make following this Item’s advice easy, but sometimes you’re
using a resource where these pre-fab classes don’t do what you need.
When that’s the case, you’ll need to craft your own resource-managing
classes. That’s not terribly difficult to do, but it does involve some
subtleties you’ll need to consider. Those considerations are the topic
of Items 14 and 15.
66A
Item 14
Chapter 3
As a final comment, I have to point out that createInvestment’s raw
pointer return type is an invitation to a resource leak, because it’s so
easy for callers to forget to call delete on the pointer they get back.
(Even if they use an auto_ptr or tr1::shared_ptr to perform the delete,
they still have to remember to store createInvestment’s return value in
a smart pointer object.) Combatting that problem calls for an interface
modification to createInvestment, a topic I address in Item 18.
Things to Remember
✦
To prevent resource leaks, use RAII objects that acquire resources
in their constructors and release them in their destructors.
✦
Two commonly useful RAII classes are tr1::shared_ptr and auto_ptr.
tr1::shared_ptr is usually the better choice, because its behavior when
copied is intuitive. Copying an auto_ptr sets it to null.
Item 14: Think carefully about copying behavior in
resource-managing classes.
Item 13 introduces the idea of Resource Acquisition Is Initialization
(RAII) as the backbone of resource-managing classes, and it describes
how auto_ptr and tr1::shared_ptr are manifestations of this idea for
heap-based resources. Not all resources are heap-based, however,
and for such resources, smart pointers like auto_ptr and tr1::shared_ptr
are generally inappropriate as resource handlers. That being the case,
you’re likely to find yourself needing to create your own resourcemanaging classes from time to time.
For example, suppose you’re using a C API to manipulate mutex
objects of type Mutex offering functions lock and unlock:
void lock(Mutex *pm);
void unlock(Mutex *pm);
// lock mutex pointed to by pm
// unlock the mutex
To make sure that you never forget to unlock a Mutex you’ve locked,
you’d like to create a class to manage locks. The basic structure of
such a class is dictated by the RAII principle that resources are
acquired during construction and released during destruction:
class Lock {
public:
explicit Lock(Mutex *pm)
: mutexPtr(pm)
{ lock(mutexPtr); }
~Lock() { unlock(mutexPtr); }
private:
Mutex *mutexPtr;
};
// acquire resource
// release resource
Resource Management
Item 14
67A
Clients use Lock in the conventional RAII fashion:
Mutex m;
...
{
Lock ml(&m);
...
}
// define the mutex you need to use
// create block to define critical section
// lock the mutex
// perform critical section operations
// automatically unlock mutex at end
// of block
This is fine, but what should happen if a Lock object is copied?
Lock ml1(&m);
Lock ml2(ml1);
// lock m
// copy ml1 to ml2 — what should
// happen here?
This is a specific example of a more general question, one that every
RAII class author must confront: what should happen when an RAII
object is copied? Most of the time, you’ll want to choose one of the following possibilities:
■
Prohibit copying. In many cases, it makes no sense to allow RAII
objects to be copied. This is likely to be true for a class like Lock,
because it rarely makes sense to have “copies” of synchronization
primitives. When copying makes no sense for an RAII class, you
should prohibit it. Item 6 explains how to do that: declare the
copying operations private. For Lock, that could look like this:
class Lock: private Uncopyable {
public:
...
};
■
// prohibit copying — see
// Item 6
// as before
Reference-count the underlying resource. Sometimes it’s desirable to hold on to a resource until the last object using it has been
destroyed. When that’s the case, copying an RAII object should increment the count of the number of objects referring to the resource. This is the meaning of “copy” used by tr1::shared_ptr.
Often, RAII classes can implement reference-counting copying behavior by containing a tr1::shared_ptr data member. For example, if
Lock wanted to employ reference counting, it could change the type
of mutexPtr from Mutex* to tr1::shared_ptr<Mutex>. Unfortunately,
tr1::shared_ptr’s default behavior is to delete what it points to when
the reference count goes to zero, and that’s not what we want.
When we’re done with a Mutex, we want to unlock it, not delete it.
68A
Item 14
Chapter 3
Fortunately, tr1::shared_ptr allows specification of a “deleter” — a
function or function object to be called when the reference count
goes to zero. (This functionality does not exist for auto_ptr, which
always deletes its pointer.) The deleter is an optional second parameter to the tr1::shared_ptr constructor, so the code would look
like this:
class Lock {
public:
explicit Lock(Mutex *pm)
// init shared_ptr with the Mutex
: mutexPtr(pm, unlock)
// to point to and the unlock func
{
// as the deleter
lock(mutexPtr.get());
// see Item 15 for info on “get”
}
private:
std::tr1::shared_ptr<Mutex> mutexPtr;
// use shared_ptr
};
// instead of raw pointer
In this example, notice how the Lock class no longer declares a destructor. That’s because there’s no need to. Item 5 explains that a
class’s destructor (regardless of whether it is compiler-generated
or user-defined) automatically invokes the destructors of the
class’s non-static data members. In this example, that’s mutexPtr.
But mutexPtr’s destructor will automatically call the tr1::shared_ptr’s
deleter — unlock, in this case — when the mutex’s reference count
goes to zero. (People looking at the class’s source code would probably appreciate a comment indicating that you didn’t forget about
destruction, you’re just relying on the default compiler-generated
behavior.)
■
Copy the underlying resource. Sometimes you can have as many
copies of a resource as you like, and the only reason you need a
resource-managing class is to make sure that each copy is released when you’re done with it. In that case, copying the resource-managing object should also copy the resource it wraps.
That is, copying a resource-managing object performs a “deep
copy.”
Some implementations of the standard string type consist of pointers to heap memory, where the characters making up the string
are stored. Objects of such strings contain a pointer to the heap
memory. When a string object is copied, a copy is made of both the
pointer and the memory it points to. Such strings exhibit deep
copying.
■
Transfer ownership of the underlying resource. On rare occasion, you may wish to make sure that only one RAII object refers
Resource Management
Item 15
69A
to a raw resource and that when the RAII object is copied, ownership of the resource is transferred from the copied object to the
copying object. As explained in Item 13, this is the meaning of
“copy” used by auto_ptr.
The copying functions (copy constructor and copy assignment operator) may be generated by compilers, so unless the compiler-generated
versions will do what you want (Item 5 explains the default behavior),
you’ll need to write them yourself. In some cases, you’ll also want to
support generalized versions of these functions. Such versions are
described in Item 45.
Things to Remember
✦
Copying an RAII object entails copying the resource it manages, so
the copying behavior of the resource determines the copying behavior of the RAII object.
✦
Common RAII class copying behaviors are disallowing copying and
performing reference counting, but other behaviors are possible.
Item 15: Provide access to raw resources in resourcemanaging classes.
Resource-managing classes are wonderful. They’re your bulwark
against resource leaks, the absence of such leaks being a fundamental characteristic of well-designed systems. In a perfect world, you’d
rely on such classes for all your interactions with resources, never
sullying your hands with direct access to raw resources. But the world
is not perfect. Many APIs refer to resources directly, so unless you
plan to foreswear use of such APIs (something that’s rarely practical),
you’ll have to bypass resource-managing objects and deal with raw
resources from time to time.
For example, Item 13 introduces the idea of using smart pointers like
auto_ptr or tr1::shared_ptr to hold the result of a call to a factory function like createInvestment:
std::tr1::shared_ptr<Investment> pInv(createInvestment());
// from Item 13
Suppose that a function you’d like to use when working with Investment objects is this:
int daysHeld(const Investment *pi);
// return number of days
// investment has been held
70A
Item 15
Chapter 3
You’d like to call it like this,
int days = daysHeld(pInv);
// error!
but the code won’t compile: daysHeld wants a raw Investment* pointer,
but you’re passing an object of type tr1::shared_ptr<Investment>.
You need a way to convert an object of the RAII class (in this case,
tr1::shared_ptr) into the raw resource it contains (e.g., the underlying
Investment*). There are two general ways to do it: explicit conversion
and implicit conversion.
tr1::shared_ptr and auto_ptr both offer a get member function to perform an explicit conversion, i.e., to return (a copy of) the raw pointer
inside the smart pointer object:
int days = daysHeld(pInv.get());
// fine, passes the raw pointer
// in pInv to daysHeld
Like virtually all smart pointer classes, tr1::shared_ptr and auto_ptr also
overload the pointer dereferencing operators (operator-> and operator*),
and this allows implicit conversion to the underlying raw pointers:
class Investment {
public:
bool isTaxFree() const;
...
};
Investment* createInvestment();
std::tr1::shared_ptr<Investment>
pi1(createInvestment());
bool taxable1 = !(pi1->isTaxFree());
// root class for a hierarchy
// of investment types
// factory function
// have tr1::shared_ptr
// manage a resource
// access resource
// via operator->
...
std::auto_ptr<Investment> pi2(createInvestment()); // have auto_ptr
// manage a
// resource
bool taxable2 = !((*pi2).isTaxFree());
// access resource
// via operator*
...
Because it is sometimes necessary to get at the raw resource inside an
RAII object, some RAII class designers grease the skids by offering an
implicit conversion function. For example, consider this RAII class for
fonts that are native to a C API:
FontHandle getFont();
void releaseFont(FontHandle fh);
// from C API — params omitted
// for simplicity
// from the same C API
Resource Management
class Font {
public:
explicit Font(FontHandle fh)
: f(fh)
{}
~Font() { releaseFont(f ); }
...
private:
FontHandle f;
};
Item 15
71A
// RAII class
// acquire resource;
// use pass-by-value, because the
// C API does
// release resource
// handle copying (see Item 14)
// the raw font resource
Assuming there’s a large font-related C API that deals entirely with
FontHandles, there will be a frequent need to convert from Font objects
to FontHandles. The Font class could offer an explicit conversion function such as get:
class Font {
public:
...
FontHandle get() const { return f; }
...
};
// explicit conversion function
Unfortunately, this would require that clients call get every time they
want to communicate with the API:
void changeFontSize(FontHandle f, int newSize);
Font f(getFont());
int newFontSize;
...
changeFontSize(f.get(), newFontSize);
// from the C API
// explicitly convert
// Font to FontHandle
Some programmers might find the need to explicitly request such conversions off-putting enough to avoid using the class. That, in turn,
would increase the chances of leaking fonts, the very thing the Font
class is designed to prevent.
The alternative is to have Font offer an implicit conversion function to
its FontHandle:
class Font {
public:
...
operator FontHandle() const
{ return f; }
...
};
// implicit conversion function
That makes calling into the C API easy and natural:
72A
Item 15
Font f(getFont());
int newFontSize;
...
changeFontSize(f, newFontSize);
Chapter 3
// implicitly convert Font
// to FontHandle
The downside is that implicit conversions increase the chance of
errors. For example, a client might accidently create a FontHandle
when a Font was intended:
Font f1(getFont());
...
FontHandle f2 = f1;
// oops! meant to copy a Font
// object, but instead implicitly
// converted f1 into its underlying
// FontHandle, then copied that
Now the program has a FontHandle being managed by the Font object
f1, but the FontHandle is also available for direct use as f2. That’s
almost never good. For example, when f1 is destroyed, the font will be
released, and f2 will dangle.
The decision about whether to offer explicit conversion from an RAII
class to its underlying resource (e.g., via a get member function) or
whether to allow implicit conversion is one that depends on the specific task the RAII class is designed to perform and the circumstances
in which it is intended to be used. The best design is likely to be the
one that adheres to Item 18’s advice to make interfaces easy to use
correctly and hard to use incorrectly. Often, an explicit conversion
function like get is the preferable path, because it minimizes the
chances of unintended type conversions. Sometime, however, the naturalness of use arising from implicit type conversions will tip the
scales in that direction.
It may have occurred to you that functions returning the raw resource
inside an RAII class are contrary to encapsulation. That’s true, but it’s
not the design disaster it may at first appear. RAII classes don’t exist
to encapsulate something; they exist to ensure that a particular action
— resource release — takes place. If desired, encapsulation of the
resource can be layered on top of this primary functionality, but it’s
not necessary. Furthermore, some RAII classes combine true encapsulation of implementation with very loose encapsulation of the
underlying resource. For example, tr1::shared_ptr encapsulates all its
reference-counting machinery, but it still offers easy access to the raw
pointer it contains. Like most well-designed classes, it hides what cli-
Resource Management
Item 16
73A
ents don’t need to see, but it makes available those things that clients
honestly need to access.
Things to Remember
✦
APIs often require access to raw resources, so each RAII class
should offer a way to get at the resource it manages.
✦
Access may be via explicit conversion or implicit conversion. In general, explicit conversion is safer, but implicit conversion is more convenient for clients.
Item 16: Use the same form in corresponding uses of
new and delete.
What’s wrong with this picture?
std::string *stringArray = new std::string[100];
...
delete stringArray;
Everything appears to be in order. The new is matched with a delete.
Still, something is quite wrong. The program’s behavior is undefined.
At the very least, 99 of the 100 string objects pointed to by stringArray
are unlikely to be properly destroyed, because their destructors will
probably never be called.
When you employ a new expression (i.e., dynamic creation of an object
via a use of new), two things happen. First, memory is allocated (via a
function named operator new — see Items 49 and 51). Second, one or
more constructors are called for that memory. When you employ a
delete expression (i.e., use delete), two other things happen: one or
more destructors are called for the memory, then the memory is deallocated (via a function named operator delete — see Item 51). The big
question for delete is this: how many objects reside in the memory
being deleted? The answer to that determines how many destructors
must be called.
Actually, the question is simpler: does the pointer being deleted point
to a single object or to an array of objects? It’s a critical question,
because the memory layout for single objects is generally different
from the memory layout for arrays. In particular, the memory for an
array usually includes the size of the array, thus making it easy for
delete to know how many destructors to call. The memory for a single
74A
Item 16
Chapter 3
object lacks this information. You can think of the different layouts as
looking like this, where n is the size of the array:
Single
Object
Object
Array
n
Object
Object
Object
...
This is just an example, of course. Compilers aren’t required to implement things this way, though many do.
When you use delete on a pointer, the only way for delete to know
whether the array size information is there is for you to tell it. If you
use brackets in your use of delete, delete assumes an array is pointed
to. Otherwise, it assumes that a single object is pointed to:
std::string *stringPtr1 = new std::string;
std::string *stringPtr2 = new std::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, but it’s unlikely to be pretty. Assuming the layout above,
delete would read some memory and interpret what it read as an array
size, then start invoking that many destructors, oblivious to the fact
that the memory it’s working on not only isn’t in the array, it’s also
probably not holding objects of the type it’s busy destructing.
What would happen if you didn’t use the “[]” form on stringPtr2? Well,
that’s undefined too, but you can see how it would lead to too few
destructors being called. Furthermore, it’s undefined (and sometimes
harmful) for built-in types like ints, too, even though such types lack
destructors.
The rule is simple: if you use [] in a new expression, you must use [] in
the corresponding delete expression. If you don’t use [] in a new
expression, don’t use [] in the matching delete expression.
This is a particularly important rule to bear in mind when you are
writing a class containing a pointer to dynamically allocated memory
and also offering multiple constructors, because then you must be
careful to use the same form of new in all the constructors to initialize
the pointer member. If you don’t, how will you know what form of
delete to use in your destructor?
Resource Management
Item 17
75A
This rule is also noteworthy for the typedef-inclined, because it means
that a typedef’s author must document which form of delete should be
employed when new is used to conjure up objects of the typedef type.
For example, consider this typedef:
typedef std::string AddressLines[4];
// a person’s address has 4 lines,
// each of which is a string
Because AddressLines is an array, this use of new,
std::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;
delete [] pal;
// undefined!
// fine
To avoid such confusion, abstain from typedefs for array types. That’s
easy, because the standard C++ library (see Item 54) includes string
and vector, and those templates reduce the need for dynamically allocated arrays to nearly zero. Here, for example, AddressLines could be
defined to be a vector of strings, i.e., the type vector<string>.
Things to Remember
✦
If you use [] in a new expression, you must use [] in the corresponding delete expression. If you don’t use [] in a new expression, you
mustn’t use [] in the corresponding delete expression.
Item 17: Store newed objects in smart pointers in
standalone statements.
Suppose we have a function to reveal our processing priority and a
second function to do some processing on a dynamically allocated
Widget in accord with a priority:
int priority();
void processWidget(std::tr1::shared_ptr<Widget> pw, int priority);
Mindful of the wisdom of using objects to manage resources (see
Item 13), processWidget uses a smart pointer (here, a tr1::shared_ptr) for
the dynamically allocated Widget it processes.
Consider now a call to processWidget:
processWidget(new Widget, priority());
76A
Item 17
Chapter 3
Wait, don’t consider that call. It won’t compile. tr1::shared_ptr’s constructor taking a raw pointer is explicit, so there’s no implicit conversion from the raw pointer returned by the expression “new Widget” to
the tr1::shared_ptr required by processWidget. The following code, however, will compile:
processWidget(std::tr1::shared_ptr<Widget>(new Widget), priority());
Surprisingly, although we’re using object-managing resources everywhere here, this call may leak resources. It’s illuminating to see how.
Before compilers can generate a call to processWidget, they have to
evaluate the arguments being passed as its parameters. The second
argument is just a call to the function priority, but the first argument,
(“std::tr1::shared_ptr<Widget>(new Widget)”) consists of two parts:
■
Execution of the expression “new Widget”.
■
A call to the tr1::shared_ptr constructor.
Before processWidget can be called, then, compilers must generate
code to do these three things:
■
Call priority.
■
Execute “new Widget”.
■
Call the tr1::shared_ptr constructor.
C++ compilers are granted considerable latitude in determining the
order in which these things are to be done. (This is different from the
way languages like Java and C# work, where function parameters are
always evaluated in a particular order.) The “new Widget” expression
must be executed before the tr1::shared_ptr constructor can be called,
because the result of the expression is passed as an argument to the
tr1::shared_ptr constructor, but the call to priority can be performed
first, second, or third. If compilers choose to perform it second (something that may allow them to generate more efficient code), we end up
with this sequence of operations:
1. Execute “new Widget”.
2. Call priority.
3. Call the tr1::shared_ptr constructor.
But consider what will happen if the call to priority yields an exception.
In that case, the pointer returned from “new Widget” will be lost,
because it won’t have been stored in the tr1::shared_ptr we were expecting would guard against resource leaks. A leak in the call to processWidget can arise because an exception can intervene between the time
Resource Management
Item 17
77A
a resource is created (via “new Widget”) and the time that resource is
turned over to a resource-managing object.
The way to avoid problems like this is simple: use a separate statement to create the Widget and store it in a smart pointer, then pass
the smart pointer to processWidget:
std::tr1::shared_ptr<Widget> pw(new Widget);
processWidget(pw, priority());
// store newed object
// in a smart pointer in a
// standalone statement
// this call won’t leak
This works because compilers are given less leeway in reordering
operations across statements than within them. In this revised code,
the “new Widget” expression and the call to the tr1::shared_ptr constructor are in a different statement from the one calling priority, so compilers are not allowed to move the call to priority between them.
Things to Remember
✦
Store newed objects in smart pointers in standalone statements.
Failure to do this can lead to subtle resource leaks when exceptions
are thrown.
Designs and
Declarations
Chapter 4: Designs and Declarations
Software designs — approaches to getting the software to do what you
want it to do — typically begin as fairly general ideas, but they eventually become detailed enough to allow for the development of specific
interfaces. These interfaces must then be translated into C++ declarations. In this chapter, we attack the problem of designing and declaring good C++ interfaces. We begin with perhaps the most important
guideline about designing interfaces of any kind: that they should be
easy to use correctly and hard to use incorrectly. That sets the stage
for a number of more specific guidelines addressing a wide range of
topics, including correctness, efficiency, encapsulation, maintainability, extensibility, and conformance to convention.
Designs and Declarations
The material that follows isn’t everything you need to know about
good interface design, but it highlights some of the most important
considerations, warns about some of the most frequent errors, and
provides solutions to problems often encountered by class, function,
and template designers.
Item 18: Make interfaces easy to use correctly and
hard to use incorrectly.
C++ is awash in interfaces. Function interfaces. Class interfaces.
Template interfaces. Each interface is a means by which clients interact with your code. Assuming you’re dealing with reasonable people,
those clients are trying to do a good job. They want to use your interfaces correctly. That being the case, if they use one incorrectly, your
interface is at least partially to blame. Ideally, if an attempted use of
an interface won’t do what the client expects, the code won’t compile;
and if the code does compile, it will do what the client wants.
Developing interfaces that are easy to use correctly and hard to use
incorrectly requires that you consider the kinds of mistakes that cli-
Designs and Declarations
Item 18
79A
ents might make. For example, suppose you’re designing the constructor for a class representing dates in time:
class Date {
public:
Date(int month, int day, int year);
...
};
At first glance, this interface may seem reasonable (at least in the
USA), but there are at least two errors that clients might easily make.
First, they might pass parameters in the wrong order:
Date d(30, 3, 1995);
// Oops! Should be “3, 30” , not “30, 3”
Second, they might pass an invalid month or day number:
Date d(3, 40, 1995);
// Oops! Should be “3, 30” , not “3, 40”
(This last example may look silly, but remember that on a keyboard,
4 is next to 3. Such “off by one” typing errors are not uncommon.)
Many client errors can be prevented by the introduction of new types.
Indeed, the type system is your primary ally in preventing undesirable
code from compiling. In this case, we can introduce simple wrapper
types to distinguish days, months, and years, then use these types in
the Date constructor:
struct Day {
struct Month {
struct Year {
explicit Day(int d)
explicit Month(int m)
explicit Year(int y)
: val(d) {}
: val(m) {}
: val(y){}
int val;
int val;
int val;
};
};
};
class Date {
public:
Date(const Month& m, const Day& d, const Year& y);
...
};
Date d(30, 3, 1995);
// error! wrong types
Date d(Day(30), Month(3), Year(1995));
// error! wrong types
Date d(Month(3), Day(30), Year(1995));
// okay, types are correct
Making Day, Month, and Year full-fledged classes with encapsulated
data would be better than the simple use of structs above (see
Item 22), but even structs suffice to demonstrate that the judicious
introduction of new types can work wonders for the prevention of
interface usage errors.
80A
Item 18
Chapter 4
Once the right types are in place, it can sometimes be reasonable to
restrict the values of those types. For example, there are only 12 valid
month values, so the Month type should reflect that. One way to do this
would be to use an enum to represent the month, but enums are not
as type-safe as we might like. For example, enums can be used like ints
(see Item 2). A safer solution is to predefine the set of all valid Months:
class Month {
public:
static Month Jan() { return Month(1); }
static Month Feb() { return Month(2); }
...
static Month Dec() { return Month(12); }
...
private:
explicit Month(int m);
...
};
Date d(Month::Mar(), Day(30), Year(1995));
// functions returning all valid
// Month values; see below for
// why these are functions, not
// objects
// other member functions
// prevent creation of new
// Month values
// month-specific data
If the idea of using functions instead of objects to represent specific
months strikes you as odd, it may be because you have forgotten that
reliable initialization of non-local static objects can be problematic.
Item 4 can refresh your memory.
Another way to prevent likely client errors is to restrict what can be
done with a type. A common way to impose restrictions is to add const.
For example, Item 3 explains how const-qualifying the return type
from operator* can prevent clients from making this error for userdefined types:
if (a * b = c) ...
// oops, meant to do a comparison!
In fact, this is just a manifestation of another general guideline for
making types easy to use correctly and hard to use incorrectly: unless
there’s a good reason not to, have your types behave consistently with
the built-in types. Clients already know how types like int behave, so
you should strive to have your types behave the same way whenever
reasonable. For example, assignment to a * b isn’t legal if a and b are
ints, so unless there’s a good reason to diverge from this behavior, it
should be illegal for your types, too. When in doubt, do as the ints do.
The real reason for avoiding gratuitous incompatibilities with the
built-in types is to offer interfaces that behave consistently. Few characteristics lead to interfaces that are easy to use correctly as much as
consistency, and few characteristics lead to aggravating interfaces as
Designs and Declarations
Item 18
81A
much as inconsistency. The interfaces to STL containers are largely
(though not perfectly) consistent, and this helps make them fairly
easy to use. For example, every STL container has a member function
named size that tells how many objects are in the container. Contrast
this with Java, where you use the length property for arrays, the length
method for Strings, and the size method for Lists; and with .NET, where
Arrays have a property named Length, while ArrayLists have a property
named Count. Some developers think that integrated development
environments (IDEs) render such inconsistencies unimportant, but
they are mistaken. Inconsistency imposes mental friction into a developer’s work that no IDE can fully remove.
Any interface that requires that clients remember to do something is
prone to incorrect use, because clients can forget to do it. For example, Item 13 introduces a factory function that returns pointers to
dynamically allocated objects in an Investment hierarchy:
Investment* createInvestment();
// from Item 13; parameters omitted
// for simplicity
To avoid resource leaks, the pointers returned from createInvestment
must eventually be deleted, but that creates an opportunity for at
least two types of client errors: failure to delete a pointer, and deletion
of the same pointer more than once.
Item 13 shows how clients can store createInvestment’s return value in
a smart pointer like auto_ptr or tr1::shared_ptr, thus turning over to the
smart pointer the responsibility for using delete. But what if clients
forget to use the smart pointer? In many cases, a better interface decision would be to preempt the problem by having the factory function
return a smart pointer in the first place:
std::tr1::shared_ptr<Investment> createInvestment();
This essentially forces clients to store the return value in a
tr1::shared_ptr, all but eliminating the possibility of forgetting to delete
the underlying Investment object when it’s no longer being used.
In fact, returning a tr1::shared_ptr makes it possible for an interface
designer to prevent a host of other client errors regarding resource
release, because, as Item 14 explains, tr1::shared_ptr allows a resourcerelease function — a “deleter” — to be bound to the smart pointer
when the smart pointer is created. (auto_ptr has no such capability.)
Suppose clients who get an Investment* pointer from createInvestment
are expected to pass that pointer to a function called getRidOfInvestment instead of using delete on it. Such an interface would open the
door to a new kind of client error, one where clients use the wrong
82A
Item 18
Chapter 4
resource-destruction mechanism (i.e., delete instead of getRidOfInvestment). The implementer of createInvestment can forestall such problems by returning a tr1::shared_ptr with getRidOfInvestment bound to it
as its deleter.
tr1::shared_ptr offers a constructor taking two arguments: the pointer
to be managed and the deleter to be called when the reference count
goes to zero. This suggests that the way to create a null tr1::shared_ptr
with getRidOfInvestment as its deleter is this:
std::tr1::shared_ptr<Investment>
pInv(0, getRidOfInvestment);
// attempt to create a null
// shared_ptr with a custom deleter;
// this won’t compile
Alas, this isn’t valid C++. The tr1::shared_ptr constructor insists on its
first parameter being a pointer, and 0 isn’t a pointer, it’s an int. Yes, it’s
convertible to a pointer, but that’s not good enough in this case;
tr1::shared_ptr insists on an actual pointer. A cast solves the problem:
std::tr1::shared_ptr<Investment>
// create a null shared_ptr with
pInv( static_cast<Investment*>(0), // getRidOfInvestment as its
getRidOfInvestment);
// deleter; see Item 27 for info on
// static_cast
This means that the code for implementing createInvestment to return
a tr1::shared_ptr with getRidOfInvestment as its deleter would look something like this:
std::tr1::shared_ptr<Investment> createInvestment()
{
std::tr1::shared_ptr<Investment> retVal(static_cast<Investment*>(0),
getRidOfInvestment);
retVal = ... ;
// make retVal point to the
// correct object
return retVal;
}
Of course, if the raw pointer to be managed by retVal could be determined prior to creating retVal, it would be better to pass the raw
pointer to retVal’s constructor instead of initializing retVal to null and
then making an assignment to it. For details on why, consult Item 26.
An especially nice feature of tr1::shared_ptr is that it automatically uses
its per-pointer deleter to eliminate another potential client error, the
“cross-DLL problem.” This problem crops up when an object is created using new in one dynamically linked library (DLL) but is deleted
in a different DLL. On many platforms, such cross-DLL new/delete
pairs lead to runtime errors. tr1::shared_ptr avoids the problem,
because its default deleter uses delete from the same DLL where the
Designs and Declarations
Item 18
83A
tr1::shared_ptr is created. This means, for example, that if Stock is a
class derived from Investment and createInvestment is implemented like
this,
std::tr1::shared_ptr<Investment> createInvestment()
{
return std::tr1::shared_ptr<Investment>(new Stock);
}
the returned tr1::shared_ptr can be passed among DLLs without concern for the cross-DLL problem. The tr1::shared_ptrs pointing to the
Stock keep track of which DLL’s delete should be used when the reference count for the Stock becomes zero.
This Item isn’t about tr1::shared_ptr — it’s about making interfaces easy
to use correctly and hard to use incorrectly — but tr1::shared_ptr is
such an easy way to eliminate some client errors, it’s worth an overview of the cost of using it. The most common implementation of
tr1::shared_ptr comes from Boost (see Item 55). Boost’s shared_ptr is
twice the size of a raw pointer, uses dynamically allocated memory for
bookkeeping and deleter-specific data, uses a virtual function call
when invoking its deleter, and incurs thread synchronization overhead
when modifying the reference count in an application it believes is
multithreaded. (You can disable multithreading support by defining a
preprocessor symbol.) In short, it’s bigger than a raw pointer, slower
than a raw pointer, and uses auxiliary dynamic memory. In many
applications, these additional runtime costs will be unnoticeable, but
the reduction in client errors will be apparent to everyone.
Things to Remember
✦
Good interfaces are easy to use correctly and hard to use incorrectly.
You should strive for these characteristics in all your interfaces.
✦
Ways to facilitate correct use include consistency in interfaces and
behavioral compatibility with built-in types.
✦
Ways to prevent errors include creating new types, restricting operations on types, constraining object values, and eliminating client resource management responsibilities.
✦
tr1::shared_ptr supports custom deleters. This prevents the crossDLL problem, can be used to automatically unlock mutexes (see
Item 14), etc.
84A
Item 19
Chapter 4
Item 19: Treat class design as type design.
In C++, as in other object-oriented programming languages, defining a
new class defines a new type. Much of your time as a C++ developer
will thus be spent augmenting your type system. This means you’re
not just a class designer, you’re a type designer. Overloading functions
and operators, controlling memory allocation and deallocation, defining object initialization and finalization — it’s all in your hands. You
should therefore approach class design with the same care that language designers lavish on the design of the language’s built-in types.
Designing good classes is challenging because designing good types is
challenging. Good types have a natural syntax, intuitive semantics,
and one or more efficient implementations. In C++, a poorly planned
class definition can make it impossible to achieve any of these goals.
Even the performance characteristics of a class’s member functions
may be affected by how they are declared.
How, then, do you design effective classes? First, you must understand the issues you face. Virtually every class requires that you confront the following questions, the answers to which often lead to
constraints on your design:
■
How should objects of your new type be created and destroyed? How this is done influences the design of your class’s
constructors and destructor, as well as its memory allocation and
deallocation functions (operator new, operator new[], operator delete,
and operator delete[] — see Chapter 8), if you write them.
■
How should object initialization differ from object assignment? The answer to this question determines the behavior of
and the differences between your constructors and your assignment operators. It’s important not to confuse initialization with assignment, because they correspond to different function calls (see
Item 4).
■
What does it mean for objects of your new type to be passed
by value? Remember, the copy constructor defines how pass-byvalue is implemented for a type.
■
What are the restrictions on legal values for your new type?
Usually, only some combinations of values for a class’s data members are valid. Those combinations determine the invariants your
class will have to maintain. The invariants determine the error
checking you’ll have to do inside your member functions, especially your constructors, assignment operators, and “setter” functions. It may also affect the exceptions your functions throw and,
Designs and Declarations
Item 19
85A
on the off chance you use them, your functions’ exception specifications.
■
Does your new type fit into an inheritance graph? If you inherit
from existing classes, you are constrained by the design of those
classes, particularly by whether their functions are virtual or nonvirtual (see Items 34 and 36). If you wish to allow other classes to
inherit from your class, that affects whether the functions you declare are virtual, especially your destructor (see Item 7).
■
What kind of type conversions are allowed for your new type?
Your type exists in a sea of other types, so should there be conversions between your type and other types? If you wish to allow objects of type T1 to be implicitly converted into objects of type T2,
you will want to write either a type conversion function in class T1
(e.g., operator T2) or a non-explicit constructor in class T2 that can
be called with a single argument. If you wish to allow explicit conversions only, you’ll want to write functions to perform the conversions, but you’ll need to avoid making them type conversion
operators or non-explicit constructors that can be called with one
argument. (For an example of both implicit and explicit conversion
functions, see Item 15.)
■
What operators and functions make sense for the new type?
The answer to this question determines which functions you’ll declare for your class. Some functions will be member functions, but
some will not (see Items 23, 24, and 46).
■
What standard functions should be disallowed? Those are the
ones you’ll need to declare private (see Item 6).
■
Who should have access to the members of your new type?
This question helps you determine which members are public,
which are protected, and which are private. It also helps you determine which classes and/or functions should be friends, as well
as whether it makes sense to nest one class inside another.
■
What is the “undeclared interface” of your new type? What
kind of guarantees does it offer with respect to performance, exception safety (see Item 29), and resource usage (e.g., locks and
dynamic memory)? The guarantees you offer in these areas will
impose constraints on your class implementation.
■
How general is your new type? Perhaps you’re not really defining
a new type. Perhaps you’re defining a whole family of types. If so,
you don’t want to define a new class, you want to define a new
class template.
86A
■
Item 20
Chapter 4
Is a new type really what you need? If you’re defining a new derived class only so you can add functionality to an existing class,
perhaps you’d better achieve your goals by simply defining one or
more non-member functions or templates.
These questions are difficult to answer, so defining effective classes
can be challenging. Done well, however, user-defined classes in C++
yield types that are at least as good as the built-in types, and that
makes all the effort worthwhile.
Things to Remember
✦
Class design is type design. Before defining a new type, be sure to
consider all the issues discussed in this Item.
Item 20: Prefer pass-by-reference-to-const to pass-byvalue.
By default, C++ passes objects to and from functions by value (a characteristic it inherits from C). 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.
These copies are produced by the objects’ copy constructors. This can
make pass-by-value an expensive operation. For example, consider
the following class hierarchy:
class Person {
public:
Person();
virtual ~Person();
...
private:
std::string name;
std::string address;
};
class Student: public Person {
public:
Student();
virtual ~Student();
...
private:
std::string schoolName;
std::string schoolAddress;
};
// parameters omitted for simplicity
// see Item 7 for why this is virtual
// parameters again omitted
Designs and Declarations
Item 20
87A
Now consider the following code, in which we call a function, validateStudent, that takes a Student argument (by value) and returns
whether it has been validated:
bool validateStudent(Student s);
Student plato;
bool platoIsOK = validateStudent(plato);
// function taking a Student
// by value
// Plato studied under Socrates
// call the function
What happens when this function is called?
Clearly, the Student copy constructor is called to initialize the parameter s from plato. Equally clearly, s is destroyed when validateStudent
returns. So the parameter-passing cost of this function is one call to
the Student copy constructor and one call to the Student destructor.
But that’s not the whole story. A Student object has two string objects
within it, so every time you construct a Student object you must also
construct two string objects. A Student object also inherits from a Person object, so every time you construct a Student object you must also
construct a Person object. A Person object has two additional string
objects inside it, so each Person construction also entails two more
string constructions. The end result is that passing a Student object by
value leads to one call to the Student copy constructor, one call to the
Person copy constructor, and four calls to the string copy constructor.
When the copy of the Student object is destroyed, each constructor call
is matched by a destructor call, so the overall cost of passing a Student
by value is six constructors and six destructors!
Now, this is correct and desirable behavior. After all, you want all your
objects to be reliably initialized and destroyed. Still, it would be nice if
there were a way to bypass all those constructions and destructions.
There is: pass by reference-to-const:
bool validateStudent(const Student& s);
This is much more efficient: no constructors or destructors are called,
because no new objects are being created. The const in the revised
parameter declaration is important. The original version of validateStudent took a Student parameter by value, so callers knew that they were
shielded from any changes the function might make to the Student they
passed in; validateStudent would be able to modify only a copy of it. Now
that the Student is being passed by reference, it’s necessary to also
declare it const, because otherwise callers would have to worry about
validateStudent making changes to the Student they passed in.
Passing parameters by reference also avoids the slicing problem. When
a derived class object is passed (by value) as a base class object, the
88A
Item 20
Chapter 4
base class copy constructor is called, and the specialized features that
make the object behave like a derived class object are “sliced” off.
You’re left with a simple base class object — little surprise, since a
base class constructor created it. This is almost never what you want.
For example, suppose you’re working on a set of classes for implementing a graphical window system:
class Window {
public:
...
std::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 be displayed, which you can bring
about by invoking the display function. The fact that display is virtual
tells you that the way in which simple base class Window objects are
displayed is apt to differ from the way in which the fancier WindowWithScrollBars objects are displayed (see Items 34 and 36).
Now suppose you’d like to write a function to print out a window’s
name and then display the window. Here’s the wrong way to write
such a function:
void printNameAndDisplay(Window w)
{
std::cout << w.name();
w.display();
}
// incorrect! parameter
// may be sliced!
Consider what happens when you call this function with a Window-
WithScrollBars object:
WindowWithScrollBars wwsb;
printNameAndDisplay(wwsb);
The parameter w will be constructed — it’s passed by value, remember? — as a Window object, and all the specialized information that
made wwsb act like a WindowWithScrollBars object will be sliced off.
Inside printNameAndDisplay, w will always act like an object of class
Window (because it is an object of class Window), regardless of the type
of object passed to the function. In particular, the call to display inside
Designs and Declarations
Item 20
89A
printNameAndDisplay will always call Window::display, never WindowWithScrollBars::display.
The way around the slicing problem is to pass w by reference-to-const:
void printNameAndDisplay(const Window& w)
{
std::cout << w.name();
w.display();
}
// fine, parameter won’t
// be sliced
Now w will act like whatever kind of window is actually passed in.
If you peek under the hood of a C++ compiler, you’ll find that references are typically implemented as pointers, so passing something by
reference usually means really passing a pointer. As a result, if you
have an object of a built-in type (e.g., an int), it’s often more efficient to
pass it by value than by reference. For built-in types, then, when you
have a choice between pass-by-value and pass-by-reference-to-const,
it’s not unreasonable to choose pass-by-value. This same advice
applies to iterators and function objects in the STL, because, by convention, they are designed to be passed by value. Implementers of iterators and function objects are responsible for seeing to it that they are
efficient to copy and are not subject to the slicing problem. (This is an
example of how the rules change, depending on the part of C++ you
are using — see Item 1.)
Built-in types are small, so some people conclude that all small types
are good candidates for pass-by-value, even if they’re user-defined.
This is shaky reasoning. Just because an object is small doesn’t mean
that calling its copy constructor is inexpensive. Many objects — most
STL containers among them — contain little more than a pointer, but
copying such objects entails copying everything they point to. That
can be very expensive.
Even when small objects have inexpensive copy constructors, there
can be performance issues. Some compilers treat built-in and userdefined types differently, even if they have the same underlying representation. For example, some compilers refuse to put objects consisting of only a double into a register, even though they happily place
naked doubles there on a regular basis. When that kind of thing happens, you can be better off passing such objects by reference, because
compilers will certainly put pointers (the implementation of references) into registers.
Another reason why small user-defined types are not necessarily good
pass-by-value candidates is that, being user-defined, their size is subject to change. A type that’s small now may be bigger in a future
90A
Item 21
Chapter 4
release, because its internal implementation may change. Things can
even change when you switch to a different C++ implementation. As I
write this, for example, some implementations of the standard
library’s string type are seven times as big as others.
In general, the only types for which you can reasonably assume that
pass-by-value is inexpensive are built-in types and STL iterator and
function object types. For everything else, follow the advice of this
Item and prefer pass-by-reference-to-const over pass-by-value.
Things to Remember
✦
Prefer pass-by-reference-to-const over pass-by-value. It’s typically
more efficient and it avoids the slicing problem.
✦
The rule doesn’t apply to built-in types and STL iterator and function object types. For them, pass-by-value is usually appropriate.
Item 21: Don’t try to return a reference when you
must return an object.
Once programmers grasp the efficiency implications of pass-by-value
for objects (see Item 20), many become crusaders, determined to root
out the evil of pass-by-value wherever it may hide. Unrelenting in
their pursuit of pass-by-reference purity, they invariably make a fatal
mistake: they start to pass references to objects that don’t exist. This
is not a good thing.
Consider a class for representing rational numbers, including a function for multiplying two rationals together:
class Rational {
public:
Rational(int numerator = 0,
int denominator = 1);
...
private:
int n, d;
friend
const Rational
operator*(const Rational& lhs,
const Rational& rhs);
};
// see Item 24 for why this
// ctor isn’t declared explicit
// numerator and denominator
// see Item 3 for why the
// return type is const
This version of operator* is returning its result object by value, and
you’d be shirking your professional duties if you failed to worry about
the cost of that object’s construction and destruction. You don’t want
Designs and Declarations
Item 21
91A
to pay for such an object 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, a name for some existing
object. Whenever you see the declaration for a reference, you should
immediately ask yourself what it is another name for, because it must
be another name for something. In the case of operator*, if the function
is to return a reference, it must return a reference to some Rational
object that already 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 you have
Rational a(1, 2);
Rational b(3, 5);
Rational c = a * b;
// a = 1/2
// b = 3/5
// c should be 3/10
it seems unreasonable to expect that there already happens to exist a
rational number with the value three-tenths. No, if operator* 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 is accomplished by defining a local
variable. Using that strategy, you might try to write operator* this way:
const Rational& operator*(const Rational& lhs,
const Rational& rhs)
{
Rational result(lhs.n * rhs.n, lhs.d * rhs.d);
return result;
}
// warning! bad code!
You can reject this approach out of hand, because your goal was to
avoid a constructor call, and result will have to be constructed just like
any other object. A more serious problem is that this function returns
a reference to result, but result is a local object, and local objects are
destroyed when the function exits. This version of operator*, then,
doesn’t return a reference to a Rational — it returns a reference to an
ex-Rational; a former Rational; the empty, stinking, rotting carcass of
what used to be a Rational but is no longer, because it has been
destroyed. Any caller so much as glancing at this function’s return
value would instantly enter the realm of undefined behavior. The fact
is, any function returning a reference to a local object is broken. (The
same is true for any function returning a pointer to a local object.)
92A
Item 21
Chapter 4
Let us consider, then, the possibility of constructing an object on the
heap and returning a reference to it. Heap-based objects come into
being through the use of new, so you might write a heap-based operator* like this:
const Rational& operator*(const Rational& lhs, // warning! more bad
const Rational& rhs) // code!
{
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 calling an appropriate constructor,
but now you have a different problem: who will apply delete to the
object conjured up by your use of new?
Even if callers are conscientious and well intentioned, there’s not
much they can do to prevent leaks in reasonable usage scenarios like
this:
Rational w, x, y, z;
w = x * y * z;
// same as operator*(operator*(x, y), z)
Here, there are two calls to operator* in the same statement, hence two
uses of new that need to be undone with uses of delete. Yet there is no
reasonable way for clients of operator* to make those calls, because
there’s no reasonable way for them to get at the pointers hidden
behind the references being returned from the calls to operator*. This
is a guaranteed resource leak.
But perhaps you notice that both the on-the-stack and on-the-heap
approaches suffer from having to call a constructor for each result
returned from operator*. Perhaps you recall that our initial goal was to
avoid such constructor invocations. Perhaps you think you know a
way to avoid all but one constructor call. Perhaps the following implementation occurs to you, an implementation based on operator*
returning a reference to a static Rational object, one defined inside the
function:
const Rational& operator*(const Rational& lhs, // warning! yet more
const Rational& rhs) // bad code!
{
static Rational result;
// static object to which a
// reference will be returned
result = ... ;
// multiply lhs by rhs and put the
// product inside result
return result;
}
Designs and Declarations
Item 21
93A
Like all designs employing the use of static objects, this one immediately raises our thread-safety hackles, but that’s its more obvious
weakness. To see its deeper flaw, consider this perfectly reasonable
client code:
bool operator==(const Rational& lhs,
const Rational& rhs);
Rational a, b, c, d;
...
if ((a * b) == (c * d)) {
// an operator==
// for Rationals
do whatever’s appropriate when the products are equal;
} else {
do whatever’s appropriate when they’re not;
}
Guess what? The expression ((a*b) == (c*d)) will always evaluate to
true, regardless of the values of a, b, c, and d!
This revelation is easiest to understand when the code is rewritten 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 will return a reference to the static
Rational object inside operator*. Thus, operator== will be asked to compare the value 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.
This should be enough to convince you that returning a reference
from a function like operator* is a waste of time, but some of you are
now thinking, “Well, if one static isn’t enough, maybe a static array
will do the trick....”
I can’t bring myself to dignify this design with example code, but I can
sketch why the notion should cause you to blush in shame. First, you
must choose n, the size of the array. If n is too small, you may run out
of places to store function return values, in which case you’ll have
gained nothing over the single-static design we just discredited. But if
n is too big, you’ll decrease the performance of your program, because
every object in the array will be constructed the first time the function
is called. That will cost you n constructors and n destructors†, even if
the function in question is called only once. If “optimization” is the
process of improving software performance, this kind of thing should
be called “pessimization.” Finally, think about how you’d put the val† The destructors will be called once at program shutdown.
94A
Item 22
Chapter 4
ues you need into the array’s objects and what it would cost you to do
it. The most direct way to move a value between objects is via assignment, but what is the cost of an assignment? For many types, it’s
about the same as a call to a destructor (to destroy the old value) plus
a call to a constructor (to copy over the new value). But your goal is to
avoid the costs of construction and destruction! Face it: this approach
just isn’t going to pan out. (No, using a vector instead of an array won’t
improve matters much.)
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 or something essentially equivalent:
inline const Rational operator*(const Rational& lhs, const Rational& rhs)
{
return Rational(lhs.n * rhs.n, lhs.d * rhs.d);
}
Sure, you may incur the cost of constructing and destructing operator*’s return value, but in the long run, that’s a small price to pay for
correct behavior. Besides, the bill that so terrifies you may never
arrive. Like all programming languages, C++ allows compiler implementers to apply optimizations to improve the performance of the generated code without changing its observable behavior, and it turns out
that in some cases, construction and destruction of operator*’s return
value can be safely eliminated. When compilers take advantage of that
fact (and compilers often do), your program continues to behave the
way it’s supposed to, just faster than you expected.
It all boils down to this: when deciding between returning a reference
and returning an object, your job is to make the choice that offers correct behavior. Let your compiler vendors wrestle with figuring out how
to make that choice as inexpensive as possible.
Things to Remember
✦
Never return a pointer or reference to a local stack object, a reference to a heap-allocated object, or a pointer or reference to a local
static object if there is a chance that more than one such object will
be needed. (Item 4 provides an example of a design where returning
a reference to a local static is reasonable, at least in single-threaded
environments.)
Item 22: Declare data members private.
Okay, here’s the plan. First, we’re going to see why data members
shouldn’t be public. Then we’ll see that all the arguments against
Designs and Declarations
Item 22
95A
public data members apply equally to protected ones. That will lead to
the conclusion that data members should be private, and at that
point, we’ll be done.
So, public data members. Why not?
Let’s begin with syntactic consistency (see also Item 18). If data members aren’t public, the only way for clients to access an object is via
member functions. If everything in the public interface is a function,
clients won’t have to scratch their heads trying to remember whether
to use parentheses when they want to access a member of the class.
They’ll just do it, because everything is a function. Over the course of
a lifetime, that can save a lot of head scratching.
But maybe you don’t find the consistency argument compelling. How
about the fact that using functions gives you much more precise control over the accessibility of data members? If you make a data member public, everybody has read-write access to it, but if you use
functions to get or set its value, you can implement no access, readonly access, and read-write access. Heck, you can even implement
write-only access if you want to:
class AccessLevels {
public:
...
int getReadOnly() const
void setReadWrite(int value)
int getReadWrite() const
void setWriteOnly(int value)
private:
int noAccess;
int readOnly;
int readWrite;
int writeOnly;
};
{ return readOnly; }
{ readWrite = value; }
{ return readWrite; }
{ writeOnly = value; }
// no access to this int
// read-only access to this int
// read-write access to this int
// write-only access to this int
Such fine-grained access control is important, because many data
members should be hidden. Rarely does every data member need a
getter and setter.
Still not convinced? Then it’s time to bring out the big gun: encapsulation. If you implement access to a data member through a function,
you can later replace the data member with a computation, and
nobody using your class will be any the wiser.
96A
Item 22
Chapter 4
For example, suppose you are writing an application in which automated equipment is monitoring the speed of passing cars. As each car
passes, its speed is computed and the value added to a collection of all
the speed data collected so far:
class SpeedDataCollection {
...
public:
void addValue(int speed);
double averageSoFar() const;
...
};
// add a new data value
// return average speed
Now consider the implementation of the member function averageSoFar. One way to implement 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 have averageSoFar compute its value
anew each time it’s called, something it could do by examining each
data value in the collection.
The first approach (keeping a running average) makes each SpeedDataCollection object bigger, because you have to allocate space for the data
members holding the running average, the accumulated total, and the
number of data points. However, averageSoFar can be implemented
very efficiently; it’s just an inline function (see Item 30) that returns
the value of the running average. Conversely, computing the average
whenever it’s requested will make averageSoFar run slower, but each
SpeedDataCollection object will be smaller.
Who’s to say which is best? On a machine where memory is tight (e.g.,
an embedded roadside device), and in an application where averages
are needed only infrequently, computing the average each time is
probably a better solution. In an application where averages are
needed frequently, speed is of the essence, and memory is not an
issue, keeping a running average will typically be preferable. The
important point is that by accessing the average through a member
function (i.e., by encapsulating it), you can interchange these different
implementations (as well as any others you might think of), and clients will, at most, only have to recompile. (You can eliminate even that
inconvenience by following the techniques described in Item 31.)
Hiding data members behind functional interfaces can offer all kinds
of implementation flexibility. For example, it makes it easy to notify
other objects when data members are read or written, to verify class
invariants and function pre- and postconditions, to perform synchro-
Designs and Declarations
Item 22
97A
nization in threaded environments, etc. Programmers coming to C++
from languages like Delphi and C# will recognize such capabilities as
the equivalent of “properties” in these other languages, albeit with the
need to type an extra set of parentheses.
The point about encapsulation is more important than it might initially appear. If you hide your data members from your clients (i.e.,
encapsulate them), you can ensure that class invariants are always
maintained, because only member functions can affect them. Furthermore, you reserve the right to change your implementation decisions
later. If you don’t hide such decisions, you’ll soon find that even if you
own the source code to a class, your ability to change anything public
is extremely restricted, because too much client code will be broken.
Public means unencapsulated, and practically speaking, unencapsulated means unchangeable, especially for classes that are widely used.
Yet widely used classes are most in need of encapsulation, because
they are the ones that can most benefit from the ability to replace one
implementation with a better one.
The argument against protected data members is similar. In fact, it’s
identical, though it may not seem that way at first. The reasoning
about syntactic consistency and fine-grained access control is clearly
as applicable to protected data as to public, but what about encapsulation? Aren’t protected data members more encapsulated than public
ones? Practically speaking, the surprising answer is that they are not.
Item 23 explains that something’s encapsulation is inversely proportional to the amount of code that might be broken if that something
changes. The encapsulatedness of a data member, then, is inversely
proportional to the amount of code that might be broken if that data
member changes, e.g., if it’s removed from the class (possibly in favor
of a computation, as in averageSoFar, above).
Suppose we have a public data member, and we eliminate it. How
much code might be broken? All the client code that uses it, which is
generally an unknowably large amount. Public data members are thus
completely unencapsulated. But suppose we have a protected data
member, and we eliminate it. How much code might be broken now?
All the derived classes that use it, which is, again, typically an
unknowably large amount of code. Protected data members are thus
as unencapsulated as public ones, because in both cases, if the data
members are changed, an unknowably large amount of client code is
broken. This is unintuitive, but as experienced library implementers
will tell you, it’s still true. Once you’ve declared a data member public
or protected and clients have started using it, it’s very hard to change
anything about that data member. Too much code has to be rewritten,
98A
Item 23
Chapter 4
retested, redocumented, or recompiled. From an encapsulation point
of view, there are really only two access levels: private (which offers
encapsulation) and everything else (which doesn’t).
Things to Remember
✦
Declare data members private. It gives clients syntactically uniform
access to data, affords fine-grained access control, allows invariants
to be enforced, and offers class authors implementation flexibility.
✦
protected is no more encapsulated than public.
Item 23: Prefer non-member non-friend functions to
member functions.
Imagine a class for representing web browsers. Among the many functions such a class might offer are those to clear the cache of downloaded elements, clear the history of visited URLs, and remove all
cookies from the system:
class WebBrowser {
public:
...
void clearCache();
void clearHistory();
void removeCookies();
...
};
Many users will want to perform all these actions together, so Web-
Browser might also offer a function to do just that:
class WebBrowser {
public:
...
void clearEverything();
// calls clearCache, clearHistory,
// and removeCookies
...
};
Of course, this functionality could also be provided by a non-member
function that calls the appropriate member functions:
void clearBrowser(WebBrowser& wb)
{
wb.clearCache();
wb.clearHistory();
wb.removeCookies();
}
Designs and Declarations
Item 23
99A
So which is better, the member function clearEverything or the nonmember function clearBrowser?
Object-oriented principles dictate that data and the functions that
operate on them should be bundled together, and that suggests that
the member function is the better choice. Unfortunately, this suggestion is incorrect. It’s based on a misunderstanding of what being
object-oriented means. Object-oriented principles dictate that data
should be as encapsulated as possible. Counterintuitively, the member function clearEverything actually yields less encapsulation than the
non-member clearBrowser. Furthermore, offering the non-member
function allows for greater packaging flexibility for WebBrowser -related
functionality, and that, in turn, yields fewer compilation dependencies
and an increase in WebBrowser extensibility. The non-member
approach is thus better than a member function in many ways. It’s
important to understand why.
We’ll begin with encapsulation. If something is encapsulated, it’s hidden from view. The more something is encapsulated, the fewer things
can see it. The fewer things can see it, the greater flexibility we have to
change it, because our changes directly affect only those things that
can see what we change. The greater something is encapsulated, then,
the greater our ability to change it. That’s the reason we value encapsulation in the first place: it affords us the flexibility to change things
in a way that affects only a limited number of clients.
Consider the data associated with an object. The less code that can
see the data (i.e., access it), the more the data is encapsulated, and
the more freely we can change characteristics of an object’s data, such
as the number of data members, their types, etc. As a coarse-grained
measure of how much code can see a piece of data, we can count the
number of functions that can access that data: the more functions
that can access it, the less encapsulated the data.
Item 22 explains that data members should be private, because if
they’re not, an unlimited number of functions can access them. They
have no encapsulation at all. For data members that are private, the
number of functions that can access them is the number of member
functions of the class plus the number of friend functions, because
only members and friends have access to private members. Given a
choice between a member function (which can access not only the private data of a class, but also private functions, enums, typedefs, etc.)
and a non-member non-friend function (which can access none of
these things) providing the same functionality, the choice yielding
greater encapsulation is the non-member non-friend function,
because it doesn’t increase the number of functions that can access
100A Item 23
Chapter 4
the private parts of the class. This explains why clearBrowser (the nonmember non-friend function) is preferable to clearEverything (the member function): it yields greater encapsulation in the WebBrowser class.
At this point, two things are worth noting. First, this reasoning applies
only to non-member non-friend functions. Friends have the same
access to a class’s private members that member functions have,
hence the same impact on encapsulation. From an encapsulation
point of view, the choice isn’t between member and non-member functions, it’s between member functions and non-member non-friend
functions. (Encapsulation isn’t the only point of view, of course.
Item 24 explains that when it comes to implicit type conversions, the
choice is between member and non-member functions.)
The second thing to note is that just because concerns about encapsulation dictate that a function be a non-member of one class doesn’t
mean it can’t be a member of another class. This may prove a mild
salve to programmers accustomed to languages where all functions
must be in classes (e.g., Eiffel, Java, C#, etc.). For example, we could
make clearBrowser a static member function of some utility class. As
long as it’s not part of (or a friend of) WebBrowser, it doesn’t affect the
encapsulation of WebBrowser’s private members.
In C++, a more natural approach would be to make clearBrowser a nonmember function in the same namespace as WebBrowser:
namespace WebBrowserStuff {
class WebBrowser { ... };
void clearBrowser(WebBrowser& wb);
...
}
This has more going for it than naturalness, however, because
namespaces, unlike classes, can be spread across multiple source
files. That’s important, because functions like clearBrowser are convenience functions. Being neither members nor friends, they have no
special access to WebBrowser, so they can’t offer any functionality a
WebBrowser client couldn’t already get in some other way. For example, if clearBrowser didn’t exist, clients could just call clearCache,
clearHistory, and removeCookies themselves.
A class like WebBrowser might have a large number of convenience
functions, some related to bookmarks, others related to printing, still
others related to cookie management, etc. As a general rule, most clients will be interested in only some of these sets of convenience functions. There’s no reason for a client interested only in bookmark-
Designs and Declarations
Item 23 101A
related convenience functions to be compilation dependent on, e.g.,
cookie-related convenience functions. The straightforward way to separate them is to declare bookmark-related convenience functions in
one header file, cookie-related convenience functions in a different
header file, printing-related convenience functions in a third, etc.:
// header “webbrowser.h” — header for class WebBrowser itself
// as well as “core” WebBrowser-related functionality
namespace WebBrowserStuff {
class WebBrowser { ... };
...
// “core” related functionality, e.g.
// non-member functions almost
// all clients need
}
// header “webbrowserbookmarks.h”
namespace WebBrowserStuff {
...
// bookmark-related convenience
}
// functions
// header “webbrowsercookies.h”
namespace WebBrowserStuff {
...
// cookie-related convenience
}
// functions
...
Note that this is exactly how the standard C++ library is organized.
Rather than having a single monolithic <C++StandardLibrary> header
containing everything in the std namespace, there are dozens of headers (e.g., <vector>, <algorithm>, <memory>, etc.), each declaring some
of the functionality in std. Clients who use only vector -related functionality aren’t required to #include <memory>; clients who don’t use
list don’t have to #include <list>. This allows clients to be compilation
dependent only on the parts of the system they actually use. (See
Item 31 for a discussion of other ways to reduce compilation dependencies.) Partitioning functionality in this way is not possible when it
comes from a class’s member functions, because a class must be
defined in its entirety; it can’t be split into pieces.
Putting all convenience functions in multiple header files — but one
namespace — also means that clients can easily extend the set of convenience functions. All they have to do is add more non-member nonfriend functions to the namespace. For example, if a WebBrowser client
decides to write convenience functions related to downloading images,
he or she just needs to create a new header file containing the declarations of those functions in the WebBrowserStuff namespace. The new
functions are now as available and as integrated as all other conve-
102A Item 24
Chapter 4
nience functions. This is another feature classes can’t offer, because
class definitions are closed to extension by clients. Sure, clients can
derive new classes, but derived classes have no access to encapsulated (i.e., private) members in the base class, so such “extended functionality” has second-class status. Besides, as Item 7 explains, not all
classes are designed to be base classes.
Things to Remember
✦
Prefer non-member non-friend functions to member functions. Doing so increases encapsulation, packaging flexibility, and functional
extensibility.
Item 24: Declare non-member functions when type
conversions should apply to all parameters.
I noted in the Introduction to this book that having classes support
implicit type conversions is generally a bad idea. Of course, there are
exceptions to this rule, and one of the most common is when creating
numerical types. For example, if you’re designing a class to represent
rational numbers, allowing implicit conversions from integers to rationals doesn’t seem unreasonable. It’s certainly no less reasonable than
C++’s built-in conversion from int to double (and it’s a lot more reasonable than C++’s built-in conversion from double to int). That being the
case, you might start your Rational class this way:
class Rational {
public:
Rational(int numerator = 0,
int denominator = 1);
int numerator() const;
int denominator() const;
private:
...
};
// ctor is deliberately not explicit;
// allows implicit int-to-Rational
// conversions
// accessors for numerator and
// denominator — see Item 22
You know you’d like to support arithmetic operations like addition,
multiplication, etc., but you’re unsure whether you should implement
them via member functions, non-member functions, or, possibly, nonmember functions that are friends. Your instincts tell you that when
you’re in doubt, you should be object-oriented. You know that, say,
multiplication of rational numbers is related to the Rational class, so it
seems natural to implement operator* for rational numbers inside the
Rational class. Counterintuitively, Item 23 argues that the idea of putting functions inside the class they are associated with is sometimes
Designs and Declarations
Item 24 103A
contrary to object-oriented principles, but let’s set that aside and
investigate the idea of making operator* a member function of Rational:
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 a reference-to-const as its argument — consult Items 3, 20, and 21.)
This design lets you multiply rationals with the greatest of ease:
Rational oneEighth(1, 8);
Rational oneHalf(1, 2);
Rational result = oneHalf * oneEighth;
result = result * oneEighth;
// fine
// fine
But you’re not satisfied. You’d also like to support mixed-mode operations, where Rationals can be multiplied with, for example, ints. After
all, few things are as natural as multiplying two numbers together,
even if they happen to be different types of numbers.
When you try to do mixed-mode arithmetic, however, you find that it
works only half the time:
result = oneHalf * 2;
result = 2 * oneHalf;
// fine
// 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 equivalent functional form:
result = oneHalf.operator*(2);
result = 2.operator*(oneHalf );
// fine
// error!
The object oneHalf is an instance of a class that contains an operator*,
so compilers call that function. However, the integer 2 has no associated class, hence no operator* member function. Compilers will also
look for non-member operator*s (i.e., ones at namespace or global
scope) that can be called like this:
result = operator*(2, oneHalf );
// error!
But in this example, there is no non-member operator* taking an int
and a Rational, so the search fails.
104A Item 24
Chapter 4
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 the other?
What’s going on is implicit type conversion. Compilers know you’re
passing an int and that the function requires a Rational, but they also
know they can conjure up a suitable Rational by calling the Rational
constructor with the int you provided, so that’s what they do. That is,
they treat the call as if it had been written more or less like this:
const Rational temp(2);
result = oneHalf * temp;
// create a temporary
// Rational object from 2
// same as oneHalf.operator*(temp);
Of course, compilers do this only because a non-explicit constructor is
involved. If Rational’s constructor were explicit, neither of these statements would compile:
result = oneHalf * 2;
result = 2 * oneHalf;
// error! (with explicit ctor);
// can’t convert 2 to Rational
// same error, same problem
That would fail to support mixed-mode arithmetic, but at least the
behavior of the two statements would be consistent.
Your goal, however, is both consistency and support for mixed-mode
arithmetic, i.e., a design where both of the above statements will compile. That brings us back to these two statements and why, even when
Rational’s constructor is not explicit, one compiles and one does not:
result = oneHalf * 2;
result = 2 * oneHalf;
// fine (with non-explicit ctor)
// error! (even with non-explicit ctor)
It turns out that parameters are eligible for implicit type conversion
only if they are listed in the parameter list. The implicit parameter corresponding to the object on which the member function is invoked —
the one this points to — is never eligible for implicit conversions.
That’s why the first call compiles and the second one does not. The
first case involves a parameter listed in the parameter list, but the
second one doesn’t.
You’d still like to support mixed-mode arithmetic, however, 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 all arguments:
Designs and Declarations
Item 24 105A
class Rational {
...
// contains no operator*
};
const Rational operator*(const Rational& lhs,
// now a non-member
const Rational& rhs)
// function
{
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 of the Rational class?
In this case, the answer is no, because operator* can be implemented
entirely in terms of Rational’s public interface. The code above shows
one way to do it. That leads to an important observation: the opposite
of a member function is a non-member function, not a friend function.
Too many C++ programmers assume that if a function is related to a
class and should not be a member (due, for example, to a need for
type conversions on all arguments), it should be a friend. This example demonstrates that such reasoning is flawed. Whenever you can
avoid friend functions, you should, because, much as in real life,
friends are often more trouble than they’re worth. Sometimes friendship is warranted, of course, but the fact remains that just because a
function shouldn’t be a member doesn’t automatically mean it should
be a friend.
This Item contains the truth and nothing but the truth, but it’s not
the whole truth. When you cross the line from Object-Oriented C++
into Template C++ (see Item 1) and make Rational a class template
instead of a class, there are new issues to consider, new ways to
resolve them, and some surprising design implications. Such issues,
resolutions, and implications are the topic of Item 46.
Things to Remember
✦
If you need type conversions on all parameters to a function (including the one that would otherwise be pointed to by the this pointer),
the function must be a non-member.
106A Item 25
Chapter 4
Item 25: Consider support for a non-throwing swap.
swap is an interesting function. Originally introduced as part of the
STL, it’s since become a mainstay of exception-safe programming (see
Item 29) and a common mechanism for coping with the possibility of
assignment to self (see Item 11). Because swap is so useful, it’s important to implement it properly, but along with its singular importance
comes a set of singular complications. In this Item, we explore what
they are and how to deal with them.
To swap the values of two objects is to give each the other’s value. By
default, swapping is accomplished via the standard swap algorithm.
Its typical implementation is exactly what you’d expect:
namespace std {
template<typename T>
void swap(T& a, T& b)
{
T temp(a);
a = b;
b = temp;
}
}
// typical implementation of std::swap;
// swaps a’s and b’s values
As long as your types support copying (via copy constructor and copy
assignment operator), the default swap implementation will let objects
of your types be swapped without your having to do any special work
to support it.
However, the default swap implementation may not thrill you. It
involves copying three objects: a to temp, b to a, and temp to b. For
some types, none of these copies are really necessary. For such types,
the default swap puts you on the fast track to the slow lane.
Foremost among such types are those consisting primarily of a
pointer to another type that contains the real data. A common manifestation of this design approach is the “pimpl idiom” (“pointer to
implementation” — see Item 31). A Widget class employing such a
design might look like this:
class WidgetImpl {
public:
...
private:
int a, b, c;
std::vector<double> v;
...
};
// class for Widget data;
// details are unimportant
// possibly lots of data —
// expensive to copy!
Designs and Declarations
Item 25 107A
class Widget {
public:
Widget(const Widget& rhs);
Widget& operator=(const Widget& rhs)
{
...
*pImpl = *(rhs.pImpl);
...
}
...
private:
WidgetImpl *pImpl;
};
// class using the pimpl idiom
// to copy a Widget, copy its
// WidgetImpl object. For
// details on implementing
// operator= in general,
// see Items 10, 11, and 12.
// ptr to object with this
// Widget’s data
To swap the value of two Widget objects, all we really need to do is
swap their pImpl pointers, but the default swap algorithm has no way
to know that. Instead, it would copy not only three Widgets, but also
three WidgetImpl objects. Very inefficient. Not a thrill.
What we’d like to do is tell std::swap that when Widgets are being
swapped, the way to perform the swap is to swap their internal pImpl
pointers. There is a way to say exactly that: specialize std::swap for
Widget. Here’s the basic idea, though it won’t compile in this form:
namespace std {
template<>
void swap<Widget>(Widget& a,
Widget& b)
{
swap(a.pImpl, b.pImpl);
}
// this is a specialized version
// of std::swap for when T is
// Widget
// to swap Widgets, swap their
// pImpl pointers; this won’t
compile
}
The “template<>” at the beginning of this function says that this is a
total template specialization for std::swap, and the “<Widget>” after the
name of the function says that the specialization is for when T is Widget. In other words, when the general swap template is applied to Widgets, this is the implementation that should be used. In general, we’re
not permitted to alter the contents of the std namespace, but we are
allowed to totally specialize standard templates (like swap) for types of
our own creation (such as Widget). That’s what we’re doing here.
As I said, though, this function won’t compile. That’s because it’s trying to access the pImpl pointers inside a and b, and they’re private. We
could declare our specialization a friend, but the convention is different: it’s to have Widget declare a public member function called swap
108A Item 25
Chapter 4
that does the actual swapping, then specialize std::swap to call the
member function:
class Widget {
public:
...
void swap(Widget& other)
{
using std::swap;
swap(pImpl, other.pImpl);
}
...
};
namespace std {
template<>
void swap<Widget>(Widget& a,
Widget& b)
{
a.swap(b);
}
}
// same as above, except for the
// addition of the swap mem func
// the need for this declaration
// is explained later in this Item
// to swap Widgets, swap their
// pImpl pointers
// revised specialization of
// std::swap
// to swap Widgets, call their
// swap member function
Not only does this compile, it’s also consistent with the STL containers, all of which provide both public swap member functions and versions of std::swap that call these member functions.
Suppose, however, that Widget and WidgetImpl were class templates
instead of classes, possibly so we could parameterize the type of the
data stored in WidgetImpl:
template<typename T>
class WidgetImpl { ... };
template<typename T>
class Widget { ... };
Putting a swap member function in Widget (and, if we need to, in WidgetImpl) is as easy as before, but we run into trouble with the specialization for std::swap. This is what we want to write:
namespace std {
template<typename T>
void swap<Widget<T> >(Widget<T>& a,
Widget<T>& b)
{ a.swap(b); }
}
// error! illegal code!
Designs and Declarations
Item 25 109A
This looks perfectly reasonable, but it’s not legal. We’re trying to partially specialize a function template (std::swap), but though C++ allows
partial specialization of class templates, it doesn’t allow it for function
templates. This code should not compile (though some compilers erroneously accept it).
When you want to “partially specialize” a function template, the usual
approach is to simply add an overload. That would look like this:
namespace std {
template<typename T>
void swap(Widget<T>& a,
Widget<T>& b)
{ a.swap(b); }
}
// an overloading of std::swap
// (note the lack of “<...>” after
// “swap”), but see below for
// why this isn’t valid code
In general, overloading function templates is fine, but std is a special
namespace, and the rules governing it are special, too. It’s okay to
totally specialize templates in std, but it’s not okay to add new templates (or classes or functions or anything else) to std. The contents of
std are determined solely by the C++ standardization committee, and
we’re prohibited from augmenting what they’ve decided should go
there. Alas, the form of the prohibition may dismay you. Programs
that cross this line will almost certainly compile and run, but their
behavior is undefined. If you want your software to have predictable
behavior, you’ll not add new things to std.
So what to do? We still need a way to let other people call swap and get
our more efficient template-specific version. The answer is simple. We
still declare a non-member swap that calls the member swap, we just
don’t declare the non-member to be a specialization or overloading of
std::swap. For example, if all our Widget-related functionality is in the
namespace WidgetStuff, it would look like this:
namespace WidgetStuff {
...
template<typename T>
class Widget { ... };
...
template<typename T>
void swap(Widget<T>& a,
Widget<T>& b)
{
a.swap(b);
}
}
// templatized WidgetImpl, etc.
// as before, including the swap
// member function
// non-member swap function;
// not part of the std namespace
110A Item 25
Chapter 4
Now, if any code anywhere calls swap on two Widget objects, the name
lookup rules in C++ (specifically the rules known as argument-dependent lookup or Koenig lookup) will find the Widget-specific version in
WidgetStuff. Which is exactly what we want.
This approach works as well for classes as for class templates, so it
seems like we should use it all the time. Unfortunately, there is a reason for specializing std::swap for classes (I’ll describe it shortly), so if
you want to have your class-specific version of swap called in as many
contexts as possible (and you do), you need to write both a non-member version in the same namespace as your class and a specialization
of std::swap.
By the way, if you’re not using namespaces, everything above continues to apply (i.e., you still need a non-member swap that calls the
member swap), but why are you clogging the global namespace with
all your class, template, function, enum, enumerant, and typedef
names? Have you no sense of propriety?
Everything I’ve written so far pertains to authors of swap, but it’s
worth looking at one situation from a client’s point of view. Suppose
you’re writing a function template where you need to swap the values
of two objects:
template<typename T>
void doSomething(T& obj1, T& obj2)
{
...
swap(obj1, obj2);
...
}
Which swap should this call? The general one in std, which you know
exists; a specialization of the general one in std, which may or may not
exist; or a T -specific one, which may or may not exist and which may
or may not be in a namespace (but should certainly not be in std)?
What you desire is to call a T -specific version if there is one, but to fall
back on the general version in std if there’s not. Here’s how you fulfill
your desire:
template<typename T>
void doSomething(T& obj1, T& obj2)
{
using std::swap;
// make std::swap available in this function
...
swap(obj1, obj2);
// call the best swap for objects of type T
...
}
Designs and Declarations
Item 25 111A
When compilers see the call to swap, they search for the right swap to
invoke. C++’s name lookup rules ensure that this will find any T -specific swap at global scope or in the same namespace as the type T. (For
example, if T is Widget in the namespace WidgetStuff, compilers will
use argument-dependent lookup to find swap in WidgetStuff.) If no T specific swap exists, compilers will use swap in std, thanks to the using
declaration that makes std::swap visible in this function. Even then,
however, compilers will prefer a T -specific specialization of std::swap
over the general template, so if std::swap has been specialized for T, the
specialized version will be used.
Getting the right swap called is therefore easy. The one thing you want
to be careful of is to not qualify the call, because that will affect how
C++ determines the function to invoke. For example, if you were to
write the call to swap this way,
std::swap(obj1, obj2);
// the wrong way to call swap
you’d force compilers to consider only the swap in std (including any
template specializations), thus eliminating the possibility of getting a
more appropriate T -specific version defined elsewhere. Alas, some
misguided programmers do qualify calls to swap in this way, and
that’s why it’s important to totally specialize std::swap for your classes:
it makes type-specific swap implementations available to code written
in this misguided fashion. (Such code is present in some standard
library implementations, so it’s in your interest to help such code
work as efficiently as possible.)
At this point, we’ve discussed the default swap, member swaps, nonmember swaps, specializations of std::swap, and calls to swap, so let’s
summarize the situation.
First, if the default implementation of swap offers acceptable efficiency
for your class or class template, you don’t need to do anything. Anybody trying to swap objects of your type will get the default version,
and that will work fine.
Second, if the default implementation of swap isn’t efficient enough
(which almost always means that your class or template is using some
variation of the pimpl idiom), do the following:
1. Offer a public swap member function that efficiently swaps the
value of two objects of your type. For reasons I’ll explain in a moment, this function should never throw an exception.
2. Offer a non-member swap in the same namespace as your class
or template. Have it call your swap member function.
112A Item 25
Chapter 4
3. If you’re writing a class (not a class template), specialize std::swap
for your class. Have it also call your swap member function.
Finally, if you’re calling swap, be sure to include a using declaration to
make std::swap visible in your function, then call swap without any
namespace qualification.
The only loose end is my admonition to have the member version of
swap never throw exceptions. That’s because one of the most useful
applications of swap is to help classes (and class templates) offer the
strong exception-safety guarantee. Item 29 provides all the details,
but the technique is predicated on the assumption that the member
version of swap never throws. This constraint applies only to the member version! It can’t apply to the non-member version, because the
default version of swap is based on copy construction and copy assignment, and, in general, both of those functions are allowed to throw
exceptions. When you write a custom version of swap, then, you are
typically offering more than just an efficient way to swap values;
you’re also offering one that doesn’t throw exceptions. As a general
rule, these two swap characteristics go hand in hand, because highly
efficient swaps are almost always based on operations on built-in
types (such as the pointers underlying the pimpl idiom), and operations on built-in types never throw exceptions.
Things to Remember
✦
Provide a swap member function when std::swap would be inefficient
for your type. Make sure your swap doesn’t throw exceptions.
✦
If you offer a member swap, also offer a non-member swap that calls
the member. For classes (not templates), specialize std::swap, too.
✦
When calling swap, employ a using declaration for std::swap, then call
swap without namespace qualification.
✦
It’s fine to totally specialize std templates for user-defined types, but
never try to add something completely new to std.
Implementations
Chapter 5: Implementations
For the most part, coming up with appropriate definitions for your
classes (and class templates) and appropriate declarations for your
functions (and function templates) is the lion’s share of the battle.
Once you’ve got those right, the corresponding implementations are
largely straightforward. Still, there are things to watch out for. Defining variables too soon can cause a drag on performance. Overuse of
casts can lead to code that’s slow, hard to maintain, and infected with
subtle bugs. Returning handles to an object’s internals can defeat
encapsulation and leave clients with dangling handles. Failure to consider the impact of exceptions can lead to leaked resources and corrupted data structures. Overzealous inlining can cause code bloat.
Excessive coupling can result in unacceptably long build times.
Implementations
All of these problems can be avoided. This chapter explains how.
Item 26: Postpone variable definitions as long as
possible.
Whenever you define a variable of a type with a constructor or
destructor, you incur the cost of construction when control reaches
the variable’s definition, and you incur the cost of destruction when
the variable goes out of scope. There’s a cost associated with unused
variables, so you want to avoid them whenever you can.
You’re probably thinking that you never define unused variables, but
you may need to think again. Consider the following function, which
returns an encrypted version of a password, provided the password 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 54):
114A Item 26
Chapter 5
// this function defines the variable "encrypted" too soon
std::string encryptPassword(const std::string& password)
{
using namespace std;
string encrypted;
if (password.length() < MinimumPasswordLength) {
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. 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
std::string encryptPassword(const std::string& password)
{
using namespace std;
if (password.length() < MinimumPasswordLength) {
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 it some value, often via an assignment. Item 4 explains
why default-constructing an object and then assigning to it is 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(std::string& s);
// encrypts s in place
Then encryptPassword could be implemented like this, though it
wouldn’t be the best way to do it:
Implementations
Item 26 115A
// this function postpones encrypted’s definition until
// it’s necessary, but it’s still needlessly inefficient
std::string encryptPassword(const std::string& password)
{
...
// import std and 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 and potentially expensive default construction:
// finally, the best way to define and initialize encrypted
std::string encryptPassword(const std::string& password)
{
...
// import std and 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 a variable’s definition until right
before you have to use the variable, you should also try to postpone
the definition until you have initialization arguments for it. By doing
so, you avoid constructing and destructing unneeded objects, and you
avoid unnecessary default constructions. Further, you help document
the purpose of variables by initializing them in contexts in which their
meaning is clear.
“But what about loops?” you may wonder. If a variable is used only
inside a loop, is it better to define it outside the loop and make an
assignment to it on each loop iteration, or is it be better to define the
variable inside the loop? That is, which of these general structures is
better?
// Approach A: define outside loop
Widget w;
for (int i = 0; i < n; ++i) {
w = some value dependent on i;
i);
...
}
// Approach B: define inside loop
for (int i = 0; i < n; ++i) {
Widget w(some value dependent on
...
}
116A Item 27
Chapter 5
Here I’ve switched from an object of type string to an object of type Widget to avoid any preconceptions about the cost of performing a construction, destruction, or assignment for the object.
In terms of Widget operations, the costs of these two approaches are
as follows:
■
Approach A: 1 constructor + 1 destructor + n assignments.
■
Approach B: n constructors + n destructors.
For classes where an assignment costs less than a constructordestructor pair, Approach A is generally more efficient. This is especially the case as n gets large. Otherwise, Approach B is probably better. Furthermore, Approach A makes the name w visible in a larger
scope (the one containing the loop) than Approach B, something that’s
contrary to program comprehensibility and maintainability. As a
result, unless you know that (1) assignment is less expensive than a
constructor-destructor pair and (2) you’re dealing with a performance-sensitive part of your code, you should default to using
Approach B.
Things to Remember
✦
Postpone variable definitions as long as possible. It increases program clarity and improves program efficiency.
Item 27: Minimize casting.
The rules of C++ are designed to guarantee that type errors are impossible. In theory, if your program compiles cleanly, it’s not trying to
perform any unsafe or nonsensical operations on any objects. This is
a valuable guarantee. You don’t want to forgo it lightly.
Unfortunately, casts subvert the type system. That can lead to all
kinds of trouble, some easy to recognize, some extraordinarily subtle.
If you’re coming to C++ from C, Java, or C#, take note, because casting in those languages is more necessary and less dangerous than in
C++. But C++ is not C. It’s not Java. It’s not C#. In this language, casting is a feature you want to approach with great respect.
Let’s begin with a review of casting syntax, because there are usually
three different ways to write the same cast. C-style casts look like this:
(T) expression
// cast expression to be of type T
Function-style casts use this syntax:
T(expression)
// cast expression to be of type T
Implementations
Item 27 117A
There is no difference in meaning between these forms; it’s purely a
matter of where you put the parentheses. I call these two forms oldstyle casts.
C++ also offers four new cast forms (often called new-style or C++-style
casts):
const_cast<T>(expression)
dynamic_cast<T>(expression)
reinterpret_cast<T>(expression)
static_cast<T>(expression)
Each serves a distinct purpose:
■
const_cast is typically used to cast away the constness of objects. It
is the only C++-style cast that can do this.
■
dynamic_cast is primarily used to perform “safe downcasting,” i.e.,
to determine whether an object is of a particular type in an inheritance hierarchy. It is the only cast that cannot be performed using
the old-style syntax. It is also the only cast that may have a significant runtime cost. (I’ll provide details on this a bit later.)
■
reinterpret_cast is intended for low-level casts that yield implementation-dependent (i.e., unportable) results, e.g., casting a pointer
to an int. Such casts should be rare outside low-level code. I use it
only once in this book, and that’s only when discussing how you
might write a debugging allocator for raw memory (see Item 50).
■
static_cast can be used to force implicit conversions (e.g., non-const
object to const object (as in Item 3), int to double, etc.). It can also be
used to perform the reverse of many such conversions (e.g., void*
pointers to typed pointers, pointer-to-base to pointer-to-derived),
though it cannot cast from const to non-const objects. (Only
const_cast can do that.)
The old-style casts continue to be legal, but the new forms are preferable. First, they’re much easier to identify in code (both for humans
and for tools like grep), thus simplifying the process of finding places
in the code where the type system is being subverted. Second, the
more narrowly specified purpose of each cast makes it possible for
compilers to diagnose usage errors. For example, if you try to cast
away constness using a new-style cast other than const_cast, your
code won’t compile.
About the only time I use an old-style cast is when I want to call an ex-
plicit constructor to pass an object to a function. For example:
118A Item 27
Chapter 5
class Widget {
public:
explicit Widget(int size);
...
};
void doSomeWork(const Widget& w);
doSomeWork(Widget(15));
doSomeWork(static_cast<Widget>(15));
// create Widget from int
// with function-style cast
// create Widget from int
// with C++-style cast
Somehow, deliberate object creation doesn’t “feel” like a cast, so I’d
probably use the function-style cast instead of the static_cast in this
case. (They do exactly the same thing here: create a temporary Widget
object to pass to doSomeWork.) Then again, code that leads to a core
dump usually feels pretty reasonable when you write it, so perhaps
you’d best ignore feelings and use new-style casts all the time.
Many programmers believe that casts do nothing but tell compilers to
treat one type as another, but this is mistaken. Type conversions of
any kind (either explicit via casts or implicit by compilers) often lead to
code that is executed at runtime. For example, in this code fragment,
int x, y;
...
double d = static_cast<double>(x)/y;
// divide x by y, but use
// floating point division
the cast of the int x to a double almost certainly generates code,
because on most architectures, the underlying representation for an
int is different from that for a double. That’s perhaps not so surprising,
but this example may widen your eyes a bit:
class Base { ... };
class Derived: public Base { ... };
Derived d;
Base *pb = &d;
// implicitly convert Derived* ⇒ Base*
Here we’re just creating a base class pointer to a derived class object,
but sometimes, the two pointer values will not be the same. When
that’s the case, an offset is applied at runtime to the Derived* pointer to
get the correct Base* pointer value.
This last example demonstrates that a single object (e.g., an object of
type Derived) might have more than one address (e.g., its address
when pointed to by a Base* pointer and its address when pointed to by
a Derived* pointer). That can’t happen in C. It can’t happen in Java. It
can’t happen in C#. It does happen in C++. In fact, when multiple
Implementations
Item 27 119A
inheritance is in use, it happens virtually all the time, but it can happen under single inheritance, too. Among other things, that means
you should generally avoid making assumptions about how things are
laid out in C++, and you should certainly not perform casts based on
such assumptions. For example, casting object addresses to char*
pointers and then using pointer arithmetic on them almost always
yields undefined behavior.
But note that I said that an offset is “sometimes” required. The way
objects are laid out and the way their addresses are calculated varies
from compiler to compiler. That means that just because your “I know
how things are laid out” casts work on one platform doesn’t mean
they’ll work on others. The world is filled with woeful programmers
who’ve learned this lesson the hard way.
An interesting thing about casts is that it’s easy to write something
that looks right (and might be right in other languages) but is wrong.
Many application frameworks, for example, require that virtual member function implementations in derived classes call their base class
counterparts first. Suppose we have a Window base class and a SpecialWindow derived class, both of which define the virtual function
onResize. Further suppose that SpecialWindow’s onResize is expected to
invoke Window’s onResize first. Here’s a way to implement this that
looks like it does the right thing, but doesn’t:
class Window {
public:
virtual void onResize() { ... }
...
};
class SpecialWindow: public Window {
public:
virtual void onResize() {
static_cast<Window>(*this).onResize();
...
}
...
// base class
// base onResize impl
// derived class
// derived onResize impl;
// cast *this to Window,
// then call its onResize;
// this doesn’t work!
// do SpecialWindow// specific stuff
};
I’ve highlighted the cast in the code. (It’s a new-style cast, but using
an old-style cast wouldn’t change anything.) As you would expect, the
code casts *this to a Window. The resulting call to onResize therefore
invokes Window::onResize. What you might not expect is that it does
not invoke that function on the current object! Instead, the cast cre-
120A Item 27
Chapter 5
ates a new, temporary copy of the base class part of *this, then invokes
onResize on the copy! The above code doesn’t call Window::onResize on
the current object and then perform the SpecialWindow-specific
actions on that object — it calls Window::onResize on a copy of the base
class part of the current object before performing SpecialWindow-specific actions on the current object. If Window::onResize modifies the
current object (hardly a remote possibility, since onResize is a nonconst member function), the current object won’t be modified. Instead,
a copy of that object will be modified. If SpecialWindow::onResize modifies the current object, however, the current object will be modified,
leading to the prospect that the code will leave the current object in an
invalid state, one where base class modifications have not been made,
but derived class ones have been.
The solution is to eliminate the cast, replacing it with what you really
want to say. You don’t want to trick compilers into treating *this as a
base class object; you want to call the base class version of onResize on
the current object. So say that:
class SpecialWindow: public Window {
public:
virtual void onResize() {
Window::onResize();
...
}
...
};
// call Window::onResize
// on *this
This example also demonstrates that if you find yourself wanting to
cast, it’s a sign that you could be approaching things the wrong way.
This is especially the case if your want is for dynamic_cast.
Before delving into the design implications of dynamic_cast, it’s worth
observing that many implementations of dynamic_cast can be quite
slow. For example, at least one common implementation is based in
part on string comparisons of class names. If you’re performing a
dynamic_cast on an object in a single-inheritance hierarchy four levels
deep, each dynamic_cast under such an implementation could cost you
up to four calls to strcmp to compare class names. A deeper hierarchy
or one using multiple inheritance would be more expensive. There are
reasons that some implementations work this way (they have to do
with support for dynamic linking). Nonetheless, in addition to being
leery of casts in general, you should be especially leery of
dynamic_casts in performance-sensitive code.
The need for dynamic_cast generally arises because you want to perform derived class operations on what you believe to be a derived class
Implementations
Item 27 121A
object, but you have only a pointer- or reference-to-base through
which to manipulate the object. There are two general ways to avoid
this problem.
First, use containers that store pointers (often smart pointers — see
Item 13) to derived class objects directly, thus eliminating the need to
manipulate such objects through base class interfaces. For example,
if, in our Window/SpecialWindow hierarchy, only SpecialWindows support blinking, instead of doing this:
class Window { ... };
class SpecialWindow: public Window {
public:
void blink();
...
};
typedef
// see Item 13 for info
std::vector<std::tr1::shared_ptr<Window> > VPW; // on tr1::shared_ptr
VPW winPtrs;
...
for (VPW::iterator iter = winPtrs.begin();
// undesirable code:
iter != winPtrs.end();
// uses dynamic_cast
++iter) {
if (SpecialWindow *psw = dynamic_cast<SpecialWindow*>(iter->get()))
psw->blink();
}
try to do this instead:
typedef std::vector<std::tr1::shared_ptr<SpecialWindow> > VPSW;
VPSW winPtrs;
...
for (VPSW::iterator iter = winPtrs.begin();
// better code: uses
iter != winPtrs.end();
// no dynamic_cast
++iter)
(*iter)->blink();
Of course, this approach won’t allow you to store pointers to all possible Window derivatives in the same container. To work with different
window types, you might need multiple type-safe containers.
An alternative that will let you manipulate all possible Window derivatives through a base class interface is to provide virtual functions in
the base class that let you do what you need. For example, though
only SpecialWindows can blink, maybe it makes sense to declare the
122A Item 27
Chapter 5
function in the base class, offering a default implementation that does
nothing:
class Window {
public:
virtual void blink() {}
...
};
// default impl is no-op;
// see Item 34 for why
// a default impl may be
// a bad idea
class SpecialWindow: public Window {
public:
virtual void blink() { ... }
// in this class, blink
...
// does something
};
typedef std::vector<std::tr1::shared_ptr<Window> > VPW;
VPW winPtrs;
// container holds
// (ptrs to) all possible
...
// Window types
for (VPW::iterator iter = winPtrs.begin();
iter != winPtrs.end();
++iter)
// note lack of
(*iter)->blink();
// dynamic_cast
Neither of these approaches — using type-safe containers or moving
virtual functions up the hierarchy — is universally applicable, but in
many cases, they provide a viable alternative to dynamic_casting. When
they do, you should embrace them.
One thing you definitely want to avoid is designs that involve cascading dynamic_casts, i.e., anything that looks like this:
class Window { ... };
...
// derived classes are defined here
typedef std::vector<std::tr1::shared_ptr<Window> > VPW;
VPW winPtrs;
...
for (VPW::iterator iter = winPtrs.begin(); iter != winPtrs.end(); ++iter)
{
if (SpecialWindow1 *psw1 =
dynamic_cast<SpecialWindow1*>(iter->get())) { ... }
else if (SpecialWindow2 *psw2 =
dynamic_cast<SpecialWindow2*>(iter->get())) { ... }
else if (SpecialWindow3 *psw3 =
dynamic_cast<SpecialWindow3*>(iter->get())) { ... }
...
}
Implementations
Item 28 123A
Such C++ generates code that’s big and slow, plus it’s brittle, because
every time the Window class hierarchy changes, all such code has to
be examined to see if it needs to be updated. (For example, if a new
derived class gets added, a new conditional branch probably needs to
be added to the above cascade.) Code that looks like this should
almost always be replaced with something based on virtual function
calls.
Good C++ uses very few casts, but it’s generally not practical to get rid
of all of them. The cast from int to double on page 118, for example, is
a reasonable use of a cast, though it’s not strictly necessary. (The code
could be rewritten to declare a new variable of type double that’s initialized with x’s value.) Like most suspicious constructs, casts should
be isolated as much as possible, typically hidden inside functions
whose interfaces shield callers from the grubby work being done
inside.
Things to Remember
✦
Avoid casts whenever practical, especially dynamic_casts in performance-sensitive code. If a design requires casting, try to develop a
cast-free alternative.
✦
When casting is necessary, try to hide it inside a function. Clients
can then call the function instead of putting casts in their own code.
✦
Prefer C++-style casts to old-style casts. They are easier to see, and
they are more specific about what they do.
Item 28: Avoid returning “handles” to object internals.
Suppose you’re working on an application involving rectangles. Each
rectangle can be represented by its upper left corner and its lower
right corner. To keep a Rectangle object small, you might decide that
the points defining its extent shouldn’t be stored in the Rectangle
itself, but rather in an auxiliary struct that the Rectangle points to:
class Point {
public:
Point(int x, int y);
...
void setX(int newVal);
void setY(int newVal);
...
};
// class for representing points
124A Item 28
Chapter 5
struct RectData {
// Point data for a Rectangle
Point ulhc;
// ulhc = “ upper left-hand corner”
Point lrhc;
// lrhc = “ lower right-hand corner”
};
class Rectangle {
...
private:
std::tr1::shared_ptr<RectData> pData;
// see Item 13 for info on
};
// tr1::shared_ptr
Because Rectangle clients will need to be able to determine the extent
of a Rectangle, the class provides the upperLeft and lowerRight functions. However, Point is a user-defined type, so, mindful of Item 20’s
observation that passing user-defined types by reference is typically
more efficient than passing them by value, these functions return references to the underlying Point objects:
class Rectangle {
public:
...
Point& upperLeft() const { return pData->ulhc; }
Point& lowerRight() const { return pData->lrhc; }
...
};
This design will compile, but it’s wrong. In fact, it’s self-contradictory.
On the one hand, upperLeft and lowerRight are declared to be const
member functions, because they are designed only to offer clients a
way to learn what the Rectangle’s points are, not to let clients modify
the Rectangle (see Item 3). On the other hand, both functions return
references to private internal data — references that callers can use to
modify that internal data! For example:
Point coord1(0, 0);
Point coord2(100, 100);
const Rectangle rec(coord1, coord2);
rec.upperLeft().setX(50);
// rec is a const rectangle from
// (0, 0) to (100, 100)
// now rec goes from
// (50, 0) to (100, 100)!
Here, notice how the caller of upperLeft is able to use the returned reference to one of rec’s internal Point data members to modify that member. But rec is supposed to be const!
This immediately leads to two lessons. First, a data member is only as
encapsulated as the most accessible function returning a reference to
it. In this case, though ulhc and lrhc are supposed to be private to their
Rectangle, they’re effectively public, because the public functions
Implementations
Item 28 125A
upperLeft and lowerRight return references to them. Second, if a const
member function returns a reference to data associated with an object
that is stored outside the object itself, the caller of the function can
modify that data. (This is just a fallout of the limitations of bitwise
constness — see Item 3.)
Everything we’ve done has involved member functions returning references, but if they returned pointers or iterators, the same problems
would exist for the same reasons. References, pointers, and iterators
are all handles (ways to get at other objects), and returning a handle
to an object’s internals always runs the risk of compromising an
object’s encapsulation. As we’ve seen, it can also lead to const member
functions that allow an object’s state to be modified.
We generally think of an object’s “internals” as its data members, but
member functions not accessible to the general public (i.e., that are
protected or private) are part of an object’s internals, too. As such, it’s
important not to return handles to them. This means you should
never have a member function return a pointer to a less accessible
member function. If you do, the effective access level will be that of the
more accessible function, because clients will be able to get a pointer
to the less accessible function, then call that function through the
pointer.
Functions that return pointers to member functions are uncommon,
however, so let’s turn our attention back to the Rectangle class and its
upperLeft and lowerRight member functions. Both of the problems
we’ve identified for those functions can be eliminated by simply applying const to their return types:
class Rectangle {
public:
...
const Point& upperLeft() const { return pData->ulhc; }
const Point& lowerRight() const { return pData->lrhc; }
...
};
With this altered design, clients can read the Points defining a rectangle, but they can’t write them. This means that declaring upperLeft and
lowerRight as const is no longer a lie, because they no longer allow callers to modify the state of the object. As for the encapsulation problem,
we always intended to let clients see the Points making up a Rectangle,
so this is a deliberate relaxation of encapsulation. More importantly,
it’s a limited relaxation: only read access is being granted by these
functions. Write access is still prohibited.
Even so, upperLeft and lowerRight are still returning handles to an
object’s internals, and that can be problematic in other ways. In par-
126A Item 28
Chapter 5
ticular, it can lead to dangling handles: handles that refer to parts of
objects that don’t exist any longer. The most common source of such
disappearing objects are function return values. For example, consider a function that returns the bounding box for a GUI object in the
form of a rectangle:
class GUIObject { ... };
const Rectangle
boundingBox(const GUIObject& obj);
// returns a rectangle by
// value; see Item 3 for why
// return type is const
Now consider how a client might use this function:
GUIObject *pgo;
...
const Point *pUpperLeft =
&(boundingBox(*pgo).upperLeft());
// make pgo point to
// some GUIObject
// get a ptr to the upper
// left point of its
// bounding box
The call to boundingBox will return a new, temporary Rectangle object.
That object doesn’t have a name, so let’s call it temp. upperLeft will
then be called on temp, and that call will return a reference to an
internal part of temp, in particular, to one of the Points making it up.
pUpperLeft will then point to that Point object. So far, so good, but
we’re not done yet, because at the end of the statement, boundingBox’s
return value — temp — will be destroyed, and that will indirectly lead
to the destruction of temp’s Points. That, in turn, will leave pUpperLeft
pointing to an object that no longer exists; pUpperLeft will dangle by
the end of the statement that created it!
This is why any function that returns a handle to an internal part of
the object is dangerous. It doesn’t matter whether the handle is a
pointer, a reference, or an iterator. It doesn’t matter whether it’s qualified with const. It doesn’t matter whether the member function
returning the handle is itself const. All that matters is that a handle is
being returned, because once that’s being done, you run the risk that
the handle will outlive the object it refers to.
This doesn’t mean that you should never have a member function that
returns a handle. Sometimes you have to. For example, operator[]
allows you to pluck individual elements out of strings and vectors, and
these operator[]s work by returning references to the data in the containers (see Item 3) — data that is destroyed when the containers
themselves are. Still, such functions are the exception, not the rule.
Things to Remember
✦
Avoid returning handles (references, pointers, or iterators) to object
internals. Not returning handles increases encapsulation, helps
const member functions act const, and minimizes the creation of
dangling handles.
Implementations
Item 29 127A
Item 29: Strive for exception-safe code.
Exception safety is sort of like pregnancy...but hold that thought for a
moment. We can’t really talk reproduction until we’ve worked our way
through courtship.
Suppose we have a class for representing GUI menus with background images. The class is designed to be used in a threaded environment, so it has a mutex for concurrency control:
class PrettyMenu {
public:
...
void changeBackground(std::istream& imgSrc); // change background
...
// image
private:
Mutex mutex;
// mutex for this object
Image *bgImage;
// current background image
int imageChanges;
// # of times image has been changed
};
Consider this possible implementation of PrettyMenu’s changeBack-
ground function:
void PrettyMenu::changeBackground(std::istream& imgSrc)
{
lock(&mutex);
// acquire mutex (as in Item 14)
delete bgImage;
// get rid of old background
++imageChanges;
// update image change count
bgImage = new Image(imgSrc);
// install new background
unlock(&mutex);
// release mutex
}
From the perspective of exception safety, this function is about as bad
as it gets. There are two requirements for exception safety, and this
satisfies neither.
When an exception is thrown, exception-safe functions:
■
Leak no resources. The code above fails this test, because if the
“new Image(imgSrc)” expression yields an exception, the call to unlock never gets executed, and the mutex is held forever.
■
Don’t allow data structures to become corrupted. If “new Image(imgSrc)” throws, bgImage is left pointing to a deleted object. In
addition, imageChanges has been incremented, even though it’s not
true that a new image has been installed. (On the other hand, the
old image has definitely been eliminated, so I suppose you could
argue that the image has been “changed.”)
128A Item 29
Chapter 5
Addressing the resource leak issue is easy, because Item 13 explains
how to use objects to manage resources, and Item 14 introduces the
Lock class as a way to ensure that mutexes are released in a timely
fashion:
void PrettyMenu::changeBackground(std::istream& imgSrc)
{
Lock ml(&mutex);
// from Item 14: acquire mutex and
// ensure its later release
delete bgImage;
++imageChanges;
bgImage = new Image(imgSrc);
}
One of the best things about resource management classes like Lock is
that they usually make functions shorter. See how the call to unlock is
no longer needed? As a general rule, less code is better code, because
there’s less to go wrong and less to misunderstand when making
changes.
With the resource leak behind us, we can turn our attention to the
issue of data structure corruption. Here we have a choice, but before
we can choose, we have to confront the terminology that defines our
choices.
Exception-safe functions offer one of three guarantees:
■
Functions offering the basic guarantee promise that if an exception is thrown, everything in the program remains in a valid state.
No objects or data structures become corrupted, and all objects
are in an internally consistent state (e.g., all class invariants are
satisfied). However, the exact state of the program may not be predictable. For example, we could write changeBackground so that if
an exception were thrown, the PrettyMenu object might continue to
have the old background image, or it might have some default
background image, but clients wouldn’t be able to predict which.
(To find out, they’d presumably have to call some member function that would tell them what the current background image
was.)
■
Functions offering the strong guarantee promise that if an exception is thrown, the state of the program is unchanged. Calls to
such functions are atomic in the sense that if they succeed, they
succeed completely, and if they fail, the program state is as if
they’d never been called.
Implementations
Item 29 129A
Working with functions offering the strong guarantee is easier
than working with functions offering only the basic guarantee, because after calling a function offering the strong guarantee, there
are only two possible program states: as expected following successful execution of the function, or the state that existed at the
time the function was called. In contrast, if a call to a function offering only the basic guarantee yields an exception, the program
could be in any valid state.
■
Functions offering the nothrow guarantee promise never to
throw exceptions, because they always do what they promise to
do. All operations on built-in types (e.g., ints, pointers, etc.) are
nothrow (i.e., offer the nothrow guarantee). This is a critical building block of exception-safe code.
It might seem reasonable to assume that functions with an empty
exception specification are nothrow, but this isn’t necessarily true.
For example, consider this function:
int doSomething() throw();
// note empty exception spec.
This doesn’t say that doSomething will never throw an exception; it
says that if doSomething throws an exception, it’s a serious error,
and the unexpected function should be called.† In fact, doSomething may not offer any exception guarantee at all. The declaration
of a function (including its exception specification, if it has one)
doesn’t tell you whether a function is correct or portable or efficient, and it doesn’t tell you which, if any, exception safety guarantee it offers, either. All those characteristics are determined by
the function’s implementation, not its declaration.
Exception-safe code must offer one of the three guarantees above. If it
doesn’t, it’s not exception-safe. The choice, then, is to determine
which guarantee to offer for each of the functions you write. Other
than when dealing with exception-unsafe legacy code (which we’ll discuss later in this Item), offering no exception safety guarantee should
be an option only if your crack team of requirements analysts has
identified a need for your application to leak resources and run with
corrupt data structures.
As a general rule, you want to offer the strongest guarantee that’s
practical. From an exception safety point of view, nothrow functions
are wonderful, but it’s hard to climb out of the C part of C++ without
† For information on the unexpected function, consult your favorite search engine or
comprehensive C++ text. (You’ll probably have better luck searching for set_unexpected,
the function that specifies the unexpected function.)
130A Item 29
Chapter 5
calling functions that might throw. Anything using dynamically allocated memory (e.g., all STL containers) typically throws a bad_alloc
exception if it can’t find enough memory to satisfy a request (see
Item 49). Offer the nothrow guarantee when you can, but for most
functions, the choice is between the basic and strong guarantees.
In the case of changeBackground, almost offering the strong guarantee
is not difficult. First, we change the type of PrettyMenu’s bgImage data
member from a built-in Image* pointer to one of the smart resourcemanaging pointers described in Item 13. Frankly, this is a good idea
purely on the basis of preventing resource leaks. The fact that it helps
us offer the strong exception safety guarantee simply reinforces
Item 13’s argument that using objects (such as smart pointers) to
manage resources is fundamental to good design. In the code below, I
show use of tr1::shared_ptr, because its more intuitive behavior when
copied generally makes it preferable to auto_ptr.
Second, we reorder the statements in changeBackground so that we
don’t increment imageChanges until the image has been changed. As a
general rule, it’s a good policy not to change the status of an object to
indicate that something has happened until something actually has.
Here’s the resulting code:
class PrettyMenu {
...
std::tr1::shared_ptr<Image> bgImage;
...
};
void PrettyMenu::changeBackground(std::istream& imgSrc)
{
Lock ml(&mutex);
bgImage.reset(new Image(imgSrc)); // replace bgImage’s internal
// pointer with the result of the
// “new Image” expression
++imageChanges;
}
Note that there’s no longer a need to manually delete the old image,
because that’s handled internally by the smart pointer. Furthermore,
the deletion takes place only if the new image is successfully created.
More precisely, the tr1::shared_ptr::reset function will be called only if its
parameter (the result of “new Image(imgSrc)”) is successfully created.
delete is used only inside the call to reset, so if the function is never
entered, delete is never used. Note also that the use of an object (the
tr1::shared_ptr) to manage a resource (the dynamically allocated Image)
has again pared the length of changeBackground.
As I said, those two changes almost suffice to allow changeBackground
to offer the strong exception safety guarantee. What’s the fly in the
Implementations
Item 29 131A
ointment? The parameter imgSrc. If the Image constructor throws an
exception, it’s possible that the read marker for the input stream has
been moved, and such movement would be a change in state visible to
the rest of the program. Until changeBackground addresses that issue,
it offers only the basic exception safety guarantee.
Let’s set that aside, however, and pretend that changeBackground does
offer the strong guarantee. (I’m confident you could come up with a
way for it to do so, perhaps by changing its parameter type from an
istream to the name of the file containing the image data.) There is a
general design strategy that typically leads to the strong guarantee,
and it’s important to be familiar with it. The strategy is known as
“copy and swap.” In principle, it’s very simple. Make a copy of the
object you want to modify, then make all needed changes to the copy.
If any of the modifying operations throws an exception, the original
object remains unchanged. After all the changes have been successfully completed, swap the modified object with the original in a nonthrowing operation.
This is usually implemented by putting all the per-object data from
the “real” object into a separate implementation object, then giving the
real object a pointer to its implementation object. This is often known
as the “pimpl idiom,” and Item 31 describes it in some detail. For PrettyMenu, it would typically look something like this:
struct PMImpl {
// PMImpl = “PrettyMenu
std::tr1::shared_ptr<Image> bgImage;
// Impl.”; see below for
int imageChanges;
// why it’s a struct
};
class PrettyMenu {
...
private:
Mutex mutex;
std::tr1::shared_ptr<PMImpl> pImpl;
};
void PrettyMenu::changeBackground(std::istream& imgSrc)
{
using std::swap;
// see Item 25
Lock ml(&mutex);
// acquire the mutex
std::tr1::shared_ptr<PMImpl>
// copy obj. data
pNew(new PMImpl(*pImpl));
pNew->bgImage.reset(new Image(imgSrc));
// modify the copy
++pNew->imageChanges;
swap(pImpl, pNew);
// swap the new
// data into place
}
// release the mutex
132A Item 29
Chapter 5
In this example, I’ve chosen to make PMImpl a struct instead of a class,
because the encapsulation of PrettyMenu data is assured by pImpl being
private. Making PMImpl a class would be at least as good, though
somewhat less convenient. (It would also keep the object-oriented purists at bay.) If desired, PMImpl could be nested inside PrettyMenu, but
packaging issues such as that are independent of writing exceptionsafe code, which is our concern here.
The copy-and-swap strategy is an excellent way to make all-or-nothing
changes to an object’s state, but, in general, it doesn’t guarantee that
the overall function is strongly exception-safe. To see why, consider
an abstraction of changeBackground, someFunc, that uses copy-andswap, but that includes calls to two other functions, f1 and f2:
void someFunc()
{
...
f1();
f2();
...
}
// make copy of local state
// swap modified state into place
It should be clear that if f1 or f2 is less than strongly exception-safe, it
will be hard for someFunc to be strongly exception-safe. For example,
suppose that f1 offers only the basic guarantee. For someFunc to offer
the strong guarantee, it would have to write code to determine the
state of the entire program prior to calling f1, catch all exceptions from
f1, then restore the original state.
Things aren’t really any better if both f1 and f2 are strongly exception
safe. After all, if f1 runs to completion, the state of the program may
have changed in arbitrary ways, so if f2 then throws an exception, the
state of the program is not the same as it was when someFunc was
called, even though f2 didn’t change anything.
The problem is side effects. As long as functions operate only on local
state (e.g., someFunc affects only the state of the object on which it’s
invoked), it’s relatively easy to offer the strong guarantee. When functions have side effects on non-local data, it’s much harder. If a side
effect of calling f1, for example, is that a database is modified, it will be
hard to make someFunc strongly exception-safe. There is, in general,
no way to undo a database modification that has already been committed; other database clients may have already seen the new state of
the database.
Issues such as these can prevent you from offering the strong guarantee for a function, even though you’d like to. Another issue is efficiency. The crux of copy-and-swap is the idea of modifying a copy of an
Implementations
Item 29 133A
object’s data, then swapping the modified data for the original in a
non-throwing operation. This requires making a copy of each object to
be modified, which takes time and space you may be unable or unwilling to make available. The strong guarantee is highly desirable, and
you should offer it when it’s practical, but it’s not practical 100% of
the time.
When it’s not, you’ll have to offer the basic guarantee. In practice,
you’ll probably find that you can offer the strong guarantee for some
functions, but the cost in efficiency or complexity will make it untenable for many others. As long as you’ve made a reasonable effort to
offer the strong guarantee whenever it’s practical, no one should be in
a position to criticize you when you offer only the basic guarantee. For
many functions, the basic guarantee is a perfectly reasonable choice.
Things are different if you write a function offering no exception-safety
guarantee at all, because in this respect it’s reasonable to assume
that you’re guilty until proven innocent. You should be writing exception-safe code. But you may have a compelling defense. Consider
again the implementation of someFunc that calls the functions f1 and
f2. Suppose f2 offers no exception safety guarantee at all, not even the
basic guarantee. That means that if f2 emits an exception, the program may have leaked resources inside f2. It means that f2 may have
corrupted data structures, e.g., sorted arrays might not be sorted any
longer, objects being transferred from one data structure to another
might have been lost, etc. There’s no way that someFunc can compensate for those problems. If the functions someFunc calls offer no exception-safety guarantees, someFunc itself can’t offer any guarantees.
Which brings me back to pregnancy. A female is either pregnant or
she’s not. It’s not possible to be partially pregnant. Similarly, a software system is either exception-safe or it’s not. There’s no such thing
as a partially exception-safe system. If a system has even a single
function that’s not exception-safe, the system as a whole is not exception-safe, because calls to that one function could lead to leaked
resources and corrupted data structures. Unfortunately, much C++
legacy code was written without exception safety in mind, so many
systems today are not exception-safe. They incorporate code that was
written in an exception-unsafe manner.
There’s no reason to perpetuate this state of affairs. When writing new
code or modifying existing code, think carefully about how to make it
exception-safe. Begin by using objects to manage resources. (Again,
see Item 13.) That will prevent resource leaks. Follow that by determining which of the three exception safety guarantees is the strongest
you can practically offer for each function you write, settling for no
134A Item 30
Chapter 5
guarantee only if calls to legacy code leave you no choice. Document
your decisions, both for clients of your functions and for future maintainers. A function’s exception-safety guarantee is a visible part of its
interface, so you should choose it as deliberately as you choose all
other aspects of a function’s interface.
Forty years ago, goto-laden code was considered perfectly good practice. Now we strive to write structured control flows. Twenty years ago,
globally accessible data was considered perfectly good practice. Now
we strive to encapsulate data. Ten years ago, writing functions without thinking about the impact of exceptions was considered perfectly
good practice. Now we strive to write exception-safe code.
Time goes on. We live. We learn.
Things to Remember
✦
Exception-safe functions leak no resources and allow no data structures to become corrupted, even when exceptions are thrown. Such
functions offer the basic, strong, or nothrow guarantees.
✦
The strong guarantee can often be implemented via copy-and-swap,
but the strong guarantee is not practical for all functions.
✦
A function can usually offer a guarantee no stronger than the weakest guarantee of the functions it calls.
Item 30: Understand the ins and outs of inlining.
Inline functions — what a wonderful idea! They look like functions,
they act like functions, they’re ever so much better than macros (see
Item 2), and you can call them without having to incur the overhead of
a function call. What more could you ask for?
You actually get more than you might think, because avoiding the cost
of a function call is only part of the story. Compiler optimizations are
typically designed for stretches of code that lack function calls, so
when you inline a function, you may enable compilers to perform context-specific optimizations on the body of the function. Most compilers
never perform such optimizations on “outlined” function calls.
In programming, however, as in life, there is no free lunch, and inline
functions are no exception. The 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 size of
your object code. On machines with limited memory, overzealous
inlining can give rise to programs that are too big for the available
Implementations
Item 30 135A
space. Even with virtual memory, inline-induced code bloat can lead
to additional paging, a reduced instruction cache hit rate, and the
performance penalties that accompany these things.
On the other hand, if an inline function body is very short, the code
generated for the function body may be smaller than the code generated for a function call. If that is the case, inlining the function may
actually lead to smaller object code and a higher instruction cache hit
rate!
Bear in mind that inline is a request to compilers, not a command. The
request can be given implicitly or explicitly. The implicit way is to
define a function inside a class definition:
class Person {
public:
...
int age() const { return theAge; }
...
private:
int theAge;
};
// an implicit inline request: age is
// defined in a class definition
Such functions are usually member functions, but Item 46 explains
that friend functions can also be defined inside classes. When they
are, they’re also implicitly declared inline.
The explicit way to declare an inline function is to precede its definition with the inline keyword. For example, this is how the standard
max template (from <algorithm>) is often implemented:
template<typename T>
inline const T& std::max(const T& a, const T& b)
{ return a < b ? b : a; }
// an explicit inline
// request: std::max is
// preceded by “inline”
The fact that max is a template brings up the observation that both
inline functions and templates are typically defined in header files.
This leads some programmers to conclude that function templates
must be inline. This conclusion is both invalid and potentially harmful, so it’s worth looking into it a bit.
Inline functions must typically be in header files, because most build
environments do inlining during compilation. In order to replace a
function call with the body of the called function, compilers must
know what the function looks like. (Some build environments can
inline during linking, and a few — e.g., managed environments based
on the .NET Common Language Infrastructure (CLI) — can actually
inline at runtime. Such environments are the exception, however, not
the rule. Inlining in most C++ programs is a compile-time activity.)
136A Item 30
Chapter 5
Templates are typically in header files, because compilers need to
know what a template looks like in order to instantiate it when it’s
used. (Again, this is not universal. Some build environments perform
template instantiation during linking. However, compile-time instantiation is more common.)
Template instantiation is independent of inlining. If you’re writing a
template and you believe that all the functions instantiated from the
template should be inlined, declare the template inline; that’s what’s
done with the std::max implementation above. But if you’re writing a
template for functions that you have no reason to want inlined, avoid
declaring the template inline (either explicitly or implicitly). Inlining
has costs, and you don’t want to incur them without forethought.
We’ve already mentioned how inlining can cause code bloat (a particularly important consideration for template authors — see Item 44), but
there are other costs, too, which we’ll discuss in a moment.
Before we do that, let’s finish the observation that inline is a request
that compilers may ignore. Most compilers refuse to inline functions
they deem too complicated (e.g., those that contain loops or are recursive), and all but the most trivial calls to virtual functions defy inlining. This latter observation shouldn’t be a surprise. virtual means “wait
until runtime to figure out which function to call,” and inline means
“before execution, replace the call site with the called function.” If
compilers don’t know which function will be called, you can hardly
blame them for refusing to inline the function’s body.
It all adds up to this: whether a given inline function is actually
inlined depends on the build environment you’re using — primarily on
the compiler. Fortunately, most compilers have a diagnostic level that
will result in a warning (see Item 53) if they fail to inline a function
you’ve asked them to.
Sometimes compilers generate a function body for an inline function
even when they are perfectly willing to inline the function. For example, if your program takes the address of an inline function, compilers
must typically generate an outlined function body for it. How can they
come up with a pointer to a function that doesn’t exist? Coupled with
the fact that compilers typically don’t perform inlining across calls
through function pointers, this means that calls to an inline function
may or may not be inlined, depending on how the calls are made:
inline void f() {...}
void (*pf )() = f;
...
f();
// assume compilers are willing to inline calls to f
// pf points to f
// this call will be inlined, because it’s a “normal” call
Implementations
pf();
Item 30 137A
// this call probably won’t be, because it’s through
// a function pointer
The specter of un-inlined inline functions can haunt you even if you
never use function pointers, because programmers aren’t necessarily
the only ones asking for pointers to functions. Sometimes compilers
generate out-of-line copies of constructors and destructors so that
they can get pointers to those functions for use during construction
and destruction of objects in arrays.
In fact, constructors and destructors are often worse candidates for
inlining than a casual examination would indicate. For example, consider the constructor for class Derived below:
class Base {
public:
...
private:
std::string bm1, bm2;
};
class Derived: public Base {
public:
Derived() {}
...
private:
std::string dm1, dm2, dm3;
};
// base members 1 and 2
// Derived’s ctor is empty — or is it?
// derived members 1–3
This constructor looks like an excellent candidate for inlining, since it
contains no code. But looks can be deceiving.
C++ makes various guarantees about things that happen when
objects are created and destroyed. When you use new, for example,
your dynamically created objects are automatically initialized by their
constructors, and when you use delete, the corresponding destructors
are invoked. When 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. If an exception is thrown during construction of
an object, any parts of the object that have already been fully constructed are automatically destroyed. In all these scenarios, C++ says
what must happen, but it doesn’t say how. That’s up to compiler
implementers, but it should be clear that those things don’t happen
by themselves. There has to be some code in your program to make
those things happen, and that code — the code written by compilers
and inserted into your program during compilation — has to go somewhere. Sometimes it ends up in constructors and destructors, so we
138A Item 30
Chapter 5
can imagine implementations generating code equivalent to the following for the allegedly empty Derived constructor above:
Derived::Derived()
{
Base::Base();
try { dm1.std::string::string(); }
catch (...) {
Base::~Base();
throw;
}
try { dm2.std::string::string(); }
catch(...) {
dm1.std::string::~string();
Base::~Base();
throw;
}
try { dm3.std::string::string(); }
catch(...) {
dm2.std::string::~string();
dm1.std::string::~string();
Base::~Base();
throw;
}
}
// conceptual implementation of
// “empty” Derived ctor
// initialize Base part
// try to construct dm1
// if it throws,
// destroy base class part and
// propagate the exception
// try to construct dm2
// if it throws,
// destroy dm1,
// destroy base class part, and
// propagate the exception
// construct dm3
// if it throws,
// destroy dm2,
// destroy dm1,
// destroy base class part, and
// propagate the exception
This code is unrepresentative of what real compilers emit, because
real compilers deal with exceptions in more sophisticated ways. Still,
this accurately reflects the behavior that Derived’s “empty” constructor
must offer. No matter how sophisticated a compiler’s exception implementation, Derived’s constructor must at least call constructors for its
data members and base class, and those calls (which might themselves be inlined) could affect its attractiveness for inlining.
The same reasoning applies to 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 be inlined, the Derived constructor will gain five copies of that function’s code, one for each of the five
strings in a Derived object (the two it inherits plus the three it declares
itself). Perhaps now it’s clear why it’s not a no-brain decision whether
to inline Derived’s constructor. Similar considerations apply to
Derived’s destructor, which, one way or another, must see to it that all
the objects initialized by Derived’s constructor are properly destroyed.
Library designers must evaluate the impact of declaring functions
inline, because it’s impossible to provide binary upgrades to the client-
Implementations
Item 30 139A
visible inline functions in a library. In other words, if f is an inline
function in a library, clients of the library compile the body of f into
their applications. If a library implementer later decides to change f,
all clients who’ve used f must recompile. This is often undesirable. On
the other hand, if f is a non-inline function, a modification to f
requires only that clients relink. This is a substantially less onerous
burden than recompiling and, if the library containing the function is
dynamically linked, one that may be absorbed in a way that’s completely transparent to clients.
For purposes of program development, it is important to keep all these
considerations in mind, but from a practical point of view during coding, one fact dominates all others: most debuggers have trouble with
inline functions. This should be no great revelation. How do you set a
breakpoint in a function that isn’t there? Although some build environments manage to support debugging of inlined functions, many
environments simply disable inlining for debug builds.
This leads to a logical strategy for determining which functions should
be declared inline and which should not. Initially, don’t inline anything, or at least limit your inlining to those functions that must be
inline (see Item 46) or are truly trivial (such as Person::age on
page 135). By employing inlines cautiously, you facilitate your use of a
debugger, but you also put inlining in its proper place: as a handapplied optimization. Don’t forget the empirically determined rule of
80-20, which states that a typical program spends 80% of its time
executing only 20% of its code. It’s an important rule, because it
reminds you that your goal as a software developer is to identify the
20% of your code that can increase your program’s overall performance. You can inline and otherwise tweak your functions until the
cows come home, but it’s wasted effort unless you’re focusing on the
right functions.
Things to Remember
✦
Limit most inlining to small, frequently called functions. This facilitates debugging and binary upgradability, minimizes potential code
bloat, and maximizes the chances of greater program speed.
✦
Don’t declare function templates inline just because they appear in
header files.
140A Item 31
Chapter 5
Item 31: Minimize compilation dependencies between
files.
So you go into your C++ program and make a minor change to the
implementation of a class. Not the class interface, mind you, just the
implementation; only the private stuff. Then you rebuild the program,
figuring that the exercise should take only a few seconds. After all,
only one class has been modified. You click on Build or type make (or
some 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. A class definition specifies not only a
class interface but also a fair number of implementation details. For
example:
class Person {
public:
Person(const std::string& name, const Date& birthday,
const Address& addr);
std::string name() const;
std::string birthDate() const;
std::string address() const;
...
private:
std::string theName;
// implementation detail
Date theBirthDate;
// implementation detail
Address theAddress;
// implementation detail
};
Here, class Person can’t be compiled without access to definitions for
the classes the Person implementation uses, namely, string, Date, and
Address. Such definitions are typically provided through #include directives, so in the file defining the Person class, you are likely to find
something like this:
#include <string>
#include "date.h"
#include "address.h"
Unfortunately, this sets up a compilation dependency between the file
defining Person and these header files. If any of these header files is
changed, or if any of the header files they depend on changes, the file
containing the Person class must be recompiled, as must any files that
use Person. Such cascading compilation dependencies have caused
many a project untold grief.
Implementations
Item 31 141A
You might wonder why C++ insists on putting the implementation
details of a class in the class definition. For example, why can’t you
define Person this way, specifying the implementation details of the
class separately?
namespace std {
class string;
// forward declaration (an incorrect
}
// one — see below)
class Date;
// forward declaration
class Address;
// forward declaration
class Person {
public:
Person(const std::string& name, const Date& birthday,
const Address& addr);
std::string name() const;
std::string birthDate() const;
std::string address() const;
...
};
If that were possible, clients of Person would have to recompile only if
the interface to the class changed.
There are two problems with this idea. First, string is not a class, it’s a
typedef (for basic_string<char>). As a result, the forward declaration for
string is incorrect. The proper forward declaration is substantially more
complex, because it involves additional templates. That doesn’t matter,
however, because you shouldn’t try to manually declare parts of the
standard library. Instead, simply use the proper #includes and be done
with it. Standard headers are unlikely to be a compilation bottleneck,
especially if your build environment allows you to take advantage of
precompiled headers. If parsing standard headers really is a problem,
you may need to change your interface design to avoid using the parts
of the standard library that give rise to the undesirable #includes.
The second (and more significant) difficulty with forward-declaring
everything has to do with the need for compilers to know the size of
objects during compilation. Consider:
int main()
{
int x;
Person p( params );
...
}
// define an int
// define a Person
When compilers see the definition for x, they know they must allocate
enough space (typically on the stack) to hold an int. No problem. Each
142A Item 31
Chapter 5
compiler knows how big an int is. When compilers see the definition
for p, they know they have to allocate enough space for a Person, but
how are they supposed to know how big a Person object is? The only
way they can get that information is to consult the class definition,
but if it were legal for a class definition to omit the implementation
details, how would compilers know how much space to allocate?
This question fails to arise in languages like Smalltalk and Java,
because, when an object is defined in such languages, compilers allocate only enough space for a pointer to an object. That is, they handle
the code above as if it had been written like this:
int main()
{
int x;
Person *p;
...
}
// define an int
// define a pointer to a Person
This, of course, is legal C++, so you can play the “hide the object
implementation behind a pointer” game yourself. One way to do that
for Person is to separate it into two classes, one offering only an interface, the other implementing that interface. If the implementation
class is named PersonImpl, Person would be defined like this:
#include <string>
// standard library components
// shouldn’t be forward-declared
// for tr1::shared_ptr; see below
// forward decl of Person impl. class
// forward decls of classes used in
// Person interface
#include <memory>
class PersonImpl;
class Date;
class Address;
class Person {
public:
Person(const std::string& name, const Date& birthday,
const Address& addr);
std::string name() const;
std::string birthDate() const;
std::string address() const;
...
private:
// ptr to implementation;
std::tr1::shared_ptr<PersonImpl> pImpl;
// see Item 13 for info on
};
// std::tr1::shared_ptr
Here, the main class (Person) contains as a data member nothing but a
pointer (here, a tr1::shared_ptr — see Item 13) to its implementation
class (PersonImpl). Such a design is often said to be using the pimpl
Implementations
Item 31 143A
idiom (“pointer to implementation”). Within such classes, the name of
the pointer is often pImpl, as it is above.
With this design, clients of Person are divorced from the details of
dates, addresses, and persons. The implementations of those classes
can be modified at will, but Person clients need not recompile. In addition, because they’re unable to see the details of Person’s implementation, clients are unlikely to write code that somehow depends on those
details. This is a true separation of interface and implementation.
The key to this separation is replacement of dependencies on definitions
with dependencies on declarations. That’s the essence of minimizing
compilation dependencies: make your header files self-sufficient whenever it’s practical, and when it’s not, depend on declarations in other
files, not definitions. Everything else flows from this simple design
strategy. Hence:
■
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
presence of the type’s definition.
■
Depend on class declarations instead of class definitions
whenever you can. Note that you never need a class definition to
declare a function using that class, not even if the function passes
or returns the class type by value:
class Date;
Date today();
void clearAppointments(Date d);
// class declaration
// fine — no definition
// of Date is needed
Of course, pass-by-value is generally a bad idea (see Item 20), but
if you find yourself using it for some reason, there’s still no justification for introducing unnecessary compilation dependencies.
The ability to declare today and clearAppointments without defining
Date may surprise you, but it’s not as curious as it seems. If anybody calls those functions, Date’s definition must have been seen
prior to the call. Why bother to declare functions that nobody
calls, you wonder? Simple. It’s not that nobody calls them, it’s that
not everybody calls them. If you have a library containing dozens
of function declarations, it’s unlikely that every client calls every
function. By moving the onus of providing class definitions from
your header file of function declarations to clients’ files containing
function calls, you eliminate artificial client dependencies on type
definitions they don’t really need.
144A Item 31
■
Chapter 5
Provide separate header files for declarations and definitions.
In order to facilitate adherence to the above guidelines, header
files need to come in pairs: one for declarations, the other for definitions. These files must be kept consistent, of course. If a declaration is changed in one place, it must be changed in both. As a
result, library clients should always #include a declaration file instead of forward-declaring something themselves, and library authors should provide both header files. For example, the Date
client wishing to declare today and clearAppointments shouldn’t
manually forward-declare Date as shown above. Rather, it should
#include the appropriate header of declarations:
#include "datefwd.h"
Date today();
void clearAppointments(Date d);
// header file declaring (but not
// defining) class Date
// as before
The name of the declaration-only header file “datefwd.h” is based
on the header <iosfwd> from the standard C++ library (see
Item 54). <iosfwd> contains declarations of iostream components
whose corresponding definitions are in several different headers,
including <sstream>, <streambuf>, <fstream>, and <iostream>.
<iosfwd> is instructive for another reason, and that’s to make clear
that the advice in this Item applies as well to templates as to nontemplates. Although Item 30 explains that in many build environments, template definitions are typically found in header files,
some build environments allow template definitions to be in nonheader files, so it still makes sense to provide declaration-only
headers for templates. <iosfwd> is one such header.
C++ also offers the export keyword to allow the separation of template declarations from template definitions. Unfortunately, compiler support for export is scanty, and real-world experience with
export is scantier still. As a result, it’s too early to say what role export will play in effective C++ programming.
Classes like Person that employ the pimpl idiom are often called Handle classes. Lest you wonder how such classes actually do anything,
one way is to forward all their function calls to the corresponding
implementation classes and have those classes do the real work. For
example, here’s how two of Person’s member functions could be implemented:
#include "Person.h"
// we’re implementing the Person class,
// so we must #include its class definition
Implementations
Item 31 145A
#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 public
// member functions as Person — their
// interfaces are identical
Person::Person(const std::string& name, const Date& birthday,
const Address& addr)
: pImpl(new PersonImpl(name, birthday, addr))
{}
std::string Person::name() const
{
return pImpl->name();
}
Note how the Person constructor calls the PersonImpl constructor (by
using new — see Item 16) and how Person::name calls PersonImpl::name.
This is important. Making Person a Handle class doesn’t change what
Person does, it just changes the way it does it.
An alternative to the Handle class approach is to make Person a special kind of abstract base class called an Interface class. The purpose
of such a class is to specify an interface for derived classes (see
Item 34). As a result, it typically has no data members, no constructors, a virtual destructor (see Item 7), and a set of pure virtual functions that specify the interface.
Interface classes are akin to Java’s and .NET’s Interfaces, but C++
doesn’t impose the restrictions on Interface classes that Java and
.NET impose on Interfaces. Neither Java nor .NET allow data members
or function implementations in Interfaces, for example, but C++ forbids neither of these things. C++’s greater flexibility can be useful. As
Item 36 explains, the implementation of non-virtual functions should
be the same for all classes in a hierarchy, so it makes sense to implement such functions as part of the Interface class that declares them.
An Interface class for Person could look like this:
class Person {
public:
virtual ~Person();
virtual std::string name() const = 0;
virtual std::string birthDate() const = 0;
virtual std::string address() const = 0;
...
};
146A Item 31
Chapter 5
Clients of this class must program in terms of Person pointers and references, because it’s not possible to instantiate classes containing
pure virtual functions. (It is, however, possible to instantiate classes
derived from Person — see below.) Like clients of Handle classes, clients of Interface classes need not recompile unless the Interface
class’s interface is modified.
Clients of an Interface class must have a way to create new objects.
They typically do it by calling a function that plays the role of the constructor for the derived classes that are actually instantiated. Such
functions are typically called factory functions (see Item 13) or virtual
constructors. They return pointers (preferably smart pointers — see
Item 18) to dynamically allocated objects that support the Interface
class’s interface. Such functions are often declared static inside the
Interface class:
class Person {
public:
...
static std::tr1::shared_ptr<Person>
create(const std::string& name,
const Date& birthday,
const Address& addr);
...
};
// return a tr1::shared_ptr to a new
// Person initialized with the
// given params; see Item 18 for
// why a tr1::shared_ptr is returned
Clients use them like this:
std::string name;
Date dateOfBirth;
Address address;
...
// create an object supporting the Person interface
std::tr1::shared_ptr<Person> pp(Person::create(name, dateOfBirth, address));
...
std::cout << pp->name()
// use the object via the
<< " was born on "
// Person interface
<< pp->birthDate()
<< " and now lives at "
<< pp->address();
...
// the object is automatically
// deleted when pp goes out of
// scope — see Item 13
At some point, of course, concrete classes supporting the Interface
class’s interface must be defined and real constructors must be
called. That all happens behind the scenes inside the files containing
Implementations
Item 31 147A
the implementations of the virtual constructors. For example, the
Interface class Person might have a concrete derived class RealPerson
that provides implementations for the virtual functions it inherits:
class RealPerson: public Person {
public:
RealPerson(const std::string& name, const Date& birthday,
const Address& addr)
: theName(name), theBirthDate(birthday), theAddress(addr)
{}
virtual ~RealPerson() {}
std::string name() const;
// implementations of these
std::string birthDate() const;
// functions are not shown, but
std::string address() const;
// they are easy to imagine
private:
std::string theName;
Date theBirthDate;
Address theAddress;
};
Given RealPerson, it is truly trivial to write Person::create:
std::tr1::shared_ptr<Person> Person::create(const std::string& name,
const Date& birthday,
const Address& addr)
{
return std::tr1::shared_ptr<Person>(new RealPerson( name, birthday,
addr));
}
A more realistic implementation of Person::create would create different
types of derived class objects, depending on e.g., the values of additional function parameters, data read from a file or database, environment variables, etc.
RealPerson demonstrates one of the two most common mechanisms for
implementing an Interface class: it inherits its interface specification
from the Interface class (Person), then it implements the functions in
the interface. A second way to implement an Interface class involves
multiple inheritance, a topic explored in Item 40.
Handle classes and Interface classes decouple interfaces from implementations, thereby reducing compilation dependencies between files.
Cynic that you are, I know you’re waiting for the fine print. “What does
all this hocus-pocus cost me?” you mutter. The answer is the usual
one in computer science: it costs you some speed at runtime, plus
some additional memory per object.
148A Item 31
Chapter 5
In the case of Handle classes, member functions have to go through
the implementation pointer to get to the object’s data. That adds one
level of indirection per access. And you must add the size of this
implementation pointer to the amount of memory required to store
each object. Finally, the implementation pointer has to be initialized
(in the Handle class’s constructors) to point to a dynamically allocated
implementation object, so you incur the overhead inherent in dynamic
memory allocation (and subsequent deallocation) and the possibility
of encountering bad_alloc (out-of-memory) exceptions.
For Interface classes, every function call is virtual, so you pay the cost
of an indirect jump each time you make a function call (see Item 7).
Also, objects derived from the Interface class must contain a virtual
table pointer (again, see Item 7). This pointer may increase the
amount of memory needed to store an object, depending on whether
the Interface class is the exclusive source of virtual functions for the
object.
Finally, neither Handle classes nor Interface classes can get much use
out of inline functions. Item 30 explains why function bodies must
typically be in header files in order to be inlined, but Handle and
Interface classes are specifically designed to hide implementation
details like function bodies.
It would be a serious mistake, however, to dismiss Handle classes and
Interface classes simply because they have a cost associated with
them. So do virtual functions, and you wouldn’t want to forgo those,
would you? (If so, you’re reading the wrong book.) Instead, consider
using these techniques in an evolutionary manner. Use Handle
classes and Interface classes during development to minimize the
impact on clients when implementations change. Replace Handle
classes and Interface classes with concrete classes for production use
when it can be shown that the difference in speed and/or size is significant enough to justify the increased coupling between classes.
Things to Remember
✦
The general idea behind minimizing compilation dependencies is to
depend on declarations instead of definitions. Two approaches
based on this idea are Handle classes and Interface classes.
✦
Library header files should exist in full and declaration-only forms.
This applies regardless of whether templates are involved.
Chapter 6: Inheritance and
Object-Oriented Design
Inheritance and
Object-Oriented Design
Object-oriented programming (OOP) has been the rage for almost two
decades, so it’s likely that you have some experience with the ideas of
inheritance, derivation, and virtual functions. Even if you’ve been programming only in C, you’ve surely not escaped the OOP hoopla.
Inheritance and Object-Oriented Design
Still, OOP in C++ is probably a bit different from what you’re used to.
Inheritance can be single or multiple, and each inheritance link can
be public, protected, or private. Each link can also be virtual or nonvirtual. Then there are the member function options. Virtual? Nonvirtual? Pure virtual? And the interactions with other language
features. How do default parameter values interact with virtual functions? How does inheritance affect C++’s name lookup rules? And
what about design options? If a class’s behavior needs to be modifiable, is a virtual function the best way to do that?
This chapter sorts it all out. Furthermore, I explain what the different
features in C++ really mean — what you are really expressing when
you use a particular construct. For example, public inheritance
means “is-a,” and if you try to make it mean anything else, you’ll run
into trouble. Similarly, a virtual function means “interface must be
inherited,” while a non-virtual function means “both interface and
implementation must be inherited.” Failing to distinguish between
these meanings has caused C++ programmers considerable grief.
If you understand the meanings of C++’s various features, you’ll find
that your outlook on OOP changes. Instead of it being an exercise in
differentiating between language features, it will become a matter of
determining what you want to say about your software system. And
once you know what you want to say, the translation into C++ is not
terribly demanding.
150A Item 32
Chapter 6
Item 32: Make sure public inheritance models “is-a.”
In his book, Some Must Watch While Some Must Sleep (W. H. Freeman
and Company, 1974), William Dement relates the story of his attempt
to fix in the minds of his students the most important lessons of his
course. It is claimed, he told his class, that the average British schoolchild remembers little more history than that the Battle of Hastings
was in 1066. If a child remembers little else, Dement emphasized, he
or she remembers the date 1066. 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 few critical facts even if they
forgot everything else discussed in the course, and he returned to
these fundamental precepts 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 will surely remember for the
rest of your life.” When Dement graded the exams, he was stunned.
Nearly everyone had written “1066.”
It is thus with great trepidation that I proclaim to you now that the
single most important rule in object-oriented programming with C++
is this: public inheritance means “is-a.” Commit this rule to memory.
If you write that class D (“Derived”) publicly inherits from class B
(“Base”), you are telling C++ compilers (as well 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 concept than B. You
are asserting that anywhere an object of type B can be used, an object
of type D can be used just as well, 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 is-a 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 is exactly what this hierarchy
asserts. We expect that anything that is true of a person — for example, that he or she has a date of birth — is also true of a student. We
do not expect that everything that is true of a student — that he or
she is enrolled in a particular school, for instance — is true of people
Inheritance and Object-Oriented Design
Item 32 151A
in general. The notion of a person is more 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 or reference-to-Person) will also take a
Student object (or pointer-to-Student or reference-to-Student):
void eat(const Person& p);
void study(const Student& s);
Person p;
Student s;
eat(p);
eat(s);
study(s);
study(p);
// anyone can eat
// only students study
// p is a Person
// s is a Student
// fine, p is a Person
// fine, s is a Student,
// and a Student is-a Person
// fine
// 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 derived from Person. Private inheritance means something entirely different (see Item 39), and protected
inheritance is something whose meaning eludes me to this day.
The equivalence of public inheritance and is-a sounds simple, but
sometimes your intuition can mislead you. For example, it is a fact
that a penguin is a bird, and it 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();
...
};
class Penguin: public Bird {
...
};
// birds can fly
// penguins are birds
Suddenly we are in trouble, because this hierarchy says that penguins can fly, which we know is not true. What happened?
In this case, we are the victims of an imprecise language: English.
When we say that birds can fly, we don’t mean that all types of birds
can fly, only that, in general, birds have the ability to fly. If we were
more precise, we’d recognize that there are several types of non-flying
152A Item 32
Chapter 6
birds, and we would come up with the following hierarchy, which
models reality much better:
class Bird {
...
};
class FlyingBird: public Bird {
public:
virtual void fly();
...
};
class Penguin: public Bird {
...
};
// no fly function is declared
// no fly function is declared
This hierarchy is much more faithful to what we really know than was
the original design.
Yet we’re not finished with these fowl matters, because for some software systems, there may be no need to distinguish between flying and
non-flying birds. If your application has much to do with beaks and
wings and nothing to do with flying, the original two-class hierarchy
might be quite satisfactory. That’s a simple reflection of the fact that
there is no one ideal design for all software. The best design depends
on what the system is expected to do, both now and in the future. If
your application has no knowledge of flying and isn’t expected to ever
have any, failing to distinguish between flying and non-flying birds
may be a perfectly valid design decision. In fact, it may be preferable
to a design that does distinguish between them, because such a distinction would be absent from the world you are trying to model.
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 runtime error:
void error(const std::string& msg);
// defined elsewhere
class Penguin: public Bird {
public:
virtual void fly() { error("Attempt to make a penguin fly!"); }
...
};
Inheritance and Object-Oriented Design
Item 32 153A
It’s important to recognize that this says something 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 actually try to do
it.”
How can you tell the difference? From the time at which the error is
detected. The injunction, “Penguins can’t fly,” can be enforced by compilers, but violations of the rule, “It’s an error for penguins to actually
try to fly,” can be detected only at runtime.
To express the constraint, “Penguins can’t fly — period,” you make
sure that no such function is defined for Penguin objects:
class Bird {
...
};
class Penguin: public Bird {
...
};
// no fly function is declared
// no fly function is declared
If you now try to make a penguin fly, compilers will reprimand you for
your transgression:
Penguin p;
p.fly();
// error!
This is very different from the behavior you get if you adopt the
approach that generates runtime errors. With that methodology, compilers won’t say a word about the call to p.fly. Item 18 explains that
good interfaces prevent invalid code from compiling, so you should
prefer the design that rejects penguin flight attempts during compilation to the one that detects them only at runtime.
Perhaps you’ll concede that your ornithological intuition may be lacking, but you can rely on your mastery of elementary geometry, right? I
mean, how complicated can rectangles and squares be?
Well, answer this simple question: should class Square publicly inherit
from class Rectangle?
Rectangle
?
Square
154A Item 32
Chapter 6
“Duh!” you say, “Of course it should! Everybody knows that a square
is a rectangle, but generally not vice versa.” True enough, at least in
school. But I don’t think we’re in school anymore.
Consider this code:
class Rectangle {
public:
virtual void setHeight(int newHeight);
virtual void setWidth(int newWidth);
virtual int height() const;
virtual int width() const;
...
};
void makeBigger(Rectangle& r)
{
int oldHeight = r.height();
r.setWidth(r.width() + 10);
assert(r.height() == oldHeight);
}
// return current values
// function to increase r’s area
// add 10 to r’s width
// 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());
makeBigger(s);
assert(s.width() == s.height());
// this must be true for all squares
// by inheritance, s is-a Rectangle,
// so we can increase its area
// this must still be true
// for all squares
It’s just as clear that this second assertion should also never fail. By
definition, the width of a square 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;
Inheritance and Object-Oriented Design
■
Item 32 155A
After returning from makeBigger, s’s height is again the same as its
width. (Note that s is passed to makeBigger 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 of study — including mathematics — may not serve you as well as you expect. The fundamental
difficulty in this case is that something applicable to a rectangle (its
width may be modified independently of its height) is not applicable to
a square (its width and height must be the same). But public inheritance asserts that everything that applies to base class objects —
everything! — also applies to derived class objects. In the case of rectangles and squares (as well as an example involving sets and lists in
Item 38), that assertion fails to hold, so using public inheritance to
model their relationship is simply incorrect. Compilers will let you do
it, but as we’ve just seen, that’s no guarantee the code will behave
properly. As every programmer must learn (some more often than others), just because the code compiles doesn’t mean it will work.
Don’t fret that the software intuition you’ve developed over the years
will fail you as you approach object-oriented design. That knowledge
is still valuable, but now that you’ve added inheritance to your arsenal
of design alternatives, you’ll have to augment your intuition with new
insights to guide you in inheritance’s proper application. In time, the
notion of having Penguin inherit from Bird or Square inherit from Rectangle will give you the same funny feeling you probably get now when
somebody shows you a function several pages long. It’s possibly the
right way to approach things, it’s just not very likely.
The is-a relationship is not the only one that can exist between
classes. Two other common inter-class relationships are “has-a” and
“is-implemented-in-terms-of.” These relationships are considered in
Items 38 and 39. It’s not uncommon for C++ designs to go awry
because one of these other important relationships was incorrectly
modeled as is-a, so you should make sure that you understand the
differences among these relationships and that you know how each is
best modeled in C++.
Things to Remember
✦
Public inheritance means “is-a.” Everything that applies to base
classes must also apply to derived classes, because every derived
class object is a base class object.
156A Item 33
Chapter 6
Item 33: Avoid hiding inherited names.
Shakespeare had a thing about names. “What’s in a name?” he asked,
“A rose by any other name would smell as sweet.” The Bard also
wrote, “he that filches from me my good name ... makes me poor
indeed.” Right. Which brings us to inherited names in C++.
The matter actually has nothing to do with inheritance. It has to do
with scopes. We all know that in code like this,
int x;
void someFunc()
{
double x;
std::cin >> x;
}
// global variable
// local variable
// read a new value for local x
the statement reading into x refers to the local variable x instead of the
global variable x, because names in inner scopes hide (“shadow”)
names in outer scopes. We can visualize the scope situation this way:
Global scope
x
someFunc’s scope
x
When compilers are in someFunc’s scope and they encounter the name
x, they look in the local scope to see if there is something with that
name. Because there is, they never examine any other scope. In this
case, someFunc’s x is of type double and the global x is of type int, but
that doesn’t matter. C++’s name-hiding rules do just that: hide names.
Whether the names correspond to the same or different types is
immaterial. In this case, a double named x hides an int named x.
Enter inheritance. We know that when we’re inside a derived class
member function and we refer to something in a base class (e.g., a
member function, a typedef, or a data member), compilers can find
what we’re referring to because derived classes inherit the things
declared in base classes. The way that actually works is that the scope
of a derived class is nested inside its base class’s scope. For example:
Inheritance and Object-Oriented Design
class Base {
private:
int x;
public:
virtual void mf1() = 0;
virtual void mf2();
void mf3();
...
};
class Derived: public Base {
public:
virtual void mf1();
void mf4();
...
};
Item 33 157A
Base’s scope
x (data member)
mf1 (1 function)
mf2 (1 function)
mf3 (1 function)
Derived’s scope
mf1 (1 function)
mf4 (1 function)
This example includes a mix of public and private names as well as
names of both data members and member functions. The member
functions are pure virtual, simple (impure) virtual, and non-virtual.
That’s to emphasize that we’re talking about names. The example
could also have included names of types, e.g., enums, nested classes,
and typedefs. The only thing that matters in this discussion is that
they’re names. What they’re names of is irrelevant. The example uses
single inheritance, but once you understand what’s happening under
single inheritance, C++’s behavior under multiple inheritance is easy
to anticipate.
Suppose mf4 in the derived class is implemented, in part, like this:
void Derived::mf4()
{
...
mf2();
...
}
When compilers see the use of the name mf2 here, they have to figure
out what it refers to. They do that by searching scopes for a declaration of something named mf2. First they look in the local scope (that of
mf4), but they find no declaration for anything called mf2. They then
search the containing scope, that of the class Derived. They still find
nothing named mf2, so they move on to the next containing scope,
that of the base class. There they find something named mf2, so the
search stops. If there were no mf2 in Base, the search would continue,
158A Item 33
Chapter 6
first to the namespace(s) containing Derived, if any, and finally to the
global scope.
The process I just described is accurate, but it’s not a comprehensive
description of how names are found in C++. Our goal isn’t to know
enough about name lookup to write a compiler, however. It’s to know
enough to avoid unpleasant surprises, and for that task, we already
have plenty of information.
Consider the previous example again, except this time let’s overload
mf1 and mf3, and let’s add a version of mf3 to Derived. (As Item 36
explains, Derived’s declaration of mf3 — an inherited non-virtual function — makes this design instantly suspicious, but in the interest of
understanding name visibility under inheritance, we’ll overlook that.)
class Base {
private:
int x;
public:
virtual void mf1() = 0;
virtual void mf1(int);
virtual void mf2();
void mf3();
void mf3(double);
...
};
class Derived: public Base {
public:
virtual void mf1();
void mf3();
void mf4();
...
};
Base’s scope
x (data member)
mf1 (2 functions)
mf2 (1 function)
mf3 (2 functions)
Derived’s scope
mf1 (1 function)
mf3 (1 function)
mf4 (1 function)
This code leads to behavior that surprises every C++ programmer the
first time they encounter it. The scope-based name hiding rule hasn’t
changed, so all functions named mf1 and mf3 in the base class are
hidden by the functions named mf1 and mf3 in the derived class. From
the perspective of name lookup, Base::mf1 and Base::mf3 are no longer
inherited by Derived!
Derived d;
int x;
...
d.mf1();
d.mf1(x);
// fine, calls Derived::mf1
// error! Derived::mf1 hides Base::mf1
Inheritance and Object-Oriented Design
d.mf2();
d.mf3();
d.mf3(x);
Item 33 159A
// fine, calls Base::mf2
// fine, calls Derived::mf3
// error! Derived::mf3 hides Base::mf3
As you can see, this applies even though the functions in the base and
derived classes take different parameter types, and it also applies
regardless of whether the functions are virtual or non-virtual. In the
same way that, at the beginning of this Item, the double x in the function someFunc hides the int x at global scope, here the function mf3 in
Derived hides a Base function named mf3 that has a different type.
The rationale behind this behavior is that it prevents you from accidentally inheriting overloads from distant base classes when you create a new derived class in a library or application framework.
Unfortunately, you typically want to inherit the overloads. In fact, if
you’re using public inheritance and you don’t inherit the overloads,
you’re violating the is-a relationship between base and derived classes
that Item 32 explains is fundamental to public inheritance. That being
the case, you’ll almost always want to override C++’s default hiding of
inherited names.
You do it with using declarations:
class Base {
Base’s scope
private:
int x;
x (data member)
mf1 (2 functions)
public:
mf2 (1 function)
virtual void mf1() = 0;
mf3 (2 functions)
virtual void mf1(int);
virtual void mf2();
Derived’s scope
void mf3();
mf1 (2 functions)
void mf3(double);
mf3 (2 functions)
...
mf4
(1 function)
};
class Derived: public Base {
public:
using Base::mf1;
// make all things in Base named mf1 and mf3
using Base::mf3;
// visible (and public) in Derived’s scope
virtual void mf1();
void mf3();
void mf4();
...
};
Now inheritance will work as expected:
160A Item 33
Derived d;
int x;
...
d.mf1();
d.mf1(x);
d.mf2();
d.mf3();
d.mf3(x);
Chapter 6
// still fine, still calls Derived::mf1
// now okay, calls Base::mf1
// still fine, still calls Base::mf2
// fine, calls Derived::mf3
// now okay, calls Base::mf3
This means that if you inherit from a base class with overloaded functions and you want to redefine or override only some of them, you need
to include a using declaration for each name you’d otherwise be hiding.
If you don’t, some of the names you’d like to inherit will be hidden.
It’s conceivable that you sometimes won’t want to inherit all the functions from your base classes. Under public inheritance, this should
never be the case, because, again, it violates public inheritance’s is-a
relationship between base and derived classes. (That’s why the using
declarations above are in the public part of the derived class: names
that are public in a base class should also be public in a publicly
derived class.) Under private inheritance (see Item 39), however, it can
make sense. For example, suppose Derived privately inherits from
Base, and the only version of mf1 that Derived wants to inherit is the
one taking no parameters. A using declaration won’t do the trick here,
because a using declaration makes all inherited functions with a given
name visible in the derived class. No, this is a case for a different technique, namely, a simple forwarding function:
class Base {
public:
virtual void mf1() = 0;
virtual void mf1(int);
...
};
class Derived: private Base {
public:
virtual void mf1()
{ Base::mf1(); }
...
};
...
Derived d;
int x;
d.mf1();
d.mf1(x);
// as before
// forwarding function; implicitly
// inline — see Item 30. (For info
// on calling a pure virtual
// function, see Item 34.)
// fine, calls Derived::mf1
// error! Base::mf1() is hidden
Inheritance and Object-Oriented Design
Item 34 161A
Another use for inline forwarding functions is to work around ancient
compilers that (incorrectly) don’t support using declarations to import
inherited names into the scope of a derived class.
That’s the whole story on inheritance and name hiding, but when
inheritance is combined with templates, an entirely different form of
the “inherited names are hidden” issue arises. For all the anglebracket-demarcated details, see Item 43.
Things to Remember
✦
Names in derived classes hide names in base classes. Under public
inheritance, this is never desirable.
✦
To make hidden names visible again, employ using declarations or
forwarding functions.
Item 34: Differentiate between inheritance of
interface and inheritance of implementation.
The seemingly straightforward notion of (public) inheritance turns
out, upon closer examination, to be composed 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 function declarations
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 member function. Sometimes you
want derived classes to inherit both a function’s interface and implementation, but you want to allow them to override the implementation
they inherit. And sometimes you want derived classes to inherit a
function’s 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 representing geometric shapes in a graphics application:
class Shape {
public:
virtual void draw() const = 0;
virtual void error(const std::string& msg);
int objectID() const;
...
};
class Rectangle: public Shape { ... };
class Ellipse: public Shape { ... };
162A Item 34
Chapter 6
Shape is an abstract class; its pure virtual function draw marks it as
such. As a result, clients cannot create instances of the Shape class,
only of classes derived from it. Nonetheless, Shape exerts a strong
influence on all classes that (publicly) inherit from it, because
■
Member function interfaces are always inherited. As explained in
Item 32, public inheritance means is-a, so anything that is true of
a base class must also be true of its derived classes. Hence, if a
function applies to a class, it must also apply to its derived
classes.
Three functions are declared in the Shape class. The first, draw, draws
the current object on an implicit display. The second, error, is called
when an error needs to be reported. The third, objectID, returns a
unique integer identifier for the current object. Each function is
declared in a different way: draw is a pure virtual function; error is a
simple (impure?) virtual function; and objectID is a non-virtual function. What are the implications of these different declarations?
Consider first the pure virtual function draw:
class Shape {
public:
virtual void draw() const = 0;
...
};
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 abstract classes. Put these two characteristics together, and you realize that
■
The purpose of declaring a pure virtual function is to have derived
classes inherit a function interface only.
This makes perfect sense for the Shape::draw function, because it is a
reasonable demand that all Shape objects must be drawable, but the
Shape class can provide no reasonable default implementation for that
function. The algorithm for drawing an ellipse is very different from
the algorithm for drawing a rectangle, for example. The declaration of
Shape::draw says to designers of concrete derived classes, “You must
provide a draw function, but I have no idea how you’re going to implement it.”
Incidentally, it is possible to provide a definition for a pure virtual
function. That is, you could provide an implementation for Shape::draw,
and C++ wouldn’t complain, but the only way to call it would be to
qualify the call with the class name:
Inheritance and Object-Oriented Design
Shape *ps = new Shape;
Shape *ps1 = new Rectangle;
ps1->draw();
Shape *ps2 = new Ellipse;
ps2->draw();
ps1->Shape::draw();
ps2->Shape::draw();
Item 34 163A
// error! Shape is abstract
// fine
// calls Rectangle::draw
// fine
// calls Ellipse::draw
// calls Shape::draw
// calls Shape::draw
Aside from helping you impress fellow programmers at cocktail parties, knowledge of this feature is generally of limited utility. As you’ll
see below, however, it can be employed as a mechanism for providing
a safer-than-usual default implementation for simple (impure) virtual
functions.
The story behind simple virtual functions is a bit different from that
behind pure virtuals. As usual, derived classes inherit the interface of
the function, but simple virtual functions provide an implementation
that derived classes may 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 interface as well as a default implementation.
Consider the case of Shape::error:
class Shape {
public:
virtual void error(const std::string& msg);
...
};
The interface says that every class must support a function to be
called when an error is encountered, but each class is free to handle
errors in whatever way it sees fit. If a class doesn’t want to do anything special, it can just fall back on the default error handling provided in the Shape class. That is, the declaration of Shape::error says to
designers of derived classes, “You’ve got to support an error function,
but if you don’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 interface and a default implementation. To
see why, consider a hierarchy of airplanes for XYZ Airlines. XYZ has
only two kinds of planes, the Model A and the Model B, and both are
flown in exactly the same way. Hence, XYZ designs the following hierarchy:
164A Item 34
Chapter 6
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
}
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 of plane 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 is provided 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), so the common feature is moved
into a base class, and the feature is inherited by the two classes. This
design makes common features explicit, avoids code duplication, facilitates future enhancements, and eases long-term maintenance — all
the things for which object-oriented technology is so highly touted.
XYZ Airlines should be proud.
Now suppose that XYZ, its fortunes on the rise, decides to acquire a
new type of airplane, the Model C. The Model C differs in some ways
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 into service, 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 PDX(...);
Airplane *pa = new ModelC;
...
pa->fly(PDX);
// PDX is the airport near my home
// calls Airplane::fly!
Inheritance and Object-Oriented Design
Item 34 165A
This is a disaster: an attempt is being made to fly a ModelC object as if
it were a ModelA or a ModelB. That’s not 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 that behavior without explicitly saying
that it wanted to. Fortunately, it’s easy to offer default behavior to
derived classes but not give it to them unless they ask for it. The trick
is to sever the connection between the interface of the virtual 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 the given destination
}
Notice how Airplane::fly has been turned into a pure virtual function.
That provides the interface for flying. The default 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 to defaultFly
inside their body of fly (but see Item 30 for information on the interaction of inlining and virtual functions):
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); }
...
};
166A Item 34
Chapter 6
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 more reliable than the original
design. As for Airplane::defaultFly, it’s protected because it’s truly an
implementation detail of Airplane and its derived classes. Clients using
airplanes should care only that they can be flown, not how the flying
is implemented.
It’s also important that Airplane::defaultFly is a non-virtual function.
This is because no derived class should redefine this function, a truth
to which Item 36 is devoted. If defaultFly were virtual, you’d have a circular problem: what if some derived class 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 of closely related function names. Yet they still agree that
interface and default implementation should be separated. How do
they resolve this seeming contradiction? By taking advantage of the
fact that pure virtual functions must be redeclared in concrete derived
classes, but they may also have implementations of their own. Here’s
how the Airplane hierarchy could take advantage of the ability to define
a pure virtual function:
class Airplane {
public:
virtual void fly(const Airport& destination) = 0;
...
};
Inheritance and Object-Oriented Design
void Airplane::fly(const Airport& destination)
{
Item 34 167A
// an implementation of
// a pure virtual function
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::fly takes the place of the independent function Airplane::defaultFly. In essence, fly has been broken into
its two fundamental components. Its declaration specifies its interface
(which derived classes must use), while its definition specifies its
default behavior (which derived classes may use, but only if they
explicitly request it). In merging 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 non-virtual function, objectID:
class Shape {
public:
int objectID() const;
...
};
168A Item 34
Chapter 6
When a member function is non-virtual, it’s not supposed to behave
differently in derived classes. In fact, a non-virtual member function
specifies an invariant over specialization, because it identifies behavior
that is not supposed to change, no matter how specialized a derived
class becomes. As such,
■
The purpose of declaring a non-virtual function is to have derived
classes inherit a function interface as well as a mandatory implementation.
You can think of the declaration for Shape::objectID as saying, “Every
Shape object has a function that yields an object identifier, and that
object identifier is always computed the same way. That way is determined by the definition of Shape::objectID, and no derived class should
try to change how it’s done.” Because a non-virtual function identifies
an invariant over specialization, it should never be redefined in a
derived class, a point that is discussed in detail in Item 36.
The differences in declarations for pure virtual, simple virtual, and
non-virtual functions allow you to specify with 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 mean fundamentally 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 non-virtual. That leaves no
room for specialization in derived classes; non-virtual destructors are
particularly problematic (see Item 7). Of course, it’s perfectly reasonable to design a class that is not intended to be used as a base class.
In that case, a set of exclusively non-virtual member functions is
appropriate. Too often, however, such classes are declared either out
of ignorance of the differences between virtual and non-virtual functions or as a result of an unsubstantiated concern over the performance cost of virtual functions. The fact of the matter is that almost
any class that’s to be used as a base class will have virtual functions
(again, see Item 7).
If you’re concerned about the cost of virtual functions, allow me to
bring up the empirically-based rule of 80-20 (see also Item 30), which
states that in a typical program, 80% of the runtime will be spent executing just 20% of the code. This rule is important, because it means
that, on average, 80% of your function calls can be virtual without
having the slightest detectable impact on your program’s overall performance. Before you go gray worrying about whether you can afford
Inheritance and Object-Oriented Design
Item 35 169A
the cost of a virtual function, take the simple precaution of making
sure that you’re focusing on the 20% 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 Item 31’s Interface
classes. However, it can also be a sign of a class designer who lacks
the backbone to take a stand. Some functions should not be redefinable in derived classes, and whenever that’s the case, you’ve got to say
so by making those functions non-virtual. It serves no one to pretend
that your class can be all things to all people if they’ll just take the
time to redefine all your functions. If you have an invariant over specialization, don’t be afraid to say so!
Things to Remember
✦
Inheritance of interface is different from inheritance of implementation. Under public inheritance, derived classes always inherit base
class interfaces.
✦
Pure virtual functions specify inheritance of interface only.
✦
Simple (impure) virtual functions specify inheritance of interface
plus inheritance of a default implementation.
✦
Non-virtual functions specify inheritance of interface plus inheritance of a mandatory implementation.
Item 35: Consider alternatives to virtual functions.
So you’re working on a video game, and you’re designing a hierarchy
for characters in the game. Your game being of the slash-and-burn
variety, it’s not uncommon for characters to be injured or otherwise in
a reduced state of health. You therefore decide to offer a member function, healthValue, that returns an integer indicating how healthy the
character is. Because different characters may calculate their health
in different ways, declaring healthValue virtual seems the obvious way
to design things:
class GameCharacter {
public:
virtual int healthValue() const;
...
};
// return character’s health rating;
// derived classes may redefine this
The fact that healthValue isn’t declared pure virtual suggests that there
is a default algorithm for calculating health (see Item 34).
170A Item 35
Chapter 6
This is, indeed, the obvious way to design things, and in some sense,
that’s its weakness. Because this design is so obvious, you may not
give adequate consideration to its alternatives. In the interest of helping you escape the ruts in the road of object-oriented design, let’s consider some other ways to approach this problem.
The Template Method Pattern via the Non-Virtual Interface Idiom
We’ll begin with an interesting school of thought that argues that virtual functions should almost always be private. Adherents to this
school would suggest that a better design would retain healthValue as
a public member function but make it non-virtual and have it call a
private virtual function to do the real work, say, doHealthValue:
class GameCharacter {
public:
int healthValue() const
{
...
int retVal = doHealthValue();
...
return retVal;
}
...
private:
virtual int doHealthValue() const
{
...
}
};
// derived classes do not redefine
// this — see Item 36
// do “before” stuff — see below
// do the real work
// do “after” stuff — see below
// derived classes may redefine this
// default algorithm for calculating
// character’s health
In this code (and for the rest of this Item), I’m showing the bodies of
member functions in class definitions. As Item 30 explains, that
implicitly declares them inline. I’m showing the code this way only to
make it easier to see what is going on. The designs I’m describing are
independent of inlining decisions, so don’t think it’s meaningful that
the member functions are defined inside classes. It’s not.
This basic design — having clients call private virtual functions indirectly through public non-virtual member functions — is known as
the non-virtual interface (NVI) idiom. It’s a particular manifestation of
the more general design pattern called Template Method (a pattern
that, unfortunately, has nothing to do with C++ templates). I call the
non-virtual function (e.g., healthValue) the virtual function’s wrapper.
Inheritance and Object-Oriented Design
Item 35 171A
An advantage of the NVI idiom is suggested by the “do ‘before’ stuff”
and “do ‘after’ stuff” comments in the code. Those comments identify
code segments guaranteed to be called before and after the virtual
function that does the real work. This means that the wrapper
ensures that before a virtual function is called, the proper context is
set up, and after the call is over, the context is cleaned up. For example, the “before” stuff could include locking a mutex, making a log
entry, verifying that class invariants and function preconditions are
satisfied, etc. The “after” stuff could include unlocking a mutex, verifying function postconditions, reverifying class invariants, etc. There’s
not really any good way to do that if you let clients call virtual functions directly.
It may have crossed your mind that the NVI idiom involves derived
classes redefining private virtual functions — redefining functions
they can’t call! There’s no design contradiction here. Redefining a virtual function specifies how something is to be done. Calling a virtual
function specifies when it will be done. These concerns are independent. The NVI idiom allows derived classes to redefine a virtual function, thus giving them control over how functionality is implemented,
but the base class reserves for itself the right to say when the function
will be called. It may seem odd at first, but C++’s rule that derived
classes may redefine private inherited virtual functions is perfectly
sensible.
Under the NVI idiom, it’s not strictly necessary that the virtual functions be private. In some class hierarchies, derived class implementations of a virtual function are expected to invoke their base class
counterparts (e.g., the example on page 120), and for such calls to be
legal, the virtuals must be protected, not private. Sometimes a virtual
function even has to be public (e.g., destructors in polymorphic base
classes — see Item 7), but then the NVI idiom can’t really be applied.
The Strategy Pattern via Function Pointers
The NVI idiom is an interesting alternative to public virtual functions,
but from a design point of view, it’s little more than window dressing.
After all, we’re still using virtual functions to calculate each character’s health. A more dramatic design assertion would be to say that
calculating a character’s health is independent of the character’s type
— that such calculations need not be part of the character at all. For
example, we could require that each character’s constructor be
passed a pointer to a health calculation function, and we could call
that function to do the actual calculation:
172A Item 35
Chapter 6
class GameCharacter;
// forward declaration
// function for the default health calculation algorithm
int defaultHealthCalc(const GameCharacter& gc);
class GameCharacter {
public:
typedef int (*HealthCalcFunc)(const GameCharacter&);
explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc)
: healthFunc(hcf )
{}
int healthValue() const
{ return healthFunc(*this); }
...
private:
HealthCalcFunc healthFunc;
};
This approach is a simple application of another common design pattern, Strategy. Compared to approaches based on virtual functions in
the GameCharacter hierarchy, it offers some interesting flexibility:
■
Different instances of the same character type can have different
health calculation functions. For example:
class EvilBadGuy: public GameCharacter {
public:
explicit EvilBadGuy(HealthCalcFunc hcf = defaultHealthCalc)
: GameCharacter(hcf )
{ ... }
...
};
int loseHealthQuickly(const GameCharacter&); // health calculation
int loseHealthSlowly(const GameCharacter&);
// funcs with different
// behavior
EvilBadGuy ebg1(loseHealthQuickly);
// same-type characEvilBadGuy ebg2(loseHealthSlowly);
// ters with different
// health-related
// behavior
■
Health calculation functions for a particular character may be
changed at runtime. For example, GameCharacter might offer a
member function, setHealthCalculator, that allowed replacement of
the current health calculation function.
On the other hand, the fact that the health calculation function is no
longer a member function of the GameCharacter hierarchy means that
it has no special access to the internal parts of the object whose
Inheritance and Object-Oriented Design
Item 35 173A
health it’s calculating. For example, defaultHealthCalc has no access to
the non-public parts of EvilBadGuy. If a character’s health can be calculated based purely on information available through the character’s
public interface, this is not a problem, but if accurate health calculation requires non-public information, it is. In fact, it’s a potential
issue anytime you replace functionality inside a class (e.g., via a member function) with equivalent functionality outside the class (e.g., via a
non-member non-friend function or via a non-friend member function
of another class). This issue will persist for the remainder of this Item,
because all the other design alternatives we’re going to consider
involve the use of functions outside the GameCharacter hierarchy.
As a general rule, the only way to resolve the need for non-member
functions to have access to non-public parts of a class is to weaken
the class’s encapsulation. For example, the class might declare the
non-member functions to be friends, or it might offer public accessor
functions for parts of its implementation it would otherwise prefer to
keep hidden. Whether the advantages of using a function pointer
instead of a virtual function (e.g., the ability to have per-object health
calculation functions and the ability to change such functions at runtime) offset the possible need to decrease GameCharacter’s encapsulation is something you must decide on a design-by-design basis.
The Strategy Pattern via tr1::function
Once you accustom yourself to templates and their use of implicit
interfaces (see Item 41), the function-pointer-based approach looks
rather rigid. Why must the health calculator be a function instead of
simply something that acts like a function (e.g., a function object)? If it
must be a function, why can’t it be a member function? And why must
it return an int instead of any type convertible to an int?
These constraints evaporate if we replace the use of a function pointer
(such as healthFunc) with an object of type tr1::function. As Item 54
explains, such objects may hold any callable entity (i.e., function
pointer, function object, or member function pointer) whose signature
is compatible with what is expected. Here’s the design we just saw,
this time using tr1::function:
class GameCharacter;
// as before
int defaultHealthCalc(const GameCharacter& gc);
// as before
class GameCharacter {
public:
// HealthCalcFunc is any callable entity that can be called with
// anything compatible with a GameCharacter and that returns anything
// compatible with an int; see below for details
typedef std::tr1::function<int (const GameCharacter&)> HealthCalcFunc;
174A Item 35
Chapter 6
explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc)
: healthFunc(hcf )
{}
int healthValue() const
{ return healthFunc(*this); }
...
private:
HealthCalcFunc healthFunc;
};
As you can see, HealthCalcFunc is a typedef for a tr1::function instantiation. That means it acts like a generalized function pointer type. Look
closely at what HealthCalcFunc is a typedef for:
std::tr1::function<int (const GameCharacter&)>
Here I’ve highlighted the “target signature” of this tr1::function instantiation. That target signature is “function taking a const GameCharacter&
and returning an int.” An object of this tr1::function type (i.e., of type
HealthCalcFunc) may hold any callable entity compatible with the target
signature. To be compatible means that const GameCharacter& either is
or can be converted to the type of the entity’s parameter, and the
entity’s return type either is or can be implicitly converted to int.
Compared to the last design we saw (where GameCharacter held a
pointer to a function), this design is almost the same. The only difference is that GameCharacter now holds a tr1::function object — a generalized pointer to a function. This change is so small, I’d call it
inconsequential, except that a consequence is that clients now have
staggeringly more flexibility in specifying health calculation functions:
short calcHealth(const GameCharacter&);
struct HealthCalculator {
int operator()(const GameCharacter&) const
{ ... }
};
class GameLevel {
public:
float health(const GameCharacter&) const;
...
};
class EvilBadGuy: public GameCharacter {
...
};
// health calculation
// function; note
// non-int return type
// class for health
// calculation function
// objects
// health calculation
// mem function; note
// non-int return type
// as before
Inheritance and Object-Oriented Design
Item 35 175A
class EyeCandyCharacter: public GameCharacter { // another character
...
// type; assume same
};
// constructor as
// EvilBadGuy
EvilBadGuy ebg1(calcHealth);
EyeCandyCharacter ecc1(HealthCalculator());
GameLevel currentLevel;
...
EvilBadGuy ebg2(
std::tr1::bind(&GameLevel::health,
currentLevel,
_1)
);
// character using a
// health calculation
// function
// character using a
// health calculation
// function object
// character using a
// health calculation
// member function;
// see below for details
Personally, I find what tr1::function lets you do so amazing, it makes
me tingle all over. If you’re not tingling, it may be because you’re staring at the definition of ebg2 and wondering what’s going on with the
call to tr1::bind. Kindly allow me to explain.
We want to say that to calculate ebg2’s health rating, the health member function in the GameLevel class should be used. Now, GameLevel::health is a function that is declared to take one parameter (a
reference to a GameCharacter), but it really takes two, because it also
gets an implicit GameLevel parameter — the one this points to. Health
calculation functions for GameCharacters, however, take a single
parameter: the GameCharacter whose health is to be calculated. If we’re
to use GameLevel::health for ebg2’s health calculation, we have to somehow “adapt” it so that instead of taking two parameters (a GameCharacter and a GameLevel), it takes only one (a GameCharacter). In this
example, we always want to use currentLevel as the GameLevel object
for ebg2’s health calculation, so we “bind” currentLevel as the GameLevel object to be used each time GameLevel::health is called to calculate ebg2’s health. That’s what the tr1::bind call does: it specifies that
ebg2’s health calculation function should always use currentLevel as
the GameLevel object.
I’m skipping over a host of details regarding the call to tr1::bind,
because such details wouldn’t be terribly illuminating, and they’d distract from the fundamental point I want to make: by using tr1::function
instead of a function pointer, we’re allowing clients to use any compatible callable entity when calculating a character’s health. Is that cool
or what?
176A Item 35
Chapter 6
The “Classic” Strategy Pattern
If you’re more into design patterns than C++ coolness, a more conventional approach to Strategy would be to make the health-calculation
function a virtual member function of a separate health-calculation
hierarchy. The resulting hierarchy design would look like this:
GameCharacter
HealthCalcFunc
EvilBadGuy
SlowHealthLoser
EyeCandyCharacter
FastHealthLoser
...
...
If you’re not up on your UML notation, this just says that GameCharacter is the root of an inheritance hierarchy where EvilBadGuy and EyeCandyCharacter are derived classes; HealthCalcFunc is the root of an
inheritance hierarchy with derived classes SlowHealthLoser and
FastHealthLoser; and each object of type GameCharacter contains a
pointer to an object from the HealthCalcFunc hierarchy.
Here’s the corresponding code skeleton:
class GameCharacter;
// forward declaration
class HealthCalcFunc {
public:
...
virtual int calc(const GameCharacter& gc) const
{ ... }
...
};
HealthCalcFunc defaultHealthCalc;
class GameCharacter {
public:
explicit GameCharacter(HealthCalcFunc *phcf = &defaultHealthCalc)
: pHealthCalc(phcf )
{}
int healthValue() const
{ return pHealthCalc->calc(*this); }
...
private:
HealthCalcFunc *pHealthCalc;
};
Inheritance and Object-Oriented Design
Item 35 177A
This approach has the appeal of being quickly recognizable to people
familiar with the “standard” Strategy pattern implementation, plus it
offers the possibility that an existing health calculation algorithm can
be tweaked by adding a derived class to the HealthCalcFunc hierarchy.
Summary
The fundamental advice of this Item is to consider alternatives to virtual functions when searching for a design for the problem you’re trying to solve. Here’s a quick recap of the alternatives we examined:
■
Use the non-virtual interface idiom (NVI idiom), a form of the
Template Method design pattern that wraps public non-virtual
member functions around less accessible virtual functions.
■
Replace virtual functions with function pointer data members, a
stripped-down manifestation of the Strategy design pattern.
■
Replace virtual functions with tr1::function data members, thus
allowing use of any callable entity with a signature compatible with
what you need. This, too, is a form of the Strategy design pattern.
■
Replace virtual functions in one hierarchy with virtual functions
in another hierarchy. This is the conventional implementation of
the Strategy design pattern.
This isn’t an exhaustive list of design alternatives to virtual functions,
but it should be enough to convince you that there are alternatives.
Furthermore, their comparative advantages and disadvantages should
make clear that you should consider them.
To avoid getting stuck in the ruts of the road of object-oriented design,
give the wheel a good jerk from time to time. There are lots of other
roads. It’s worth taking the time to investigate them.
Things to Remember
✦
Alternatives to virtual functions include the NVI idiom and various
forms of the Strategy design pattern. The NVI idiom is itself an example of the Template Method design pattern.
✦
A disadvantage of moving functionality from a member function to a
function outside the class is that the non-member function lacks access to the class’s non-public members.
✦
tr1::function objects act like generalized function pointers. Such objects support all callable entities compatible with a given target signature.
178A Item 36
Chapter 6
Item 36: Never redefine an inherited non-virtual
function.
Suppose I tell you that a class D is publicly derived from a class B and
that there is a public member function mf defined 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;
pB->mf();
// get pointer to x
// call mf through pointer
behaved differently from this:
D *pD = &x;
pD->mf();
// get pointer to x
// 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 same function 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 of mf:
class D: public B {
public:
void mf();
...
};
pB->mf();
pD->mf();
// hides B::mf; see Item 33
// calls B::mf
// calls D::mf
The reason for this two-faced behavior is that non-virtual functions
like B::mf and D::mf are statically bound (see Item 37). That means that
because pB is declared to be of type pointer-to-B, non-virtual functions invoked through pB will always be those defined for class B, even
Inheritance and Object-Oriented Design
Item 36 179A
if pB points to an object of a class derived from B, as it does in this
example.
Virtual functions, on the other hand, are dynamically bound (again,
see Item 37), so they don’t suffer from this problem. If mf were a virtual function, a call to mf through either pB or pD would result in an
invocation of D::mf, because what pB and pD really point to is an object
of type D.
If you are writing class D and you redefine a non-virtual function mf
that you inherit from class B, D objects will likely exhibit inconsistent
behavior. In particular, any given D object may act like either a B or a
D when mf is called, and the determining factor will have nothing to do
with the object itself, but with the declared type of the pointer that
points to it. References exhibit the same baffling behavior as do pointers.
But that’s just a pragmatic argument. What you really want, I know,
is some kind of theoretical justification for not redefining inherited
non-virtual functions. I am pleased to oblige.
Item 32 explains that public inheritance means is-a, and Item 34
describes why declaring a non-virtual function in a class establishes
an invariant over specialization for that class. If you apply these
observations to the classes B and D and to the non-virtual member
function B::mf, then
■
Everything that applies to B objects also applies to D objects, because every D object is-a B object;
■
Classes derived from B must inherit both the interface and the implementation of mf, because mf is non-virtual in B.
Now, if D redefines mf, there is a contradiction in your design. If D
really needs to implement mf differently from B, and if every B object —
no matter how specialized — really has to use the B implementation
for mf, then it’s simply not true that every D is-a 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 not true that mf reflects an invariant over
specialization for B. In that case, mf should be virtual. Finally, if every
D really is-a B, and if mf really corresponds to an invariant over specialization for B, then D can’t honestly need to redefine mf, and it
shouldn’t try to.
Regardless of which argument applies, something has to give, and
under no conditions is it the prohibition on redefining an inherited
non-virtual function.
180A Item 37
Chapter 6
If reading this Item gives you a sense of déjà vu, it’s probably because
you’ve already read Item 7, which explains why destructors in polymorphic base classes should be virtual. If you violate that guideline
(i.e., if you declare a non-virtual destructor in a polymorphic base
class), you’ll also be violating this guideline, because derived classes
would invariably redefine an inherited non-virtual function: the base
class’s destructor. This would be true even for derived classes that
declare no destructor, because, as Item 5 explains, the destructor is
one of the member functions that compilers generate for you if you
don’t declare one yourself. In essence, Item 7 is nothing more than a
special case of this Item, though it’s important enough to merit calling
out on its own.
Things to Remember
✦
Never redefine an inherited non-virtual function.
Item 37: Never redefine a function’s inherited default
parameter value.
Let’s simplify this discussion right from the start. There are only two
kinds of functions you can inherit: virtual and non-virtual. However,
it’s always a mistake to redefine an inherited non-virtual function (see
Item 36), so we can safely limit our discussion here to the situation in
which you inherit a virtual function with a default parameter value.
That being the case, the justification for this Item becomes quite
straightforward: virtual functions are dynamically bound, but default
parameter values are statically bound.
What’s that? You say the difference between static and dynamic binding has slipped your already overburdened mind? (For the record,
static binding is also known as early binding, and dynamic binding is
also known as late binding.) 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:
// a class for geometric shapes
class Shape {
public:
enum ShapeColor { Red, Green, Blue };
// all shapes must offer a function to draw themselves
virtual void draw(ShapeColor color = Red) const = 0;
...
};
Inheritance and Object-Oriented Design
Item 37 181A
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:
Shape
Rectangle
Circle
Now consider these pointers:
Shape *ps;
Shape *pc = new Circle;
Shape *pr = new Rectangle;
// static type = Shape*
// static type = Shape*
// static type = Shape*
In this example, ps, pc, and pr are all declared to be of type pointer-toShape, so they all have that as their static type. 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 dynamic type indicates how it will
behave. In the example above, pc’s dynamic type is Circle*, and pr’s
dynamic type is Rectangle*. 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 = pr;
// ps’s dynamic type is
// now Circle*
// ps’s dynamic type is
// now Rectangle*
Virtual functions are dynamically bound, meaning that the particular
function called is determined by the dynamic type of the object
through which it’s invoked:
182A Item 37
pc->draw(Shape::Red);
pr->draw(Shape::Red);
Chapter 6
// calls Circle::draw(Shape::Red)
// calls Rectangle::draw(Shape::Red)
This is all old hat, I know; you surely understand virtual functions.
The twist comes in when you consider virtual functions with default
parameter values, because, as I said above, virtual functions are
dynamically bound, but default parameters are statically bound. That
means you may end up invoking a virtual function defined in a
derived class but using a default parameter value from a base class:
pr->draw();
// calls Rectangle::draw(Shape::Red)!
In this case, pr’s dynamic type is Rectangle*, so the Rectangle virtual
function is called, just as you would expect. 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! The result is a call consisting of a strange and almost certainly unanticipated combination of
the declarations for draw in both the Shape and Rectangle classes.
The fact that ps, pc, and pr are pointers is of no consequence in this
matter. Were they references, the problem would persist. The only
important things are that draw is a virtual function, and one of its
default parameter values is redefined in a derived class.
Why does C++ insist on acting in this perverse manner? The answer
has to do with runtime efficiency. If default parameter values were
dynamically bound, compilers would have to come up with a way to
determine the appropriate default value(s) for parameters of virtual
functions at runtime, which would be slower and more complicated
than the current mechanism of determining them during compilation.
The decision was made to err on the side of speed and simplicity of
implementation, and the result is that you now enjoy execution
behavior that is efficient, but, if you fail to heed the advice of this
Item, confusing.
That’s all well and good, but look what happens if you try to follow
this rule and also offer default parameter values to users of both base
and derived classes:
class Shape {
public:
enum ShapeColor { Red, Green, Blue };
virtual void draw(ShapeColor color = Red) const = 0;
...
};
Inheritance and Object-Oriented Design
Item 37 183A
class Rectangle: public Shape {
public:
virtual void draw(ShapeColor color = Red) const;
...
};
Uh oh, code duplication. Worse yet, code duplication with dependencies: if the default parameter value is changed in Shape, all derived
classes that repeat it must also be changed. Otherwise they’ll end up
redefining an inherited default parameter value. What to do?
When you’re having trouble making a virtual function behave the way
you’d like, it’s wise to consider alternative designs, and Item 35 is
filled with alternatives to virtual functions. One of the alternatives is
the non-virtual interface idiom (NVI idiom): having a public non-virtual
function in a base class call a private virtual function that derived
classes may redefine. Here, we have the non-virtual function specify
the default parameter, while the virtual function does the actual work:
class Shape {
public:
enum ShapeColor { Red, Green, Blue };
void draw(ShapeColor color = Red) const
{
doDraw(color);
}
...
private:
virtual void doDraw(ShapeColor color) const = 0;
};
class Rectangle: public Shape {
public:
...
private:
virtual void doDraw(ShapeColor color) const;
...
};
// now non-virtual
// calls a virtual
// the actual work is
// done in this func
// note lack of a
// default param val.
Because non-virtual functions should never be redefined by derived
classes (see Item 36), this design makes clear that the default value
for draw’s color parameter should always be Red.
Things to Remember
✦
Never redefine an inherited default parameter value, because default
parameter values are statically bound, while virtual functions — the
only functions you should be redefining — are dynamically bound.
184A Item 38
Chapter 6
Item 38: Model “has-a” or “is-implemented-in-termsof” through composition.
Composition is the relationship between types that arises when objects
of one type contain objects of another type. For example:
class Address { ... };
class PhoneNumber { ... };
class Person {
public:
...
private:
std::string name;
Address address;
PhoneNumber voiceNumber;
PhoneNumber faxNumber;
};
// where someone lives
// composed object
// ditto
// ditto
// ditto
In this example, Person objects are composed of string, Address, and
PhoneNumber objects. Among programmers, the term composition has
lots of synonyms. It’s also known as layering, containment, aggregation, and embedding.
Item 32 explains that public inheritance means “is-a.” Composition
has a meaning, too. Actually, it has two meanings. Composition
means either “has-a” or “is-implemented-in-terms-of.” That’s because
you are dealing with two different domains in your software. Some
objects in your programs correspond to things in the world you are
modeling, e.g., people, vehicles, video frames, etc. Such objects are
part of the application domain. Other objects are purely implementation artifacts, e.g., buffers, mutexes, search trees, etc. These kinds of
objects correspond to your software’s implementation domain. When
composition occurs between objects in the application domain, it
expresses a has-a relationship. When it occurs in the implementation
domain, it expresses an is-implemented-in-terms-of relationship.
The Person class above demonstrates the has-a relationship. A Person
object has a name, an address, and voice and fax telephone numbers.
You wouldn’t say that a person is a name or that a person is an
address. You would say that a person has a name and has an
address. Most people have little difficulty with this distinction, so confusion between the roles of is-a and has-a is relatively rare.
Somewhat more troublesome is the difference between is-a and isimplemented-in-terms-of. For example, suppose you need a template
for classes representing fairly small sets of objects, i.e., collections
without duplicates. Because reuse is a wonderful thing, your first
Inheritance and Object-Oriented Design
Item 38 185A
instinct is to employ the standard library’s set template. Why write a
new template when you can use one that’s already been written?
Unfortunately, set implementations typically incur an overhead of
three pointers per element. This is because sets are usually implemented as balanced search trees, something that allows them to guarantee logarithmic-time lookups, insertions, and erasures. When speed
is more important than space, this is a reasonable design, but it turns
out that for your application, space is more important than speed. The
standard library’s set thus offers the wrong trade-off for you. It seems
you’ll need to write your own template after all.
Still, reuse is a wonderful thing. Being the data structure maven you
are, you know that of the many choices for implementing sets, one is
to use linked lists. You also know that the standard C++ library has a
list template, so you decide to (re)use it.
In particular, you decide to have your nascent Set template inherit
from list. That is, Set<T> will inherit from list<T>. After all, in your
implementation, a Set object will in fact be a list object. You thus
declare your Set template like this:
template<typename T>
class Set: public std::list<T> { ... };
// the wrong way to use list for Set
Everything may seem fine at this point, but in fact there is something
quite wrong. As Item 32 explains, if D is-a B, everything true of B is
also true of D. However, a list object may contain duplicates, so if the
value 3051 is inserted into a list<int> twice, that list will contain two
copies of 3051. In contrast, a Set may not contain duplicates, so if the
value 3051 is inserted into a Set<int> twice, the set contains only one
copy of the value. It is thus untrue that a Set is-a list, because some of
the things that are true for list objects are not true for Set objects.
Because the relationship between these two classes isn’t is-a, public
inheritance is the wrong way to model that relationship. The right way
is to realize that a Set object can be implemented in terms of a list
object:
template<class T>
// the right way to use list for Set
class Set {
public:
bool member(const T& item) const;
void insert(const T& item);
void remove(const T& item);
std::size_t size() const;
private:
std::list<T> rep;
// representation for Set data
};
186A Item 38
Chapter 6
Set’s member functions can lean heavily on functionality already
offered by list and other parts of the standard library, so the implementation is straightforward, as long as you’re familiar with the
basics of programming with the STL:
template<typename T>
bool Set<T>::member(const T& item) const
{
return std::find(rep.begin(), rep.end(), item) != rep.end();
}
template<typename T>
void Set<T>::insert(const T& item)
{
if (!member(item)) rep.push_back(item);
}
template<typename T>
void Set<T>::remove(const T& item)
{
typename std::list<T>::iterator it =
// see Item 42 for info on
std::find(rep.begin(), rep.end(), item);
// “typename” here
if (it != rep.end()) rep.erase(it);
}
template<typename T>
std::size_t Set<T>::size() const
{
return rep.size();
}
These functions are simple enough that they make reasonable candidates for inlining, though I know you’d want to review the discussion
in Item 30 before making any firm inlining decisions.
One can argue that Set’s interface would be more in accord with
Item 18’s admonition to design interfaces that are easy to use correctly and hard to use incorrectly if it followed the STL container conventions, but following those conventions here would require adding a
lot of stuff to Set that would obscure the relationship between it and
list. Since that relationship is the point of this Item, we’ll trade STL
compatibility for pedagogical clarity. Besides, nits about Set’s interface
shouldn’t overshadow what’s indisputably right about Set: the relationship between it and list. That relationship is not is-a (though it initially looked like it might be), it’s is-implemented-in-terms-of.
Things to Remember
✦
Composition has meanings completely different from that of public
inheritance.
✦
In the application domain, composition means has-a. In the implementation domain, it means is-implemented-in-terms-of.
Inheritance and Object-Oriented Design
Item 39 187A
Item 39: Use private inheritance judiciously.
Item 32 demonstrates that C++ treats public inheritance as an is-a
relationship. It does this by showing that compilers, when given a
hierarchy in which a class Student publicly inherits from a class Person, implicitly convert Students to Persons when that is necessary for a
function call to succeed. It’s worth repeating a portion of that example
using private inheritance instead of public inheritance:
class Person { ... };
class Student: private Person { ... };
void eat(const Person& p);
void study(const Student& s);
Person p;
Student s;
eat(p);
eat(s);
// inheritance is now private
// anyone can eat
// only students study
// p is a Person
// s is a Student
// fine, p is a Person
// error! a Student isn’t a Person
Clearly, private inheritance doesn’t mean is-a. What does it mean
then?
“Whoa!” you say. “Before we get to the meaning, let’s cover the behavior. How does private inheritance behave?” Well, the first rule governing private inheritance you’ve just seen in action: in contrast to public
inheritance, compilers will generally not convert a derived class object
(such as Student) into a base class object (such as Person) if the inheritance relationship between the classes is private. That’s why the call
to eat fails for the object s. The second rule is that members inherited
from a private base class become private members 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 D privately
inherit from a class B, you do so because you are interested in taking
advantage of some of the features available in class B, not because
there is any conceptual relationship between objects of types B and D.
As such, private inheritance is purely an implementation technique.
(That’s why everything you inherit from a private base class becomes
private in your class: it’s all just implementation detail.) Using the
terms introduced in Item 34, private inheritance means that implementation only should be inherited; interface should be ignored. If D
privately inherits from B, it means that D objects are implemented in
terms of B objects, nothing more. Private inheritance means nothing
during software design, only during software implementation.
188A Item 39
Chapter 6
The fact that private inheritance means is-implemented-in-terms-of is
a little disturbing, because Item 38 points out that composition can
mean the same thing. How are you supposed to choose between
them? The answer is simple: use composition whenever you can, and
use private inheritance whenever you must. When must you? Primarily when protected members and/or virtual functions enter the picture, though there’s also an edge case where space concerns can tip
the scales toward private inheritance. We’ll worry about the edge case
later. After all, it’s an edge case.
Suppose we’re working on an application involving Widgets, and we
decide we need to better understand how Widgets are being used. For
example, not only do we want to know things like how often Widget
member functions are called, we also want to know how the call ratios
change over time. Programs with distinct phases of execution can
have different behavioral profiles during the different phases. For
example, the functions used during the parsing phase of a compiler
are largely different from the functions used during optimization and
code generation.
We decide to modify the Widget class to keep track of how many times
each member function is called. At runtime, we’ll periodically examine
that information, possibly along with the values of each Widget and
whatever other data we deem useful. To make this work, we’ll need to
set up a timer of some kind so that we’ll know when it’s time to collect
the usage statistics.
Preferring to reuse existing code over writing new code, we rummage
around in our utility toolkit and are pleased to find the following class:
class Timer {
public:
explicit Timer(int tickFrequency);
virtual void onTick() const;
...
};
// automatically called for each tick
This is just what we’re looking for. A Timer object can be configured to
tick with whatever frequency we need, and on each tick, it calls a virtual function. We can redefine that virtual function so that it examines the current state of the Widget world. Perfect!
In order for Widget to redefine a virtual function in Timer, Widget must
inherit from Timer. But public inheritance is inappropriate in this
case. It’s not true that a Widget is-a Timer. Widget clients shouldn’t be
able to call onTick on a Widget, because that’s not part of the concep-
Inheritance and Object-Oriented Design
Item 39 189A
tual Widget interface. Allowing such a function call would make it easy
for clients to use the Widget interface incorrectly, a clear violation of
Item 18’s advice to make interfaces easy to use correctly and hard to
use incorrectly. Public inheritance is not a valid option here.
We thus inherit privately:
class Widget: private Timer {
private:
virtual void onTick() const;
...
};
// look at Widget usage data, etc.
By virtue of private inheritance, Timer’s public onTick function
becomes private in Widget, and we keep it there when we redeclare it.
Again, putting onTick in the public interface would mislead clients into
thinking they could call it, and that would violate Item 18.
This is a nice design, but it’s worth noting that private inheritance
isn’t strictly necessary. If we were determined to use composition
instead, we could. We’d just declare a private nested class inside Widget that would publicly inherit from Timer, redefine onTick there, and
put an object of that type inside Widget. Here’s a sketch of the
approach:
class Widget {
private:
class WidgetTimer: public Timer {
public:
virtual void onTick() const;
...
};
WidgetTimer timer;
...
};
Timer
Widget
WidgetTimer
This design is more complicated than the one using only private
inheritance, because it involves both (public) inheritance and composition, as well as the introduction of a new class ( WidgetTimer). To be
honest, I show it primarily to remind you that there is more than one
way to approach a design problem, and it’s worth training yourself to
consider multiple approaches (see also Item 35). Nevertheless, I can
think of two reasons why you might prefer public inheritance plus
composition over private inheritance.
First, you might want to design Widget to allow for derived classes, but
you might also want to prevent derived classes from redefining onTick.
If Widget inherits from Timer, that’s not possible, not even if the inher-
190A Item 39
Chapter 6
itance is private. (Recall from Item 35 that derived classes may redefine virtual functions even if they are not permitted to call them.) But
if WidgetTimer is private in Widget and inherits from Timer, Widget’s
derived classes have no access to WidgetTimer, hence can’t inherit
from it or redefine its virtual functions. If you’ve programmed in Java
or C# and miss the ability to prevent derived classes from redefining
virtual functions (i.e., Java’s final methods and C#’s sealed ones), now
you have an idea how to approximate that behavior in C++.
Second, you might want to minimize Widget’s compilation dependencies. If Widget inherits from Timer, Timer’s definition must be available
when Widget is compiled, so the file defining Widget probably has to
#include Timer.h. On the other hand, if WidgetTimer is moved out of Widget and Widget contains only a pointer to a WidgetTimer, Widget can get
by with a simple declaration for the WidgetTimer class; it need not
#include anything to do with Timer. For large systems, such decouplings can be important. (For details on minimizing compilation
dependencies, consult Item 31.)
I remarked earlier that private inheritance is useful primarily when a
would-be derived class wants access to the protected parts of a wouldbe base class or would like to redefine one or more of its virtual functions, but the conceptual relationship between the classes is is-implemented-in-terms-of instead of is-a. However, I also said that there was
an edge case involving space optimization that could nudge you to
prefer private inheritance over composition.
The edge case is edgy indeed: it applies only when you’re dealing with
a class that has no data in it. Such classes have no non-static data
members; no virtual functions (because the existence of such functions adds a vptr to each object — see Item 7); and no virtual base
classes (because such base classes also incur a size overhead — see
Item 40). Conceptually, objects of such empty classes should use no
space, because there is no per-object data to be stored. However,
there are technical reasons for C++ decreeing that freestanding
objects must have non-zero size, so if you do this,
class Empty {};
class HoldsAnInt {
private:
int x;
Empty e;
};
// has no data, so objects should
// use no memory
// should need only space for an int
// should require no memory
you’ll find that sizeof(HoldsAnInt) > sizeof(int); an Empty data member
requires memory. With most compilers, sizeof(Empty) is 1, because
Inheritance and Object-Oriented Design
Item 39 191A
C++’s edict against zero-size freestanding objects is typically satisfied
by the silent insertion of a char into “empty” objects. However, alignment requirements (see Item 50) may cause compilers to add padding
to classes like HoldsAnInt, so it’s likely that HoldsAnInt objects wouldn’t
gain just the size of a char, they would actually enlarge enough to hold
a second int. (On all the compilers I tested, that’s exactly what happened.)
But perhaps you’ve noticed that I’ve been careful to say that “freestanding” objects mustn’t have zero size. This constraint doesn’t apply
to base class parts of derived class objects, because they’re not freestanding. If you inherit from Empty instead of containing an object of
that type,
class HoldsAnInt: private Empty {
private:
int x;
};
you’re almost sure to find that sizeof(HoldsAnInt) == sizeof(int). This is
known as the empty base optimization (EBO), and it’s implemented by
all the compilers I tested. If you’re a library developer whose clients
care about space, the EBO is worth knowing about. Also worth knowing is that the EBO is generally viable only under single inheritance.
The rules governing C++ object layout generally mean that the EBO
can’t be applied to derived classes that have more than one base.
In practice, “empty” classes aren’t truly empty. Though they never
have non-static data members, they often contain typedefs, enums,
static data members, or non-virtual functions. The STL has many
technically empty classes that contain useful members (usually
unary_function and
typedefs),
including
the
base
classes
binary_function, from which classes for user-defined function objects
typically inherit. Thanks to widespread implementation of the EBO,
such inheritance rarely increases the size of the inheriting classes.
Still, let’s get back to basics. Most classes aren’t empty, so the EBO is
rarely a legitimate justification for private inheritance. Furthermore,
most inheritance corresponds to is-a, and that’s a job for public inheritance, not private. Both composition and private inheritance mean isimplemented-in-terms-of, but composition is easier to understand, so
you should use it whenever you can.
Private inheritance is most likely to be a legitimate design strategy
when you’re dealing with two classes not related by is-a where one
either needs access to the protected members of another or needs to
redefine one or more of its virtual functions. Even in that case, we’ve
192A Item 40
Chapter 6
seen that a mixture of public inheritance and containment can often
yield the behavior you want, albeit with greater design complexity.
Using private inheritance judiciously means employing it when, having considered all the alternatives, it’s the best way to express the
relationship between two classes in your software.
Things to Remember
✦
Private inheritance means is-implemented-in-terms of. It’s usually
inferior to composition, but it makes sense when a derived class
needs access to protected base class members or needs to redefine
inherited virtual functions.
✦
Unlike composition, private inheritance can enable the empty base
optimization. This can be important for library developers who strive
to minimize object sizes.
Item 40: Use multiple inheritance judiciously.
When it comes to multiple inheritance (MI), the C++ community
largely breaks into two basic camps. One camp believes that if single
inheritance (SI) is good, multiple inheritance must be better. The other
camp argues that single inheritance is good, but multiple inheritance
isn’t worth the trouble. In this Item, our primary goal is to understand
both perspectives on the MI question.
One of the first things to recognize is that when MI enters the designscape, it becomes possible to inherit the same name (e.g., function,
typedef, etc.) from more than one base class. That leads to new opportunities for ambiguity. For example:
class BorrowableItem {
public:
void checkOut();
...
};
class ElectronicGadget {
private:
bool checkOut() const;
...
};
class MP3Player:
public BorrowableItem,
public ElectronicGadget
{ ... };
MP3Player mp;
mp.checkOut();
// something a library lets you borrow
// check the item out from the library
// perform self-test, return whether
// test succeeds
// note MI here
// (some libraries loan MP3 players)
// class definition is unimportant
// ambiguous! which checkOut?
Inheritance and Object-Oriented Design
Item 40 193A
Note that in this example, the call to checkOut is ambiguous, even
though only one of the two functions is accessible. (checkOut is public
in BorrowableItem but private in ElectronicGadget.) That’s in accord with
the C++ rules for resolving calls to overloaded functions: before seeing
whether a function is accessible, C++ first identifies the function
that’s the best match for the call. It checks accessibility only after
finding the best-match function. In this case, the name checkOut is
ambiguous during name lookup, so neither function overload resolution nor best match determination takes place. The accessibility of
ElectronicGadget::checkOut is therefore never examined.
To resolve the ambiguity, you must specify which base class’s function to call:
mp.BorrowableItem::checkOut();
// ah, that checkOut...
You could try to explicitly call ElectronicGadget::checkOut, too, of
course, but then the ambiguity error would be replaced with a “you’re
trying to call a private member function” error.
Multiple inheritance just means inheriting from more than one base
class, but it is not uncommon for MI to be found in hierarchies that
have higher-level base classes, too. That can lead to what is sometimes known as the “deadly MI diamond”:
class File { ... };
class InputFile: public File { ... };
class OutputFile: public File { ... };
class IOFile: public InputFile,
public OutputFile
{ ... };
File
InputFile
OutputFile
IOFile
Any time you have an inheritance hierarchy with more than one path
between a base class and a derived class (such as between File and
IOFile above, which has paths through both InputFile and OutputFile),
you must confront the question of whether you want the data members in the base class to be replicated for each of the paths. For example, suppose that the File class has a data member, fileName. How
many copies of this field should IOFile have? On the one hand, it inherits a copy from each of its base classes, so that suggests that IOFile
should have two fileName data members. On the other hand, simple
logic says that an IOFile object has only one file name, so the fileName
field it inherits through its two base classes should not be replicated.
C++ takes no position on this debate. It happily supports both
options, though its default is to perform the replication. If that’s not
what you want, you must make the class with the data (i.e., File) a vir-
194A Item 40
Chapter 6
tual base class. To do that, you have all classes that immediately
inherit from it use virtual inheritance:
class File { ... };
class InputFile: virtual public File { ... };
class OutputFile: virtual public File { ... };
class IOFile: public InputFile,
public OutputFile
{ ... };
File
{virtual}
InputFile
{virtual}
OutputFile
IOFile
The standard C++ library contains an MI hierarchy just like this one,
except the classes are class templates, and the names are basic_ios,
basic_istream, basic_ostream, and basic_iostream instead of File, InputFile,
OutputFile, and IOFile.
From the viewpoint of correct behavior, public inheritance should
always be virtual. If that were the only point of view, the rule would be
simple: anytime you use public inheritance, use virtual public inheritance. Alas, correctness is not the only perspective. Avoiding the replication of inherited fields requires some behind-the-scenes legerdemain
on the part of compilers, and the result is that objects created from
classes using virtual inheritance are generally larger than they would
be without virtual inheritance. Access to data members in virtual base
classes is also slower than to those in non-virtual base classes. The
details vary from compiler to compiler, but the basic thrust is clear:
virtual inheritance costs.
It costs in other ways, too. The rules governing the initialization of virtual base classes are more complicated and less intuitive than are
those for non-virtual bases. The responsibility for initializing a virtual
base is borne by the most derived class in the hierarchy. Implications
of this rule include (1) classes derived from virtual bases that require
initialization must be aware of their virtual bases, no matter how far
distant the bases are, and (2) when a new derived class is added to the
hierarchy, it must assume initialization responsibilities for its virtual
bases (both direct and indirect).
My advice on virtual base classes (i.e., on virtual inheritance) is simple. First, don’t use virtual bases unless you need to. By default, use
non-virtual inheritance. Second, if you must use virtual base classes,
try to avoid putting data in them. That way you won’t have to worry
about oddities in the initialization (and, as it turns out, assignment)
rules for such classes. It’s worth noting that Interfaces in Java and
.NET, which are in many ways comparable to virtual base classes in
C++, are not allowed to contain any data.
Let us now turn to the following C++ Interface class (see Item 31) for
modeling persons:
Inheritance and Object-Oriented Design
Item 40 195A
class IPerson {
public:
virtual ~IPerson();
virtual std::string name() const = 0;
virtual std::string birthDate() const = 0;
};
IPerson clients must program in terms of IPerson pointers and references, because abstract classes cannot be instantiated. To create
objects that can be manipulated as IPerson objects, clients of IPerson
use factory functions (again, see Item 31) to instantiate concrete
classes derived from IPerson:
// factory function to create a Person object from a unique database ID;
// see Item 18 for why the return type isn’t a raw pointer
std::tr1::shared_ptr<IPerson> makePerson(DatabaseID personIdentifier);
// function to get a database ID from the user
DatabaseID askUserForDatabaseID();
DatabaseID id(askUserForDatabaseID());
std::tr1::shared_ptr<IPerson> pp(makePerson(id));
...
// create an object
// supporting the
// IPerson interface
// manipulate *pp via
// IPerson’s member
// functions
But how does makePerson create the objects to which it returns pointers? Clearly, there must be some concrete class derived from IPerson
that makePerson can instantiate.
Suppose this class is called CPerson. As a concrete class, CPerson must
provide implementations for the pure virtual functions it inherits from
IPerson. It could write these from scratch, but it would be better to
take advantage of existing components that do most or all of what’s
necessary. For example, suppose an old database-specific class PersonInfo offers the essence of what CPerson needs:
class PersonInfo {
public:
explicit PersonInfo(DatabaseID pid);
virtual ~PersonInfo();
virtual const char * theName() const;
virtual const char * theBirthDate() const;
...
private:
virtual const char * valueDelimOpen() const;
virtual const char * valueDelimClose() const;
...
};
// see
// below
196A Item 40
Chapter 6
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 likely to be pretty comfortable.
You come to discover that PersonInfo was designed to facilitate printing
database fields 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 square brackets, so the field
value “Ring-tailed Lemur” would be formatted this way:
[Ring-tailed Lemur]
In recognition of the fact that square brackets are not universally
desired by clients of PersonInfo, the virtual functions valueDelimOpen
and valueDelimClose allow derived classes to specify their own opening
and closing delimiter strings. The implementations of PersonInfo’s
member functions call these virtual functions to add the appropriate
delimiters to the values they return. Using PersonInfo::theName as an
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
std::strcpy(value, valueDelimOpen());
append to the string in value this object’s name field (being careful
to avoid buffer overruns!)
// write closing delimiter
std::strcat(value, valueDelimClose());
return value;
}
One might question the antiquated design of PersonInfo::theName (especially the use of a fixed-size static buffer, something that’s rife for both
overrun and threading problems — see also Item 21), but set such
questions aside and focus instead on this: theName calls valueDelimOpen to generate the opening delimiter of the string it will return, then it
generates the name value itself, then it calls valueDelimClose. Because
Inheritance and Object-Oriented Design
Item 40 197A
valueDelimOpen and valueDelimClose are virtual functions, the result
returned by theName is dependent not only on PersonInfo but also on
the classes derived from PersonInfo.
As the implementer of CPerson, that’s good news, because while perusing the fine print in the IPerson documentation, you discover that name
and birthDate are required to return unadorned values, i.e., no delimiters are allowed. That is, if a person is named Homer, a call to that
person’s name function should return “Homer”, not “[Homer]”.
The relationship between CPerson and PersonInfo is that PersonInfo happens to have some functions that would make CPerson easier to implement. That’s all. Their relationship is thus is-implemented-in-termsof, and we know that can be represented in two ways: via composition
(see Item 38) and via private inheritance (see Item 39). Item 39 points
out that composition is the generally preferred approach, but inheritance is necessary if virtual functions are to be redefined. In this case,
CPerson needs to redefine valueDelimOpen and valueDelimClose, so simple composition won’t do. The most straightforward solution is to have
CPerson privately inherit from PersonInfo, though Item 39 explains that
with a bit more work, CPerson could also use a combination of composition and inheritance to effectively redefine PersonInfo’s virtuals. Here,
we’ll use private inheritance.
But CPerson must also implement the IPerson interface, and that calls
for public inheritance. This leads to one reasonable application of
multiple inheritance: combine public inheritance of an interface with
private inheritance of an implementation:
class IPerson {
// this class specifies the
public:
// interface to be implemented
virtual ~IPerson();
virtual std::string name() const = 0;
virtual std::string birthDate() const = 0;
};
class DatabaseID { ... };
// used below; details are
// unimportant
class PersonInfo {
// this class has functions
public:
// useful in implementing
explicit PersonInfo(DatabaseID pid);
// the IPerson interface
virtual ~PersonInfo();
virtual const char * theName() const;
virtual const char * theBirthDate() const;
...
private:
virtual const char * valueDelimOpen() const;
virtual const char * valueDelimClose() const;
...
};
198A Item 40
Chapter 6
class CPerson: public IPerson, private PersonInfo {
// note use of MI
public:
explicit CPerson(DatabaseID pid): PersonInfo(pid) {}
virtual std::string name() const
// implementations
{ return PersonInfo::theName(); }
// of the required
// IPerson member
virtual std::string birthDate() const
// functions
{ return PersonInfo::theBirthDate(); }
private:
// redefinitions of
const char * valueDelimOpen() const { return ""; } // inherited virtual
const char * valueDelimClose() const { return ""; } // delimiter
};
// functions
In UML, the design looks like this:
IPerson
PersonInfo
{private}
CPerson
This example demonstrates that MI can be both useful and comprehensible.
At the end of the day, multiple inheritance is just another tool in the
object-oriented toolbox. Compared to single inheritance, it’s typically
more complicated to use and more complicated to understand, so if
you’ve got an SI design that’s more or less equivalent to an MI design,
the SI design is almost certainly preferable. If the only design you can
come up with involves MI, you should think a little harder — there’s
almost certainly some way to make SI work. At the same time, MI is
sometimes the clearest, most maintainable, most reasonable way to
get the job done. When that’s the case, don’t be afraid to use it. Just
be sure to use it judiciously.
Things to Remember
✦
Multiple inheritance is more complex than single inheritance. It can
lead to new ambiguity issues and to the need for virtual inheritance.
✦
Virtual inheritance imposes costs in size, speed, and complexity of
initialization and assignment. It’s most practical when virtual base
classes have no data.
✦
Multiple inheritance does have legitimate uses. One scenario involves combining public inheritance from an Interface class with
private inheritance from a class that helps with implementation.
Templates and
Generic Programming
Chapter 7: Templates and Generic Programming
The initial motivation for C++ templates was straightforward: to make
it possible to create type-safe containers like vector, list, and map. The
more people worked with templates, however, the wider the variety of
things they found they could do with them. Containers were good, but
generic programming — the ability to write code that is independent of
the types of objects being manipulated — was even better. STL algorithms like for_each, find, and merge are examples of such programming. Ultimately, it was discovered that the C++ template mechanism
is itself Turing-complete: it can be used to compute any computable
value. That led to template metaprogramming: the creation of programs that execute inside C++ compilers and that stop running when
compilation is complete. These days, containers are but a small part
of the C++ template pie. Despite the breadth of template applications,
however, a set of core ideas underlie all template-based programming.
Those ideas are the focus of this chapter.
Templates and Generic Programming
This chapter won’t make you an expert template programmer, but it
will make you a better one. It will also give you information you need
to expand your template-programming boundaries as far as you
desire.
Item 41: Understand implicit interfaces and compiletime polymorphism.
The world of object-oriented programming revolves around explicit
interfaces and runtime polymorphism. For example, given this (meaningless) class,
class Widget {
public:
Widget();
virtual ~Widget();
200A Item 41
virtual std::size_t size() const;
virtual void normalize();
void swap(Widget& other);
...
Chapter 7
// see Item 25
};
and this (equally meaningless) function,
void doProcessing(Widget& w)
{
if (w.size() > 10 && w != someNastyWidget) {
Widget temp(w);
temp.normalize();
temp.swap(w);
}
}
we can say this about w in doProcessing:
■
Because w is declared to be of type Widget, w must support the
Widget interface. We can look up this interface in the source code
(e.g., the .h file for Widget) to see exactly what it looks like, so I call
this an explicit interface — one explicitly visible in the source code.
■
Because some of Widget’s member functions are virtual, w’s calls
to those functions will exhibit runtime polymorphism: the specific
function to call will be determined at runtime based on w’s dynamic type (see Item 37).
The world of templates and generic programming is fundamentally different. In that world, explicit interfaces and runtime polymorphism
continue to exist, but they’re less important. Instead, implicit interfaces and compile-time polymorphism move to the fore. To see how this
is the case, look what happens when we turn doProcessing from a
function into a function template:
template<typename T>
void doProcessing(T& w)
{
if (w.size() > 10 && w != someNastyWidget) {
T temp(w);
temp.normalize();
temp.swap(w);
}
}
Now what can we say about w in doProcessing?
■
The interface that w must support is determined by the operations
performed on w in the template. In this example, it appears that
w’s type ( T) must support the size, normalize, and swap member
Templates and Generic Programming
Item 41 201A
functions; copy construction (to create temp); and comparison for
inequality (for comparison with someNastyWidget). We’ll soon see
that this isn’t quite accurate, but it’s true enough for now. What’s
important is that the set of expressions that must be valid in order
for the template to compile is the implicit interface that T must
support.
■
The calls to functions involving w such as operator> and operator!=
may involve instantiating templates to make these calls succeed.
Such instantiation occurs during compilation. Because instantiating function templates with different template parameters leads to
different functions being called, this is known as compile-time
polymorphism.
Even if you’ve never used templates, you should be familiar with the
difference between runtime and compile-time polymorphism, because
it’s similar to the difference between the process of determining which
of a set of overloaded functions should be called (which takes place
during compilation) and dynamic binding of virtual function calls
(which takes place at runtime). The difference between explicit and
implicit interfaces is new to templates, however, and it bears closer
examination.
An explicit interface typically consists of function signatures, i.e.,
function names, parameter types, return types, etc. The Widget class
public interface, for example,
class Widget {
public:
Widget();
virtual ~Widget();
virtual std::size_t size() const;
virtual void normalize();
void swap(Widget& other);
};
consists of a constructor, a destructor, and the functions size, normalize, and swap, along with the parameter types, return types, and constnesses of these functions. (It also includes the compiler-generated
copy constructor and copy assignment operator — see Item 5.) It
could also include typedefs and, if you were so bold as to violate
Item 22’s advice to make data members private, data members,
though in this case, it does not.
An implicit interface is quite different. It is not based on function signatures. Rather, it consists of valid expressions. Look again at the
conditional at the beginning of the doProcessing template:
202A Item 41
Chapter 7
template<typename T>
void doProcessing(T& w)
{
if (w.size() > 10 && w != someNastyWidget) {
...
The implicit interface for T (w’s type) appears to have these constraints:
■
It must offer a member function named size that returns an integral value.
■
It must support an operator!= function that compares two objects
of type T. (Here, we assume that someNastyWidget is of type T.)
Thanks to the possibility of operator overloading, neither of these constraints need be satisfied. Yes, T must support a size member function,
though it’s worth mentioning that the function might be inherited
from a base class. But this member function need not return an integral type. It need not even return a numeric type. For that matter, it
need not even return a type for which operator> is defined! All it needs
to do is return an object of some type X such that there is an operator>
that can be called with an object of type X and an int (because 10 is of
type int). The operator> need not take a parameter of type X, because it
could take a parameter of type Y, and that would be okay as long as
there were an implicit conversion from objects of type X to objects of
type Y!
Similarly, there is no requirement that T support operator!=, because it
would be just as acceptable for operator!= to take one object of type X
and one object of type Y. As long as T can be converted to X and someNastyWidget’s type can be converted to Y, the call to operator!= would
be valid.
(As an aside, this analysis doesn’t take into account the possibility
that operator&& could be overloaded, thus changing the meaning of
the above expression from a conjunction to something potentially
quite different.)
Most people’s heads hurt when they first start thinking about implicit
interfaces this way, but there’s really no need for aspirin. Implicit
interfaces are simply made up of a set of valid expressions. The
expressions themselves may look complicated, but the constraints
they impose are generally straightforward. For example, given the conditional,
if (w.size() > 10 && w != someNastyWidget) ...
it’s hard to say much about the constraints on the functions size, operator>, operator&&, or operator!=, but it’s easy to identify the constraint
Templates and Generic Programming
Item 42 203A
on the expression as a whole. The conditional part of an if statement
must be a boolean expression, so regardless of the exact types
involved, whatever “w.size() > 10 && w != someNastyWidget” yields, it
must be compatible with bool. This is part of the implicit interface the
template doProcessing imposes on its type parameter T. The rest of the
interface required by doProcessing is that calls to the copy constructor,
to normalize, and to swap must be valid for objects of type T.
The implicit interfaces imposed on a template’s parameters are just as
real as the explicit interfaces imposed on a class’s objects, and both
are checked during compilation. Just as you can’t use an object in a
way contradictory to the explicit interface its class offers (the code
won’t compile), you can’t try to use an object in a template unless that
object supports the implicit interface the template requires (again, the
code won’t compile).
Things to Remember
✦
Both classes and templates support interfaces and polymorphism.
✦
For classes, interfaces are explicit and centered on function signatures. Polymorphism occurs at runtime through virtual functions.
✦
For template parameters, interfaces are implicit and based on valid
expressions. Polymorphism occurs during compilation through template instantiation and function overloading resolution.
Item 42: Understand the two meanings of typename.
Question: what is the difference between class and typename in the following template declarations?
template<class T> class Widget;
template<typename T> class Widget;
// uses “class”
// uses “typename”
Answer: nothing. When declaring a template type parameter, class and
typename mean exactly the same thing. Some programmers prefer
class all the time, because it’s easier to type. Others (including me)
prefer typename, because it suggests that the parameter need not be a
class type. A few developers employ typename when any type is
allowed and reserve class for when only user-defined types are acceptable. But from C++’s point of view, class and typename mean exactly
the same thing when declaring a template parameter.
C++ doesn’t always view class and typename as equivalent, however.
Sometimes you must use typename. To understand when, we have to
talk about two kinds of names you can refer to in a template.
204A Item 42
Chapter 7
Suppose we have a template for a function that takes an STL-compatible container holding objects that can be assigned to ints. Further
suppose that this function simply prints the value of its second element. It’s a silly function implemented in a silly way, and as I’ve written it below, it shouldn’t even compile, but please overlook those
things — there’s a method to my madness:
template<typename C>
void print2nd(const C& container)
{
if (container.size() >= 2) {
C::const_iterator iter(container.begin());
++iter;
int value = *iter;
std::cout << value;
}
}
// print 2nd element in
// container;
// this is not valid C++!
// get iterator to 1st element
// move iter to 2nd element
// copy that element to an int
// print the int
I’ve highlighted the two local variables in this function, iter and value.
The type of iter is C::const_iterator, a type that depends on the template
parameter C. Names in a template that are dependent on a template
parameter are called dependent names. When a dependent name is
nested inside a class, I call it a nested dependent name. C::const_iterator
is a nested dependent name. In fact, it’s a nested dependent type
name, i.e., a nested dependent name that refers to a type.
The other local variable in print2nd, value, has type int. int is a name
that does not depend on any template parameter. Such names are
known as non-dependent names, (I have no idea why they’re not called
independent names. If, like me, you find the term “non-dependent” an
abomination, you have my sympathies, but “non-dependent” is the
term for these kinds of names, so, like me, roll your eyes and resign
yourself to it.)
Nested dependent names can lead to parsing difficulties. For example,
suppose we made print2nd even sillier by starting it this way:
template<typename C>
void print2nd(const C& container)
{
C::const_iterator * x;
...
}
This looks like we’re declaring x as a local variable that’s a pointer to a
C::const_iterator. But it looks that way only because we “know” that
C::const_iterator is a type. But what if C::const_iterator weren’t a type?
What if C had a static data member that happened to be named
const_iterator, and what if x happened to be the name of a global vari-
Templates and Generic Programming
Item 42 205A
able? In that case, the code above wouldn’t declare a local variable, it
would be a multiplication of C::const_iterator by x! Sure, that sounds
crazy, but it’s possible, and people who write C++ parsers have to
worry about all possible inputs, even the crazy ones.
Until C is known, there’s no way to know whether C::const_iterator is a
type or isn’t, and when the template print2nd is parsed, C isn’t known.
C++ has a rule to resolve this ambiguity: if the parser encounters a
nested dependent name in a template, it assumes that the name is not
a type unless you tell it otherwise. By default, nested dependent
names are not types. (There is an exception to this rule that I’ll get to
in a moment.)
With that in mind, look again at the beginning of print2nd:
template<typename C>
void print2nd(const C& container)
{
if (container.size() >= 2) {
C::const_iterator iter(container.begin());
...
// this name is assumed to
// not be a type
Now it should be clear why this isn’t valid C++. The declaration of iter
makes sense only if C::const_iterator is a type, but we haven’t told C++
that it is, and C++ assumes that it’s not. To rectify the situation, we
have to tell C++ that C::const_iterator is a type. We do that by putting
typename immediately in front of it:
template<typename C>
// this is valid C++
void print2nd(const C& container)
{
if (container.size() >= 2) {
typename C::const_iterator iter(container.begin());
...
}
}
The general rule is simple: anytime you refer to a nested dependent
type name in a template, you must immediately precede it by the word
typename. (Again, I’ll describe an exception shortly.)
typename should be used to identify only nested dependent type
names; other names shouldn’t have it. For example, here’s a function
template that takes both a container and an iterator into that container:
template<typename C>
void f(const C& container,
typename C::iterator iter);
// typename allowed (as is “class”)
// typename not allowed
// typename required
206A Item 42
Chapter 7
C is not a nested dependent type name (it’s not nested inside anything
dependent on a template parameter), so it must not be preceded by
typename when declaring container, but C::iterator is a nested dependent type name, so it’s required to be preceded by typename.
The exception to the “typename must precede nested dependent type
names” rule is that typename must not precede nested dependent type
names in a list of base classes or as a base class identifier in a member initialization list. For example:
template<typename T>
class Derived: public Base<T>::Nested {
public:
explicit Derived(int x)
: Base<T>::Nested(x)
{
typename Base<T>::Nested temp;
...
}
...
// base class list: typename not
// allowed
// base class identifier in mem.
// init. list: typename not allowed
// use of nested dependent type
// name not in a base class list or
// as a base class identifier in a
// mem. init. list: typename
required
};
Such inconsistency is irksome, but once you have a bit of experience
under your belt, you’ll barely notice it.
Let’s look at one last typename example, because it’s representative of
something you’re going to see in real code. Suppose we’re writing a
function template that takes an iterator, and we want to make a local
copy, temp, of the object the iterator points to. We can do it like this:
template<typename IterT>
void workWithIterator(IterT iter)
{
typename std::iterator_traits<IterT>::value_type temp(*iter);
...
}
Don’t let the std::iterator_traits<IterT>::value_type startle you. That’s just
a use of a standard traits class (see Item 47), the C++ way of saying
“the type of thing pointed to by objects of type IterT.” The statement
declares a local variable (temp) of the same type as what IterT objects
point to, and it initializes temp with the object that iter points to. If
IterT is vector<int>::iterator, temp is of type int. If IterT is list<string>::iterator, temp is of type string. Because std::iterator_traits<IterT>::value_type is
a nested dependent type name (value_type is nested inside
iterator_traits<IterT>, and IterT is a template parameter), we must precede it by typename.
Templates and Generic Programming
Item 43 207A
If you think reading std::iterator_traits<IterT>::value_type is unpleasant,
imagine what it’s like to type it. If you’re like most programmers, the
thought of typing it more than once is ghastly, so you’ll want to create
a typedef. For traits member names like value_type (again, see Item 47
for information on traits), a common convention is for the typedef
name to be the same as the traits member name, so such a local
typedef is often defined like this:
template<typename IterT>
void workWithIterator(IterT iter)
{
typedef typename std::iterator_traits<IterT>::value_type value_type;
value_type temp(*iter);
...
}
Many programmers find the “typedef typename” juxtaposition initially
jarring, but it’s a logical fallout from the rules for referring to nested
dependent type names. You’ll get used to it fairly quickly. After all, you
have strong motivation. How many times do you want to type typename std::iterator_traits<IterT>::value_type?
As a closing note, I should mention that enforcement of the rules surrounding typename varies from compiler to compiler. Some compilers
accept code where typename is required but missing; some accept
code where typename is present but not allowed; and a few (usually
older ones) reject typename where it’s present and required. This
means that the interaction of typename and nested dependent type
names can lead to some mild portability headaches.
Things to Remember
✦
When declaring template parameters, class and typename are interchangeable.
✦
Use typename to identify nested dependent type names, except in
base class lists or as a base class identifier in a member initialization list.
Item 43: Know how to access names in templatized
base classes.
Suppose we need to write an application that can send messages to
several different companies. Messages can be sent in either encrypted
or cleartext (unencrypted) form. If we have enough information during
compilation to determine which messages will go to which companies,
we can employ a template-based solution:
208A Item 43
Chapter 7
class CompanyA {
public:
...
void sendCleartext(const std::string& msg);
void sendEncrypted(const std::string& msg);
...
};
class CompanyB {
public:
...
void sendCleartext(const std::string& msg);
void sendEncrypted(const std::string& msg);
...
};
...
// classes for other companies
class MsgInfo { ... };
// class for holding information
// used to create a message
template<typename Company>
class MsgSender {
public:
...
// ctors, dtor, etc.
void sendClear(const MsgInfo& info)
{
std::string msg;
create msg from info;
Company c;
c.sendCleartext(msg);
}
void sendSecret(const MsgInfo& info)
{ ... }
// similar to sendClear, except
// calls c.sendEncrypted
};
This will work fine, but suppose we sometimes want to log some information each time we send a message. A derived class can easily add
that capability, and this seems like a reasonable way to do it:
template<typename Company>
class LoggingMsgSender: public MsgSender<Company> {
public:
...
// ctors, dtor, etc.
void sendClearMsg(const MsgInfo& info)
{
write "before sending" info to the log;
sendClear(info);
write "after sending" info to the log;
}
...
};
// call base class function;
// this code will not compile!
Templates and Generic Programming
Item 43 209A
Note how the message-sending function in the derived class has a different name (sendClearMsg) from the one in its base class (there, it’s
called sendClear). That’s good design, because it side-steps the issue of
hiding inherited names (see Item 33) as well as the problems inherent
in redefining an inherited non-virtual function (see Item 36). But the
code above won’t compile, at least not with conformant compilers.
Such compilers will complain that sendClear doesn’t exist. We can see
that sendClear is in the base class, but compilers won’t look for it
there. We need to understand why.
The problem is that when compilers encounter the definition for the
class template LoggingMsgSender, they don’t know what class it inherits from. Sure, it’s MsgSender<Company>, but Company is a template
parameter, one that won’t be known until later (when LoggingMsgSender is instantiated). Without knowing what Company is,
there’s no way to know what the class MsgSender<Company> looks
like. In particular, there’s no way to know if it has a sendClear function.
To make the problem concrete, suppose we have a class CompanyZ
that insists on encrypted communications:
class CompanyZ {
// this class offers no
public:
// sendCleartext function
...
void sendEncrypted(const std::string& msg);
...
};
The general MsgSender template is inappropriate for CompanyZ, because
that template offers a sendClear function that makes no sense for CompanyZ objects. To rectify that problem, we can create a specialized version of MsgSender for CompanyZ:
template<>
class MsgSender<CompanyZ> {
public:
...
void sendSecret(const MsgInfo& info)
{ ... }
};
// a total specialization of
// MsgSender; the same as the
// general template, except
// sendClear is omitted
Note the “template <>” syntax at the beginning of this class definition.
It signifies that this is neither a template nor a standalone class.
Rather, it’s a specialized version of the MsgSender template to be used
when the template argument is CompanyZ. This is known as a total
template specialization: the template MsgSender is specialized for the
type CompanyZ, and the specialization is total — once the type param-
210A Item 43
Chapter 7
eter has been defined to be CompanyZ, no other aspect of the template’s parameters can vary.
Given that MsgSender has been specialized for CompanyZ, consider
again the derived class LoggingMsgSender:
template<typename Company>
class LoggingMsgSender: public MsgSender<Company> {
public:
...
void sendClearMsg(const MsgInfo& info)
{
write "before sending" info to the log;
sendClear(info);
// if Company == CompanyZ,
// this function doesn’t exist!
write "after sending" info to the log;
}
...
};
As the comment notes, this code makes no sense when the base class
is MsgSender<CompanyZ>, because that class offers no sendClear function. That’s why C++ rejects the call: it recognizes that base class templates may be specialized and that such specializations may not offer
the same interface as the general template. As a result, it generally
refuses to look in templatized base classes for inherited names. In
some sense, when we cross from Object-Oriented C++ to Template
C++ (see Item 1), inheritance stops working.
To restart it, we have to somehow disable C++’s “don’t look in templatized base classes” behavior. There are three ways to do this. First,
you can preface calls to base class functions with “this->”:
template<typename Company>
class LoggingMsgSender: public MsgSender<Company> {
public:
...
void sendClearMsg(const MsgInfo& info)
{
write "before sending" info to the log;
this->sendClear(info);
write "after sending" info to the log;
}
...
};
// okay, assumes that
// sendClear will be inherited
Templates and Generic Programming
Item 43 211A
Second, you can employ a using declaration, a solution that should
strike you as familiar if you’ve read Item 33. That Item explains how
using declarations bring hidden base class names into a derived
class’s scope. We can therefore write sendClearMsg like this:
template<typename Company>
class LoggingMsgSender: public MsgSender<Company> {
public:
using MsgSender<Company>::sendClear; // tell compilers to assume
...
// that sendClear is in the
// base class
void sendClearMsg(const MsgInfo& info)
{
...
sendClear(info);
// okay, assumes that
...
// sendClear will be inherited
}
...
};
(Although a using declaration will work both here and in Item 33, the
problems being solved are different. Here, the situation isn’t that base
class names are hidden by derived class names, it’s that compilers
don’t search base class scopes unless we tell them to.)
A final way to get your code to compile is to explicitly specify that the
function being called is in the base class:
template<typename Company>
class LoggingMsgSender: public MsgSender<Company> {
public:
...
void sendClearMsg(const MsgInfo& info)
{
...
MsgSender<Company>::sendClear(info);
// okay, assumes that
...
// sendClear will be
}
// inherited
...
};
This is generally the least desirable way to solve the problem, because
if the function being called is virtual, explicit qualification turns off
the virtual binding behavior.
From a name visibility point of view, each of these approaches does
the same thing: it promises compilers that any subsequent specializations of the base class template will support the interface offered by
the general template. Such a promise is all compilers need when they
parse a derived class template like LoggingMsgSender, but if the prom-
212A Item 44
Chapter 7
ise turns out to be unfounded, the truth will emerge during subsequent compilation. For example, if the source code later contains this,
LoggingMsgSender<CompanyZ> zMsgSender;
MsgInfo msgData;
...
zMsgSender.sendClearMsg(msgData);
// put info in msgData
// error! won’t compile
the call to sendClearMsg won’t compile, because at this point, compilers know that the base class is the template specialization
MsgSender<CompanyZ>, and they know that class doesn’t offer the
sendClear function that sendClearMsg is trying to call.
Fundamentally, the issue is whether compilers will diagnose invalid
references to base class members sooner (when derived class template
definitions are parsed) or later (when those templates are instantiated
with specific template arguments). C++’s policy is to prefer early diagnoses, and that’s why it assumes it knows nothing about the contents
of base classes when those classes are instantiated from templates.
Things to Remember
✦
In derived class templates, refer to names in base class templates
via a “this->” prefix, via using declarations, or via an explicit base
class qualification.
Item 44: Factor parameter-independent code out of
templates.
Templates are a wonderful way to save time and avoid code replication. Instead of typing 20 similar classes, each with 15 member functions, you type one class template, and you let compilers instantiate
the 20 specific classes and 300 functions you need. (Member functions of class templates are implicitly instantiated only when used, so
you should get the full 300 member functions only if each is actually
used.) Function templates are similarly appealing. Instead of writing
many functions, you write one function template and let the compilers
do the rest. Ain’t technology grand?
Yes, well...sometimes. If you’re not careful, using templates can lead
to code bloat: binaries with replicated (or almost replicated) code,
data, or both. The result can be source code that looks fit and trim,
yet object code that’s fat and flabby. Fat and flabby is rarely fashionable, so you need to know how to avoid such binary bombast.
Your primary tool has the imposing name commonality and variability
analysis, but there’s nothing imposing about the idea. Even if you’ve
Templates and Generic Programming
Item 44 213A
never written a template in your life, you do such analysis all the time.
When you’re writing a function and you realize that some part of the
function’s implementation is essentially the same as another function’s implementation, do you just replicate the code? Of course not.
You factor the common code out of the two functions, put it into a
third function, and have both of the other functions call the new one.
That is, you analyze the two functions to find the parts that are common and those that vary, you move the common parts into a new
function, and you keep the varying parts in the original functions.
Similarly, if you’re writing a class and you realize that some parts of
the class are the same as parts of another class, you don’t replicate
the common parts. Instead, you move the common parts to a new
class, then you use inheritance or composition (see Items 32, 38, and
39) to give the original classes access to the common features. The
parts of the original classes that differ — the varying parts — remain
in their original locations.
When writing templates, you do the same analysis, and you avoid replication in the same ways, but there’s a twist. In non-template code,
replication is explicit: you can see that there’s duplication between
two functions or two classes. In template code, replication is implicit:
there’s only one copy of the template source code, so you have to train
yourself to sense the replication that may take place when a template
is instantiated multiple times.
For example, suppose you’d like to write a template for fixed-size
square matrices that, among other things, support matrix inversion.
template<typename T,
std::size_t n>
class SquareMatrix {
public:
...
void invert();
};
// template for n x n matrices of
// objects of type T; see below for info
// on the size_t parameter
// invert the matrix in place
This template takes a type parameter, T, but it also takes a parameter
of type size_t — a non-type parameter. Non-type parameters are less
common than type parameters, but they’re completely legal, and, as
in this example, they can be quite natural.
Now consider this code:
SquareMatrix<double, 5> sm1;
...
sm1.invert();
// call SquareMatrix<double, 5>::invert
SquareMatrix<double, 10> sm2;
...
sm2.invert();
// call SquareMatrix<double, 10>::invert
214A Item 44
Chapter 7
Two copies of invert will be instantiated here. The functions won’t be
identical, because one will work on 5 ×5 matrices and one will work on
10 ×10 matrices, but other than the constants 5 and 10, the two functions will be the same. This is a classic way for template-induced code
bloat to arise.
What would you do if you saw two functions that were character-forcharacter identical except for the use of 5 in one version and 10 in the
other? Your instinct would be to create a version of the function that
took a value as a parameter, then call the parameterized function with
5 or 10 instead of replicating the code. Your instinct serves you well!
Here’s a first pass at doing that for SquareMatrix:
template<typename T>
// size-independent base class for
class SquareMatrixBase {
// square matrices
protected:
...
void invert(std::size_t matrixSize);
// invert matrix of the given size
...
};
template<typename T, std::size_t n>
class SquareMatrix: private SquareMatrixBase<T> {
private:
using SquareMatrixBase<T>::invert; // make base class version of invert
// visible in this class; see Items 33
// and 43
public:
...
void invert() { invert(n); }
// make inline call to base class
};
// version of invert
As you can see, the parameterized version of invert is in a base class,
SquareMatrixBase. Like SquareMatrix, SquareMatrixBase is a template, but
unlike SquareMatrix, it’s templatized only on the type of objects in the
matrix, not on the size of the matrix. Hence, all matrices holding a
given type of object will share a single SquareMatrixBase class. They will
thus share a single copy of that class’s version of invert. (Provided, of
course, you refrain from declaring that function inline. If it’s inlined,
each instantiation of SquareMatrix::invert will get a copy of SquareMatrixBase::invert’s code (see Item 30), and you’ll find yourself back in the
land of object code replication.)
SquareMatrixBase::invert is intended only to be a way for derived classes
to avoid code replication, so it’s protected instead of being public. The
additional cost of calling it should be zero, because derived classes’
inverts call the base class version using inline functions. (The inline is
implicit — see Item 30.) Notice also that the inheritance between
SquareMatrix and SquareMatrixBase is private. This accurately reflects
the fact that the reason for the base class is only to facilitate the
Templates and Generic Programming
Item 44 215A
derived classes’ implementations, not to express a conceptual is-a
relationship between SquareMatrix and SquareMatrixBase. (For information on private inheritance, see Item 39.)
So far, so good, but there’s a sticky issue we haven’t addressed yet.
How does SquareMatrixBase::invert know what data to operate on? It
knows the size of the matrix from its parameter, but how does it know
where the data for a particular matrix is? Presumably only the derived
class knows that. How does the derived class communicate that to the
base class so that the base class can do the inversion?
One possibility would be to add another parameter to SquareMatrixBase::invert, perhaps a pointer to the beginning of a chunk of memory
with the matrix’s data in it. That would work, but in all likelihood,
invert is not the only function in SquareMatrix that can be written in a
size-independent manner and moved into SquareMatrixBase. If there
are several such functions, all will need a way to find the memory
holding the values in the matrix. We could add an extra parameter to
all of them, but we’d be telling SquareMatrixBase the same information
repeatedly. That seems wrong.
An alternative is to have SquareMatrixBase store a pointer to the memory for the matrix values. And as long as it’s storing that, it might as
well store the matrix size, too. The resulting design looks like this:
template<typename T>
class SquareMatrixBase {
protected:
SquareMatrixBase(std::size_t n, T *pMem)
: size(n), pData(pMem) {}
void setDataPtr(T *ptr) { pData = ptr; }
...
private:
std::size_t size;
T *pData;
};
// store matrix size and a
// ptr to matrix values
// reassign pData
// size of matrix
// pointer to matrix values
This lets derived classes decide how to allocate the memory. Some
implementations might decide to store the matrix data right inside the
SquareMatrix object:
template<typename T, std::size_t n>
class SquareMatrix: private SquareMatrixBase<T> {
public:
SquareMatrix()
// send matrix size and
: SquareMatrixBase<T>(n, data) {}
// data ptr to base class
...
private:
T data[n*n];
};
216A Item 44
Chapter 7
Objects of such types have no need for dynamic memory allocation,
but the objects themselves could be very large. An alternative would
be to put the data for each matrix on the heap:
template<typename T, std::size_t n>
class SquareMatrix: private SquareMatrixBase<T> {
public:
SquareMatrix()
// set base class data ptr to null,
: SquareMatrixBase<T>(n, 0),
// allocate memory for matrix
pData(new T[n*n])
// values, save a ptr to the
{ this->setDataPtr(pData.get()); }
// memory, and give a copy of it
...
// to the base class
private:
boost::scoped_array<T> pData;
// see Item 13 for info on
};
// boost::scoped_array
Regardless of where the data is stored, the key result from a bloat
point of view is that now many — maybe all — of SquareMatrix’s member functions can be simple inline calls to (non-inline) base class versions that are shared with all other matrices holding the same type of
data, regardless of their size. At the same time, SquareMatrix objects of
different sizes are distinct types, so even though, e.g.,
SquareMatrix<double, 5> and SquareMatrix<double, 10> objects use the
same member functions in SquareMatrixBase<double>, there’s no
chance of passing a SquareMatrix<double, 5> object to a function expecting a SquareMatrix<double, 10>. Nice, no?
Nice, yes, but not free. The versions of invert with the matrix sizes
hardwired into them are likely to generate better code than the shared
version where the size is passed as a function parameter or is stored
in the object. For example, in the size-specific versions, the sizes
would be compile-time constants, hence eligible for such optimizations as constant propagation, including their being folded into the
generated instructions as immediate operands. That can’t be done in
the size-independent version.
On the other hand, having only one version of invert for multiple
matrix sizes decreases the size of the executable, and that could
reduce the program’s working set size and improve locality of reference in the instruction cache. Those things could make the program
run faster, more than compensating for any lost optimizations in sizespecific versions of invert. Which effect would dominate? The only way
to know is to try it both ways and observe the behavior on your particular platform and on representative data sets.
Another efficiency consideration concerns the sizes of objects. If
you’re not careful, moving size-independent versions of functions up
into a base class can increase the overall size of each object. For
Templates and Generic Programming
Item 44 217A
example, in the code I just showed, each SquareMatrix object has a
pointer to its data in the SquareMatrixBase class, even though each
derived class already has a way to get to the data. This increases the
size of each SquareMatrix object by at least the size of a pointer. It’s
possible to modify the design so that these pointers are unnecessary,
but, again, there are trade-offs. For example, having the base class
store a protected pointer to the matrix data leads to the loss of encapsulation described in Item 22. It can also lead to resource management complications: if the base class stores a pointer to the matrix
data, but that data may have been either dynamically allocated or
physically stored inside the derived class object (as we saw), how will
it be determined whether the pointer should be deleted? Such questions have answers, but the more sophisticated you try to be about
them, the more complicated things become. At some point, a little
code replication begins to look like a mercy.
This Item has discussed only bloat due to non-type template parameters, but type parameters can lead to bloat, too. For example, on many
platforms, int and long have the same binary representation, so the
member functions for, say, vector<int> and vector<long> would likely be
identical — the very definition of bloat. Some linkers will merge identical function implementations, but some will not, and that means that
some templates instantiated on both int and long could cause code
bloat in some environments. Similarly, on most platforms, all pointer
types have the same binary representation, so templates holding
pointer types (e.g., list<int*>, list<const int*>, list<SquareMatrix<long, 3>*>,
etc.) should often be able to use a single underlying implementation for
each member function. Typically, this means implementing member
functions that work with strongly typed pointers (i.e., T* pointers) by
having them call functions that work with untyped pointers (i.e., void*
pointers). Some implementations of the standard C++ library do this
for templates like vector, deque, and list. If you’re concerned about code
bloat arising in your templates, you’ll probably want to develop templates that do the same thing.
Things to Remember
✦
Templates generate multiple classes and multiple functions, so any
template code not dependent on a template parameter causes bloat.
✦
Bloat due to non-type template parameters can often be eliminated
by replacing template parameters with function parameters or class
data members.
✦
Bloat due to type parameters can be reduced by sharing implementations for instantiation types with identical binary representations.
218A Item 45
Chapter 7
Item 45: Use member function templates to accept
“all compatible types.”
Smart pointers are objects that act much like pointers but add functionality pointers don’t provide. For example, Item 13 explains how
the standard auto_ptr and tr1::shared_ptr can be used to automatically
delete heap-based resources at the right time. Iterators into STL containers are almost always smart pointers; certainly you couldn’t
expect to move a built-in pointer from one node in a linked list to the
next by using “ ++ ,” yet that works for list::iterators.
One of the things that real pointers do well is support implicit conversions. Derived class pointers implicitly convert into base class pointers, pointers to non-const objects convert into pointers to const
objects, etc. For example, consider some conversions that can occur
in a three-level hierarchy:
class Top { ... };
class Middle: public Top { ... };
class Bottom: public Middle { ... };
Top *pt1 = new Middle;
Top *pt2 = new Bottom;
const Top *pct2 = pt1;
// convert Middle* ⇒ Top*
// convert Bottom* ⇒ Top*
// convert Top* ⇒ const Top*
Emulating such conversions in user-defined smart pointer classes is
tricky. We’d need the following code to compile:
template<typename T>
class SmartPtr {
public:
explicit SmartPtr(T *realPtr);
...
};
SmartPtr<Top> pt1 =
SmartPtr<Middle>(new Middle);
SmartPtr<Top> pt2 =
SmartPtr<Bottom>(new Bottom);
SmartPtr<const Top> pct2 = pt1;
// smart pointers are typically
// initialized by built-in pointers
// convert SmartPtr<Middle> ⇒
// SmartPtr<Top>
// convert SmartPtr<Bottom> ⇒
// SmartPtr<Top>
// convert SmartPtr<Top> ⇒
// SmartPtr<const Top>
There is no inherent relationship among different instantiations of the
same template, so compilers view SmartPtr<Middle> and SmartPtr<Top>
as completely different classes, no more closely related than, say, vector<float> and Widget. To get the conversions among SmartPtr classes
that we want, we have to program them explicitly.
Templates and Generic Programming
Item 45 219A
In the smart pointer sample code above, each statement creates a new
smart pointer object, so for now we’ll focus on how to write smart
pointer constructors that behave the way we want. A key observation
is that there is no way to write out all the constructors we need. In the
hierarchy above, we can construct a SmartPtr<Top> from a SmartPtr<Middle> or a SmartPtr<Bottom>, but if the hierarchy is extended in the
future, SmartPtr<Top> objects will have to be constructible from other
smart pointer types. For example, if we later add
class BelowBottom: public Bottom { ... };
we’ll need to support the creation of SmartPtr<Top> objects from SmartPtr<BelowBottom> objects, and we certainly won’t want to have to modify the SmartPtr template to do it.
In principle, the number of constructors we need is unlimited. Since a
template can be instantiated to generate an unlimited number of
functions, it seems that we don’t need a constructor function for SmartPtr, we need a constructor template. Such templates are examples of
member function templates (often just known as member templates) —
templates that generate member functions of a class:
template<typename T>
class SmartPtr {
public:
template<typename U>
SmartPtr(const SmartPtr<U>& other);
...
};
// member template
// for a ”generalized
// copy constructor”
This says that for every type T and every type U, a SmartPtr<T> can be
created from a SmartPtr<U>, because SmartPtr<T> has a constructor
that takes a SmartPtr<U> parameter. Constructors like this — ones
that create one object from another object whose type is a different
instantiation of the same template (e.g., create a SmartPtr<T> from a
SmartPtr<U>) — are sometimes known as generalized copy constructors.
The generalized copy constructor above is not declared explicit. That’s
deliberate. Type conversions among built-in pointer types (e.g., from
derived to base class pointers) are implicit and require no cast, so it’s
reasonable for smart pointers to emulate that behavior. Omitting
explicit on the templatized constructor does just that.
As declared, the generalized copy constructor for SmartPtr offers more
than we want. Yes, we want to be able to create a SmartPtr<Top> from a
SmartPtr<Bottom>, but we don’t want to be able to create a SmartPtr<Bottom> from a SmartPtr<Top>, as that’s contrary to the meaning of
public inheritance (see Item 32). We also don’t want to be able to cre-
220A Item 45
Chapter 7
ate a SmartPtr<int> from a SmartPtr<double>, because there is no corresponding implicit conversion from int* to double*. Somehow, we have
to cull the herd of member functions that this member template will
generate.
Assuming that SmartPtr follows the lead of auto_ptr and tr1::shared_ptr
by offering a get member function that returns a copy of the built-in
pointer held by the smart pointer object (see Item 15), we can use the
implementation of the constructor template to restrict the conversions
to those we want:
template<typename T>
class SmartPtr {
public:
template<typename U>
SmartPtr(const SmartPtr<U>& other)
: heldPtr(other.get()) { ... }
T* get() const { return heldPtr; }
...
private:
T *heldPtr;
};
// initialize this held ptr
// with other’s held ptr
// built-in pointer held
// by the SmartPtr
We use the member initialization list to initialize SmartPtr<T>’s data
member of type T* with the pointer of type U* held by the SmartPtr<U>.
This will compile only if there is an implicit conversion from a U*
pointer to a T* pointer, and that’s precisely what we want. The net
effect is that SmartPtr<T> now has a generalized copy constructor that
will compile only if passed a parameter of a compatible type.
The utility of member function templates isn’t limited to constructors.
Another common role for them is in support for assignment. For
example, TR1’s shared_ptr (again, see Item 13) supports construction
from all compatible built-in pointers, tr1::shared_ptrs, auto_ptrs, and
tr1::weak_ptrs (see Item 54), as well as assignment from all of those
except tr1::weak_ptrs. Here’s an excerpt from TR1’s specification for
tr1::shared_ptr, including its penchant for using class instead of typename when declaring template parameters. (As Item 42 explains, they
mean exactly the same thing in this context.)
template<class T> class shared_ptr {
public:
template<class Y>
explicit shared_ptr(Y * p);
template<class Y>
shared_ptr(shared_ptr<Y> const& r);
template<class Y>
explicit shared_ptr(weak_ptr<Y> const& r);
template<class Y>
explicit shared_ptr(auto_ptr<Y>& r);
// construct from
// any compatible
// built-in pointer,
// shared_ptr,
// weak_ptr, or
// auto_ptr
Templates and Generic Programming
template<class Y>
shared_ptr& operator=(shared_ptr<Y> const& r);
template<class Y>
shared_ptr& operator=(auto_ptr<Y>& r);
...
Item 45 221A
// assign from
// any compatible
// shared_ptr or
// auto_ptr
};
All these constructors are explicit, except the generalized copy constructor. That means that implicit conversion from one type of
shared_ptr to another is allowed, but implicit conversion from a built-in
pointer or other smart pointer type is not permitted. (Explicit conversion — e.g., via a cast — is okay.) Also interesting is how the auto_ptrs
passed to tr1::shared_ptr constructors and assignment operators aren’t
declared const, in contrast to how the tr1::shared_ptrs and tr1::weak_ptrs
are passed. That’s a consequence of the fact that auto_ptrs stand alone
in being modified when they’re copied (see Item 13).
Member function templates are wonderful things, but they don’t alter
the basic rules of the language. Item 5 explains that two of the four
member functions that compilers may generate are the copy constructor and the copy assignment operator. tr1::shared_ptr declares a generalized copy constructor, and it’s clear that when the types T and Y are
the same, the generalized copy constructor could be instantiated to
create the “normal” copy constructor. So will compilers generate a
copy constructor for tr1::shared_ptr, or will they instantiate the generalized copy constructor template when one tr1::shared_ptr object is constructed from another tr1::shared_ptr object of the same type?
As I said, member templates don’t change the rules of the language,
and the rules state that if a copy constructor is needed and you don’t
declare one, one will be generated for you automatically. Declaring a
generalized copy constructor (a member template) in a class doesn’t
keep compilers from generating their own copy constructor (a nontemplate), so if you want to control all aspects of copy construction,
you must declare both a generalized copy constructor as well as the
“normal” copy constructor. The same applies to assignment. Here’s an
excerpt from tr1::shared_ptr’s definition that exemplifies this:
template<class T> class shared_ptr {
public:
shared_ptr(shared_ptr const& r);
template<class Y>
shared_ptr(shared_ptr<Y> const& r);
shared_ptr& operator=(shared_ptr const& r);
template<class Y>
shared_ptr& operator=(shared_ptr<Y> const& r);
...
};
// copy constructor
// generalized
// copy constructor
// copy assignment
// generalized
// copy assignment
222A Item 46
Chapter 7
Things to Remember
✦
Use member function templates to generate functions that accept all
compatible types.
✦
If you declare member templates for generalized copy construction
or generalized assignment, you’ll still need to declare the normal
copy constructor and copy assignment operator, too.
Item 46: Define non-member functions inside
templates when type conversions are desired.
Item 24 explains why only non-member functions are eligible for
implicit type conversions on all arguments, and it uses as an example
the operator* function for a Rational class. I recommend you familiarize
yourself with that example before continuing, because this Item
extends the discussion with a seemingly innocuous modification to
Item 24’s example: it templatizes both Rational and operator*:
template<typename T>
class Rational {
public:
Rational(const T& numerator = 0,
// see Item 20 for why params
const T& denominator = 1); // are now passed by reference
const T numerator() const;
// see Item 28 for why return
const T denominator() const;
// values are still passed by value,
...
// Item 3 for why they’re const
};
template<typename T>
const Rational<T> operator*(const Rational<T>& lhs,
const Rational<T>& rhs)
{ ... }
As in Item 24, we want to support mixed-mode arithmetic, so we want
the code below to compile. We expect that it will, because we’re using
the same code that works in Item 24. The only difference is that Rational and operator* are now templates:
Rational<int> oneHalf(1, 2);
Rational<int> result = oneHalf * 2;
// this example is from Item 24,
// except Rational is now a template
// error! won’t compile
The fact that this fails to compile suggests that there’s something
about the templatized Rational that’s different from the non-template
version, and indeed there is. In Item 24, compilers know what function we’re trying to call (operator* taking two Rationals), but here, compilers do not know which function we want to call. Instead, they’re
Templates and Generic Programming
Item 46 223A
trying to figure out what function to instantiate (i.e., create) from the
template named operator*. They know that they’re supposed to instantiate some function named operator* taking two parameters of type
Rational<T>, but in order to do the instantiation, they have to figure
out what T is. The problem is, they can’t.
In attempting to deduce T, they look at the types of the arguments
being passed in the call to operator*. In this case, those types are Rational<int> (the type of oneHalf) and int (the type of 2). Each parameter is
considered separately.
The deduction using oneHalf is easy. operator*’s first parameter is
declared to be of type Rational<T>, and the first argument passed to
operator* (oneHalf) is of type Rational<int>, so T must be int. Unfortunately, the deduction for the other parameter is not so simple. operator*’s second parameter is declared to be of type Rational<T>, but the
second argument passed to operator* (2) is of type int. How are compilers to figure out what T is in this case? You might expect them to use
Rational<int>’s non-explicit constructor to convert 2 into a Rational<int>,
thus allowing them to deduce that T is int, but they don’t do that. They
don’t, because implicit type conversion functions are never considered
during template argument deduction. Never. Such conversions are
used during function calls, yes, but before you can call a function,
you have to know which functions exist. In order to know that, you
have to deduce parameter types for the relevant function templates (so
that you can instantiate the appropriate functions). But implicit type
conversion via constructor calls is not considered during template
argument deduction. Item 24 involves no templates, so template argument deduction is not an issue. Now that we’re in the template part of
C++ (see Item 1), it’s the primary issue.
We can relieve compilers of the challenge of template argument deduction by taking advantage of the fact that a friend declaration in a template class can refer to a specific function. That means the class
Rational<T> can declare operator* for Rational<T> as a friend function.
Class templates don’t depend on template argument deduction (that
process applies only to function templates), so T is always known at
the time the class Rational<T> is instantiated. That makes it easy for
the Rational<T> class to declare the appropriate operator* function as a
friend:
template<typename T>
class Rational {
public:
...
224A Item 46
friend
const Rational operator*(const Rational& lhs,
const Rational& rhs);
};
template<typename T>
const Rational<T> operator*(const Rational<T>& lhs,
const Rational<T>& rhs)
{ ... }
Chapter 7
// declare operator*
// function (see
// below for details)
// define operator*
// functions
Now our mixed-mode calls to operator* will compile, because when the
object oneHalf is declared to be of type Rational<int>, the class Rational<int> is instantiated, and as part of that process, the friend function operator* that takes Rational<int> parameters is automatically
declared. As a declared function (not a function template), compilers
can use implicit conversion functions (such as Rational’s non-explicit
constructor) when calling it, and that’s how they make the mixedmode call succeed.
Alas, “succeed” is a funny word in this context, because although the
code will compile, it won’t link. We’ll deal with that in a moment, but
first I want to remark on the syntax used to declare operator* inside
Rational.
Inside a class template, the name of the template can be used as
shorthand for the template and its parameters, so inside Rational<T>,
we can just write Rational instead of Rational<T>. That saves us only a
few characters in this example, but when there are multiple parameters or longer parameter names, it can both save typing and make the
resulting code clearer. I bring this up, because operator* is declared
taking and returning Rationals instead of Rational<T>s. It would have
been just as valid to declare operator* like this:
template<typename T>
class Rational {
public:
...
friend
const Rational<T> operator*(const Rational<T>& lhs,
const Rational<T>& rhs);
...
};
However, it’s easier (and more common) to use the shorthand form.
Now back to the linking problem. The mixed-mode code compiles,
because compilers know that we want to call a specific function (operator* taking a Rational<int> and a Rational<int>), but that function is
only declared inside Rational, not defined there. Our intent is to have
Templates and Generic Programming
Item 46 225A
the operator* template outside the class provide that definition, but
things don’t work that way. If we declare a function ourselves (which
is what we’re doing inside the Rational template), we’re also responsible for defining that function. In this case, we never provide a definition, and that’s why linkers can’t find one.
The simplest thing that could possibly work is to merge the body of
operator* into its declaration:
template<typename T>
class Rational {
public:
...
friend const Rational operator*(const Rational& lhs, const Rational& rhs)
{
return Rational( lhs.numerator() * rhs.numerator(),
// same impl
lhs.denominator() * rhs.denominator()); // as in
}
// Item 24
};
Indeed, this works as intended: mixed-mode calls to operator* now
compile, link, and run. Hooray!
An interesting observation about this technique is that the use of
friendship has nothing to do with a need to access non-public parts of
the class. In order to make type conversions possible on all arguments, we need a non-member function (Item 24 still applies); and in
order to have the proper function automatically instantiated, we need
to declare the function inside the class. The only way to declare a nonmember function inside a class is to make it a friend. So that’s what
we do. Unconventional? Yes. Effective? Without a doubt.
As Item 30 explains, functions defined inside a class are implicitly
declared inline, and that includes friend functions like operator*. You
can minimize the impact of such inline declarations by having operator* do nothing but call a helper function defined outside of the class.
In the example in this Item, there’s not much point in doing that,
because operator* is already implemented as a one-line function, but
for more complex function bodies, it may be desirable. It’s worth taking a look at the “have the friend call a helper” approach.
The fact that Rational is a template means that the helper function will
usually also be a template, so the code in the header file defining
Rational will typically look something like this:
template<typename T> class Rational;
// declare
// Rational
// template
226A Item 47
template<typename T>
const Rational<T> doMultiply( const Rational<T>& lhs,
const Rational<T>& rhs);
template<typename T>
class Rational {
public:
...
friend
const Rational<T> operator*(const Rational<T>& lhs,
const Rational<T>& rhs)
{ return doMultiply(lhs, rhs); }
...
};
Chapter 7
// declare
// helper
// template
// Have friend
// call helper
Many compilers essentially force you to put all template definitions in
header files, so you may need to define doMultiply in your header as
well. (As Item 30 explains, such templates need not be inline.) That
could look like this:
template<typename T>
// define
const Rational<T> doMultiply(const Rational<T>& lhs,
// helper
const Rational<T>& rhs)
// template in
{
// header file,
return Rational<T>(lhs.numerator() * rhs.numerator(),
// if necessary
lhs.denominator() * rhs.denominator());
}
As a template, of course, doMultiply won’t support mixed-mode multiplication, but it doesn’t need to. It will only be called by operator*, and
operator* does support mixed-mode operations! In essence, the function operator* supports whatever type conversions are necessary to
ensure that two Rational objects are being multiplied, then it passes
these two objects to an appropriate instantiation of the doMultiply template to do the actual multiplication. Synergy in action, no?
Things to Remember
✦
When writing a class template that offers functions related to the
template that support implicit type conversions on all parameters,
define those functions as friends inside the class template.
Item 47: Use traits classes for information about types.
The STL is primarily made up of templates for containers, iterators,
and algorithms, but it also has a few utility templates. One of these is
called advance. advance moves a specified iterator a specified distance:
Templates and Generic Programming
template<typename IterT, typename DistT>
void advance(IterT& iter, DistT d);
Item 47 227A
// move iter d units
// forward; if d < 0,
// move iter backward
Conceptually, advance just does iter += d, but advance can’t be implemented that way, because only random access iterators support the
+= operation. Less powerful iterator types have to implement advance
by iteratively applying ++ or -- d times.
Um, you don’t remember your STL iterator categories? No problem,
we’ll do a mini-review. There are five categories of iterators, corresponding to the operations they support. Input iterators can move only
forward, can move only one step at a time, can only read what they
point to, and can read what they’re pointing to only once. They’re
modeled on the read pointer into an input file; the C++ library’s
istream_iterators are representative of this category. Output iterators
are analogous, but for output: they move only forward, move only one
step at a time, can only write what they point to, and can write it only
once. They’re modeled on the write pointer into an output file;
ostream_iterators epitomize this category. These are the two least powerful iterator categories. Because input and output iterators can move
only forward and can read or write what they point to at most once,
they are suitable only for one-pass algorithms.
A more powerful iterator category consists of forward iterators. Such
iterators can do everything input and output iterators can do, plus
they can read or write what they point to more than once. This makes
them viable for multi-pass algorithms. The STL offers no singly linked
list, but some libraries offer one (usually called slist), and iterators into
such containers are forward iterators. Iterators into TR1’s hashed
containers (see Item 54) may also be in the forward category.
Bidirectional iterators add to forward iterators the ability to move
backward as well as forward. Iterators for the STL’s list are in this category, as are iterators for set, multiset, map, and multimap.
The most powerful iterator category is that of random access iterators.
These kinds of iterators add to bidirectional iterators the ability to perform “iterator arithmetic,” i.e., to jump forward or backward an arbitrary distance in constant time. Such arithmetic is analogous to
pointer arithmetic, which is not surprising, because random access
iterators are modeled on built-in pointers, and built-in pointers can
act as random access iterators. Iterators for vector, deque, and string
are random access iterators.
For each of the five iterator categories, C++ has a “tag struct” in the
standard library that serves to identify it:
228A Item 47
Chapter 7
struct input_iterator_tag {};
struct output_iterator_tag {};
struct forward_iterator_tag: public input_iterator_tag {};
struct bidirectional_iterator_tag: public forward_iterator_tag {};
struct random_access_iterator_tag: public bidirectional_iterator_tag {};
The inheritance relationships among these structs are valid is-a relationships (see Item 32): it’s true that all forward iterators are also
input iterators, etc. We’ll see the utility of this inheritance shortly.
But back to advance. Given the different iterator capabilities, one way
to implement advance would be to use the lowest-common-denominator strategy of a loop that iteratively increments or decrements the
iterator. However, that approach would take linear time. Random
access iterators support constant-time iterator arithmetic, and we’d
like to take advantage of that ability when it’s present.
What we really want to do is implement advance essentially like this:
template<typename IterT, typename DistT>
void advance(IterT& iter, DistT d)
{
if (iter is a random access iterator) {
iter += d;
}
else {
if (d >= 0) { while (d--) ++iter; }
else { while (d++) --iter; }
}
}
// use iterator arithmetic
// for random access iters
// use iterative calls to
// ++ or -- for other
// iterator categories
This requires being able to determine whether iter is a random access
iterator, which in turn requires knowing whether its type, IterT, is a
random access iterator type. In other words, we need to get some
information about a type. That’s what traits let you do: they allow you
to get information about a type during compilation.
Traits aren’t a keyword or a predefined construct in C++; they’re a
technique and a convention followed by C++ programmers. One of the
demands made on the technique is that it has to work as well for
built-in types as it does for user-defined types. For example, if advance
is called with a pointer (like a const char*) and an int, advance has to
work, but that means that the traits technique must apply to built-in
types like pointers.
The fact that traits must work with built-in types means that things
like nesting information inside types won’t do, because there’s no way
to nest information inside pointers. The traits information for a type,
then, must be external to the type. The standard technique is to put it
Templates and Generic Programming
Item 47 229A
into a template and one or more specializations of that template. For
iterators, the template in the standard library is named iterator_traits:
template<typename IterT>
struct iterator_traits;
// template for information about
// iterator types
As you can see, iterator_traits is a struct. By convention, traits are
always implemented as structs. Another convention is that the structs
used to implement traits are known as — I am not making this up —
traits classes.
The way iterator_traits works is that for each type IterT, a typedef named
iterator_category is declared in the struct iterator_traits<IterT>. This
typedef identifies the iterator category of IterT.
iterator_traits implements this in two parts. First, it imposes the
requirement that any user-defined iterator type must contain a nested
typedef named iterator_category that identifies the appropriate tag
struct. deque’s iterators are random access, for example, so a class for
deque iterators would look something like this:
template < ... >
// template params elided
class deque {
public:
class iterator {
public:
typedef random_access_iterator_tag iterator_category;
...
};
...
};
list’s iterators are bidirectional, however, so they’d do things this way:
template < ... >
class list {
public:
class iterator {
public:
typedef bidirectional_iterator_tag iterator_category;
...
};
...
};
iterator_traits just parrots back the iterator class’s nested typedef:
// the iterator_category for type IterT is whatever IterT says it is;
// see Item 42 for info on the use of “typedef typename”
template<typename IterT>
struct iterator_traits {
typedef typename IterT::iterator_category iterator_category;
...
};
230A Item 47
Chapter 7
This works well for user-defined types, but it doesn’t work at all for
iterators that are pointers, because there’s no such thing as a pointer
with a nested typedef. The second part of the iterator_traits implementation handles iterators that are pointers.
To support such iterators, iterator_traits offers a partial template specialization for pointer types. Pointers act as random access iterators,
so that’s the category iterator_traits specifies for them:
template<typename T>
// partial template specialization
struct iterator_traits<T*>
// for built-in pointer types
{
typedef random_access_iterator_tag iterator_category;
...
};
At this point, you know how to design and implement a traits class:
■
Identify some information about types you’d like to make available
(e.g., for iterators, their iterator category).
■
Choose a name to identify that information (e.g., iterator_category).
■
Provide a template and set of specializations (e.g., iterator_traits)
that contain the information for the types you want to support.
Given iterator_traits — actually std::iterator_traits, since it’s part of C++’s
standard library — we can refine our pseudocode for advance:
template<typename IterT, typename DistT>
void advance(IterT& iter, DistT d)
{
if (typeid(typename std::iterator_traits<IterT>::iterator_category) ==
typeid(std::random_access_iterator_tag))
...
}
Although this looks promising, it’s not what we want. For one thing, it
will lead to compilation problems, but we’ll explore that in Item 48;
right now, there’s a more fundamental issue to consider. IterT’s type is
known during compilation, so iterator_traits<IterT>::iterator_category can
also be determined during compilation. Yet the if statement is evaluated at runtime (unless your optimizer is crafty enough to get rid of it).
Why do something at runtime that we can do during compilation? It
wastes time (literally), and it bloats our executable.
What we really want is a conditional construct (i.e., an if...else statement) for types that is evaluated during compilation. As it happens,
C++ already has a way to get that behavior. It’s called overloading.
When you overload some function f, you specify different parameter
types for the different overloads. When you call f, compilers pick the
Templates and Generic Programming
Item 47 231A
best overload, based on the arguments you’re passing. Compilers
essentially say, “If this overload is the best match for what’s being
passed, call this f; if this other overload is the best match, call it; if
this third one is best, call it,” etc. See? A compile-time conditional
construct for types. To get advance to behave the way we want, all we
have to do is create multiple versions of an overloaded function containing the “guts” of advance, declaring each to take a different type of
iterator_category object. I use the name doAdvance for these functions:
template<typename IterT, typename DistT>
void doAdvance(IterT& iter, DistT d,
std::random_access_iterator_tag)
{
iter += d;
}
template<typename IterT, typename DistT>
void doAdvance(IterT& iter, DistT d,
std::bidirectional_iterator_tag)
{
if (d >= 0) { while (d--) ++iter; }
else { while (d++) --iter; }
}
template<typename IterT, typename DistT>
void doAdvance(IterT& iter, DistT d,
std::input_iterator_tag)
{
if (d < 0 ) {
throw std::out_of_range("Negative distance");
}
while (d--) ++iter;
}
// use this impl for
// random access
// iterators
// use this impl for
// bidirectional
// iterators
// use this impl for
// input iterators
// see below
Because forward_iterator_tag inherits from input_iterator_tag, the version of doAdvance for input_iterator_tag will also handle forward iterators. That’s the motivation for inheritance among the various
iterator_tag structs. (In fact, it’s part of the motivation for all public
inheritance: to be able to write code for base class types that also
works for derived class types.)
The specification for advance allows both positive and negative distances for random access and bidirectional iterators, but behavior is
undefined if you try to move a forward or input iterator a negative distance. The implementations I checked simply assumed that d was
non-negative, thus entering a very long loop counting “down” to zero if
a negative distance was passed in. In the code above, I’ve shown an
exception being thrown instead. Both implementations are valid.
That’s the curse of undefined behavior: you can’t predict what will
happen.
232A Item 47
Chapter 7
Given the various overloads for doAdvance, all advance needs to do is
call them, passing an extra object of the appropriate iterator category
type so that the compiler will use overloading resolution to call the
proper implementation:
template<typename IterT, typename DistT>
void advance(IterT& iter, DistT d)
{
doAdvance(
iter, d,
typename
std::iterator_traits<IterT>::iterator_category()
);
}
// call the version
// of doAdvance
// that is
// appropriate for
// iter’s iterator
// category
We can now summarize how to use a traits class:
■
Create a set of overloaded “worker” functions or function templates (e.g., doAdvance) that differ in a traits parameter. Implement
each function in accord with the traits information passed.
■
Create a “master” function or function template (e.g., advance) that
calls the workers, passing information provided by a traits class.
Traits are widely used in the standard library. There’s iterator_traits, of
course, which, in addition to iterator_category, offers four other pieces
of information about iterators (the most useful of which is value_type
— Item 42 shows an example of its use). There’s also char_traits, which
holds information about character types, and numeric_limits, which
serves up information about numeric types, e.g., their minimum and
maximum representable values, etc. (The name numeric_limits is a bit
of a surprise, because the more common convention is for traits
classes to end with “traits,” but numeric_limits is what it’s called, so
numeric_limits is the name we use.)
TR1 (see Item 54) introduces a slew of new traits classes that give information about types, including is_fundamental<T> (whether T is a built-in
type), is_array<T> (whether T is an array type), and is_base_of<T1, T2>
(whether T1 is the same as or is a base class of T2). All told, TR1 adds
over 50 traits classes to standard C++.
Things to Remember
✦
Traits classes make information about types available during compilation. They’re implemented using templates and template specializations.
✦
In conjunction with overloading, traits classes make it possible to
perform compile-time if...else tests on types.
Templates and Generic Programming
Item 48 233A
Item 48: Be aware of template metaprogramming.
Template metaprogramming (TMP) is the process of writing templatebased C++ programs that execute during compilation. Think about
that for a minute: a template metaprogram is a program written in
C++ that executes inside the C++ compiler. When a TMP program finishes running, its output — pieces of C++ source code instantiated
from templates — is then compiled as usual.
If this doesn’t strike you as just plain bizarre, you’re not thinking
about it hard enough.
C++ was not designed for template metaprogramming, but since TMP
was discovered in the early 1990s, it has proven to be so useful,
extensions are likely to be added to both the language and its standard library to make TMP easier. Yes, TMP was discovered, not
invented. The features underlying TMP were introduced when templates were added to C++. All that was needed was for somebody to
notice how they could be used in clever and unexpected ways.
TMP has two great strengths. First, it makes some things easy that
would otherwise be hard or impossible. Second, because template
metaprograms execute during C++ compilation, they can shift work
from runtime to compile-time. One consequence is that some kinds of
errors that are usually detected at runtime can be found during compilation. Another is that C++ programs making use of TMP can be
more efficient in just about every way: smaller executables, shorter
runtimes, lesser memory requirements. (However, a consequence of
shifting work from runtime to compile-time is that compilation takes
longer. Programs using TMP may take much longer to compile than
their non-TMP counterparts.)
Consider the pseudocode for STL’s advance introduced on page 228.
(That’s in Item 47. You may want to read that Item now, because in
this Item, I’ll assume you are familiar with the material in that one.)
As on page 228, I’ve highlighted the pseudo part of the code:
template<typename IterT, typename DistT>
void advance(IterT& iter, DistT d)
{
if (iter is a random access iterator) {
iter += d;
}
else {
if (d >= 0) { while (d--) ++iter; }
else { while (d++) --iter; }
}
}
// use iterator arithmetic
// for random access iters
// use iterative calls to
// ++ or -- for other
// iterator categories
234A Item 48
Chapter 7
We can use typeid to make the pseudocode real. That yields a “normal”
C++ approach to this problem — one that does all its work at runtime:
template<typename IterT, typename DistT>
void advance(IterT& iter, DistT d)
{
if ( typeid(typename std::iterator_traits<IterT>::iterator_category) ==
typeid(std::random_access_iterator_tag)) {
iter += d;
// use iterator arithmetic
}
// for random access iters
else {
if (d >= 0) { while (d--) ++iter; }
// use iterative calls to
else { while (d++) --iter; }
// ++ or -- for other
}
// iterator categories
}
Item 47 notes that this typeid-based approach is less efficient than the
one using traits, because with this approach, (1) the type testing
occurs at runtime instead of during compilation, and (2) the code to
do the runtime type testing must be present in the executable. In fact,
this example shows how TMP can be more efficient than a “normal”
C++ program, because the traits approach is TMP. Remember, traits
enable compile-time if...else computations on types.
I remarked earlier that some things are easier in TMP than in “normal” C++, and advance offers an example of that, too. Item 47 mentions that the typeid-based implementation of advance can lead to
compilation problems, and here’s an example where it does:
std::list<int>::iterator iter;
...
advance(iter, 10);
// move iter 10 elements forward;
// won’t compile with above impl.
Consider the version of advance that will be generated for the above
call. After substituting iter’s and 10’s types for the template parameters IterT and DistT, we get this:
void advance(std::list<int>::iterator& iter, int d)
{
if (typeid(std::iterator_traits<std::list<int>::iterator>::iterator_category) ==
typeid(std::random_access_iterator_tag)) {
iter += d;
// error! won’t compile
}
else {
if (d >= 0) { while (d--) ++iter; }
else { while (d++) --iter; }
}
}
Templates and Generic Programming
Item 48 235A
The problem is the highlighted line, the one using +=. In this case,
we’re trying to use += on a list<int>::iterator, but list<int>::iterator is a
bidirectional iterator (see Item 47), so it doesn’t support +=. Only random access iterators support +=. Now, we know we’ll never try to execute the += line, because the typeid test will always fail for
list<int>::iterators, but compilers are obliged to make sure that all
source code is valid, even if it’s not executed, and “iter += d” isn’t valid
when iter isn’t a random access iterator. Contrast this with the traitsbased TMP solution, where code for different types is split into separate functions, each of which uses only operations applicable to the
types for which it is written.
TMP has been shown to be Turing-complete, which means that it is
powerful enough to compute anything. Using TMP, you can declare
variables, perform loops, write and call functions, etc. But such constructs look very different from their “normal” C++ counterparts. For
example, Item 47 shows how if...else conditionals in TMP are expressed
via templates and template specializations. But that’s assembly-level
TMP. Libraries for TMP (e.g., Boost’s MPL — see Item 55) offer a
higher-level syntax, though still not something you’d mistake for “normal” C++.
For another glimpse into how things work in TMP, let’s look at loops.
TMP has no real looping construct, so the effect of loops is accomplished via recursion. (If you’re not comfortable with recursion, you’ll
need to address that before venturing into TMP. It’s largely a functional language, and recursion is to functional languages as TV is to
American pop culture: inseparable.) Even the recursion isn’t the normal kind, however, because TMP loops don’t involve recursive function calls, they involve recursive template instantiations.
The “hello world” program of TMP is computing a factorial during compilation. It’s not a very exciting program, but then again, neither is
“hello world,” yet both are helpful as language introductions. TMP factorial computation demonstrates looping through recursive template
instantiation. It also demonstrates one way in which variables are created and used in TMP. Look:
template<unsigned n>
struct Factorial {
// general case: the value of
// Factorial<n> is n times the value
// of Factorial<n-1>
enum { value = n * Factorial<n-1>::value };
};
template<>
struct Factorial<0> {
enum { value = 1 };
};
// special case: the value of
// Factorial<0> is 1
236A Item 48
Chapter 7
Given this template metaprogram (really just the single template
metafunction Factorial), you get the value of factorial(n) by referring to
Factorial<n>::value.
The looping part of the code occurs where the template instantiation
Factorial<n> references the template instantiation Factorial<n-1>. Like
all good recursion, there’s a special case that causes the recursion to
terminate. Here, it’s the template specialization Factorial<0>.
Each instantiation of the Factorial template is a struct, and each struct
uses the enum hack (see Item 2) to declare a TMP variable named
value. value is what holds the current value of the factorial computation. If TMP had a real looping construct, value would be updated each
time around the loop. Since TMP uses recursive template instantiation in place of loops, each instantiation gets its own copy of value,
and each copy has the proper value for its place in the “loop.”
You could use Factorial like this:
int main()
{
std::cout << Factorial<5>::value;
std::cout << Factorial<10>::value;
}
// prints 120
// prints 3628800
If you think this is cooler than ice cream, you’ve got the makings of a
template metaprogrammer. If the templates and specializations and
recursive instantiations and enum hacks and the need to type things
like Factorial<n-1>::value make your skin crawl, well, you’re a pretty
normal C++ programmer.
Of course, Factorial demonstrates the utility of TMP about as well as
“hello world” demonstrates the utility of any conventional programming language. To grasp why TMP is worth knowing about, it’s important to have a better understanding of what it can accomplish. Here
are three examples:
■
Ensuring dimensional unit correctness. In scientific and engineering applications, it’s essential that dimensional units (e.g.,
mass, distance, time, etc.) be combined correctly. Assigning a variable representing mass to a variable representing velocity, for example, is an error, but dividing a distance variable by a time
variable and assigning the result to a velocity variable is fine. Using TMP, it’s possible to ensure (during compilation) that all dimensional unit combinations in a program are correct, no matter
how complex the calculations. (This is an example of how TMP can
be used for early error detection.) One interesting aspect of this
use of TMP is that fractional dimensional exponents can be sup-
Templates and Generic Programming
Item 48 237A
ported. This requires that such fractions be reduced during compilation so that compilers can confirm, for example, that the unit
time1/2 is the same as time4/8.
■
Optimizing matrix operations. Item 21 explains that some functions, including operator*, must return new objects, and Item 44
introduces the SquareMatrix class, so consider the following code:
typedef SquareMatrix<double, 10000> BigMatrix;
BigMatrix m1, m2, m3, m4, m5;
// create matrices and
...
// give them values
BigMatrix result = m1 * m2 * m3 * m4 * m5;
// compute their product
Calculating result in the “normal” way calls for the creation of four
temporary matrices, one for the result of each call to operator*.
Furthermore, the independent multiplications generate a sequence of four loops over the matrix elements. Using an advanced
template technology related to TMP called expression templates,
it’s possible to eliminate the temporaries and merge the loops, all
without changing the syntax of the client code above. The resulting software uses less memory and runs dramatically faster.
■
Generating custom design pattern implementations. Design
patterns like Strategy (see Item 35), Observer, Visitor, etc. can be
implemented in many ways. Using a TMP-based technology called
policy-based design, it’s possible to create templates representing
independent design choices (“policies”) that can be combined in
arbitrary ways to yield pattern implementations with custom behavior. For example, this technique has been used to allow a few
templates implementing smart pointer behavioral policies to generate (during compilation) any of hundreds of different smart
pointer types. Generalized beyond the domain of programming artifacts like design patterns and smart pointers, this technology is
a basis for what’s known as generative programming.
TMP is not for everybody. The syntax is unintuitive, and tool support
is weak. (Debuggers for template metaprograms? Ha!) Being an “accidental” language that was only relatively recently discovered, TMP
programming conventions are still somewhat experimental. Nevertheless, the efficiency improvements afforded by shifting work from runtime to compile-time can be impressive, and the ability to express
behavior that is difficult or impossible to implement at runtime is
attractive, too.
TMP support is on the rise. It’s likely that the next version of C++ will
provide explicit support for it, and TR1 already does (see Item 54).
Books are beginning to come out on the subject, and TMP information
238A Item 48
Chapter 7
on the web just keeps getting richer. TMP will probably never be mainstream, but for some programmers — especially library developers —
it’s almost certain to be a staple.
Things to Remember
✦
Template metaprogramming can shift work from runtime to compile-time, thus enabling earlier error detection and higher runtime
performance.
✦
TMP can be used to generate custom code based on combinations of
policy choices, and it can also be used to avoid generating code inappropriate for particular types.
Chapter 8: Customizing
new and delete
Customizing
new and delete
In these days of computing environments boasting built-in support for
garbage collection (e.g., Java and .NET), the manual C++ approach to
memory management can look rather old-fashioned. Yet many developers working on demanding systems applications choose C++
because it lets them manage memory manually. Such developers
study the memory usage characteristics of their software, and they
tailor their allocation and deallocation routines to offer the best possible performance (in both time and space) for the systems they build.
Customizing new and delete
Doing that requires an understanding of how C++’s memory management routines behave, and that’s the focus of this chapter. The two
primary players in the game are the allocation and deallocation routines (operator new and operator delete), with a supporting role played
by the new-handler — the function called when operator new can’t satisfy a request for memory.
Memory management in a multithreaded environment poses challenges not present in a single-threaded system, because both the heap
and the new-handler are modifiable global resources, subject to the
race conditions that can bedevil threaded systems. Many Items in this
chapter mention the use of modifiable static data, always something
to put thread-aware programmers on high alert. Without proper synchronization, the use of lock-free algorithms, or careful design to prevent concurrent access, calls to memory routines can lead to baffling
behavior or to corrupted heap management data structures. Rather
than repeatedly remind you of this danger, I’ll mention it here and
assume that you keep it in mind for the rest of the chapter.
Something else to keep in mind is that operator new and operator delete
apply only to allocations for single objects. Memory for arrays is allocated by operator new[] and deallocated by operator delete[]. (In both
cases, note the “[]” part of the function names.) Unless indicated oth-
240A Item 49
Chapter 8
erwise, everything I write about operator new and operator delete also
applies to operator new[] and operator delete[].
Finally, note that heap memory for STL containers is managed by the
containers’ allocator objects, not by new and delete directly. That
being the case, this chapter has nothing to say about STL allocators.
Item 49: Understand the behavior of the new-handler.
When operator new can’t satisfy a memory allocation request, it throws
an exception. Long ago, it returned a null pointer, and some older
compilers still do that. You can still get the old behavior (sort of), but
I’ll defer that discussion until the end of this Item.
Before operator new throws an exception in response to an unsatisfiable request for memory, it calls a client-specifiable error-handling
function called a new-handler. (This is not quite true. What operator
new really does is a bit more complicated. Details are provided in
Item 51.) To specify the out-of-memory-handling function, clients call
set_new_handler, a standard library function declared in <new>:
namespace std {
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, and set_new_handler is a function that
takes and returns a new_handler. (The “throw()” at the end of
set_new_handler’s declaration is an exception specification. It essentially says that this function won’t throw any exceptions, though the
truth is a bit more interesting. For details, see Item 29.)
set_new_handler’s parameter is a pointer to the function operator new
should call if it can’t allocate the requested memory. The return value
of set_new_handler is a pointer to the function in effect for that purpose 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 outOfMem()
{
std::cerr << "Unable to satisfy request for memory\n";
std::abort();
}
Customizing new and delete
Item 49 241A
int main()
{
std::set_new_handler(outOfMem);
int *pBigDataArray = new int[100000000L];
...
}
If operator new is unable to allocate space for 100,000,000 integers,
outOfMem will be called, and the program will abort after issuing an
error message. (By the way, consider what happens if memory must
be dynamically allocated during the course of writing the error message to cerr....)
When operator new is unable to fulfill a memory request, it calls the
new-handler function repeatedly until it can find enough memory. The
code giving rise to these repeated calls is shown in Item 51, but this
high-level description is enough to conclude that a well-designed newhandler function must do one of the following:
■
Make more memory available. This may allow the next memory
allocation attempt inside operator new to succeed. One way to implement this strategy is to allocate a large block of memory at program start-up, then release it for use in the program the first time
the new-handler is invoked.
■
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 can. If so, the current new-handler can install
the other new-handler in its place (by calling set_new_handler). The
next time operator new calls the new-handler function, it will get
the one most recently installed. (A variation on this theme is for a
new-handler to modify its own behavior, so the next time it’s invoked, it does something different. One way to achieve this is to
have the new-handler modify static, namespace-specific, or global
data that affects the new-handler’s behavior.)
■
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 when memory allocation is unsuccessful.
■
Throw an exception of type bad_alloc or some type derived from
bad_alloc. Such exceptions will not be caught by operator new, so
they will propagate to the site originating the request for memory.
■
Not return, typically by calling abort or exit.
These choices give you considerable flexibility in implementing newhandler functions.
242A Item 49
Chapter 8
Sometimes you’d like to handle memory allocation failures in different
ways, depending on the class of the object being allocated:
class X {
public:
static void outOfMemory();
...
};
class Y {
public:
static void outOfMemory();
...
};
X* p1 = new X;
Y* p2 = new Y;
// if allocation is unsuccessful,
// call X::outOfMemory
// if allocation is unsuccessful,
// call Y::outOfMemory
C++ has no support for class-specific new-handlers, but it doesn’t need
any. You can implement this behavior yourself. You just have each
class provide its own versions of set_new_handler and operator new. The
class’s set_new_handler allows clients to specify the new-handler for the
class (exactly like the standard set_new_handler allows clients to specify
the global new-handler). The class’s operator new ensures that the
class-specific new-handler is used in place of the global new-handler
when memory for class objects is allocated.
Suppose you want to handle memory allocation failures for the Widget
class. You’ll have to keep track of the function to call when operator
new can’t allocate enough memory for a Widget object, so you’ll declare
a static member of type new_handler to point to the new-handler function for the class. Widget will look something like this:
class Widget {
public:
static std::new_handler set_new_handler(std::new_handler p) throw();
static void* operator new(std::size_t size) throw(std::bad_alloc);
private:
static std::new_handler currentHandler;
};
Static class members must be defined outside the class definition
(unless they’re const and integral — see Item 2), so:
std::new_handler Widget::currentHandler = 0;
// init to null in the class
// impl. file
Customizing new and delete
Item 49 243A
The set_new_handler function in Widget will save whatever pointer is
passed to it, and it will return whatever pointer had been saved prior
to the call. This is what the standard version of set_new_handler does:
std::new_handler Widget::set_new_handler(std::new_handler p) throw()
{
std::new_handler oldHandler = currentHandler;
currentHandler = p;
return oldHandler;
}
Finally, Widget’s operator new will do the following:
1. Call the standard set_new_handler with Widget’s error-handling
function. This installs Widget’s new-handler as the global newhandler.
2. Call the global operator new to perform the actual memory allocation. If allocation fails, the global operator new invokes Widget’s
new-handler, because that function was just installed as the
global new-handler. If the global operator new is ultimately unable to allocate the memory, it throws a bad_alloc exception. In
that case, Widget’s operator new must restore the original global
new-handler, then propagate the exception. To ensure that the
original new-handler is always reinstated, Widget treats the
global new-handler as a resource and follows the advice of
Item 13 to use resource-managing objects to prevent resource
leaks.
3. If the global operator new was able to allocate enough memory for
a Widget object, Widget’s operator new returns a pointer to the allocated memory. The destructor for the object managing the
global new-handler automatically restores the global new-handler to what it was prior to the call to Widget’s operator new.
Here’s how you say all that in C++. We’ll begin with the resource-handling class, which consists of nothing more than the fundamental
RAII operations of acquiring a resource during construction and
releasing it during destruction (see Item 13):
class NewHandlerHolder {
public:
explicit NewHandlerHolder(std::new_handler nh)
: handler(nh) {}
~NewHandlerHolder()
{ std::set_new_handler(handler); }
private:
std::new_handler handler;
// acquire current
// new-handler
// release it
// remember it
244A Item 49
NewHandlerHolder(const NewHandlerHolder&);
NewHandlerHolder&
operator=(const NewHandlerHolder&);
Chapter 8
// prevent copying
// (see Item 14)
};
This makes implementation of Widget’s operator new quite simple:
void* Widget::operator new(std::size_t size) throw(std::bad_alloc)
{
NewHandlerHolder
// install Widget’s
h(std::set_new_handler(currentHandler));
// new-handler
return ::operator new(size);
// allocate memory
// or throw
}
// restore global
// new-handler
Clients of Widget use its new-handling capabilities like this:
void outOfMem();
// decl. of func. to call if mem. alloc.
// for Widget objects fails
Widget::set_new_handler(outOfMem); // set outOfMem as Widget’s
// new-handling function
Widget *pw1 = new Widget;
// if memory allocation
// fails, call outOfMem
std::string *ps = new std::string;
// if memory allocation fails,
// call the global new-handling
// function (if there is one)
Widget::set_new_handler(0);
// set the Widget-specific
// new-handling function to
// nothing (i.e., null)
Widget *pw2 = new Widget;
// if mem. alloc. fails, throw an
// exception immediately. (There is
// no new- handling function for
// class Widget.)
The code for implementing this scheme is the same regardless of the
class, so a reasonable goal would be to reuse it in other places. An easy
way to make that possible is to create a “mixin-style” base class, i.e., a
base class that’s designed to allow derived classes to inherit a single
specific capability — in this case, the ability to set a class-specific newhandler. Then turn the base class into a template, so that you get a different copy of the class data for each inheriting class.
The base class part of this design lets derived classes inherit the
set_new_handler and operator new functions they all need, while the
template part of the design ensures that each inheriting class gets a
different currentHandler data member. That may sound a bit compli-
Customizing new and delete
Item 49 245A
cated, but the code looks reassuringly familiar. In fact, the only real
difference is that it’s now available to any class that wants it:
template<typename T>
// “mixin-style” base class for
class NewHandlerSupport {
// class-specific set_new_handler
public:
// support
static std::new_handler set_new_handler(std::new_handler p) throw();
static void* operator new(std::size_t size) throw(std::bad_alloc);
...
// other versions of op. new —
// see Item 52
private:
static std::new_handler currentHandler;
};
template<typename T>
std::new_handler
NewHandlerSupport<T>::set_new_handler(std::new_handler p) throw()
{
std::new_handler oldHandler = currentHandler;
currentHandler = p;
return oldHandler;
}
template<typename T>
void* NewHandlerSupport<T>::operator new(std::size_t size)
throw(std::bad_alloc)
{
NewHandlerHolder h(std::set_new_handler(currentHandler));
return ::operator new(size);
}
// this initializes each currentHandler to null
template<typename T>
std::new_handler NewHandlerSupport<T>::currentHandler = 0;
With this class template, adding set_new_handler support to Widget is
easy: Widget just inherits from NewHandlerSupport<Widget>. (That may
look peculiar, but I’ll explain in more detail below exactly what’s going
on.)
class Widget: public NewHandlerSupport<Widget> {
...
// as before, but without declarations for
};
// set_new_handler or operator new
That’s all Widget needs to do to offer a class-specific set_new_handler.
But maybe you’re still fretting over Widget inheriting from NewHandlerSupport<Widget>. If so, your fretting may intensify when you note that
the NewHandlerSupport template never uses its type parameter T. It
doesn’t need to. All we need is a different copy of NewHandlerSupport —
in particular, its static data member currentHandler — for each class
246A Item 49
Chapter 8
that inherits from NewHandlerSupport. The template parameter T just
distinguishes one inheriting class from another. The template mechanism itself automatically generates a copy of currentHandler for each T
with which NewHandlerSupport is instantiated.
As for Widget inheriting from a templatized base class that takes Widget as a type parameter, don’t feel bad if the notion makes you a little
woozy. It initially has that effect on everybody. However, it turns out to
be such a useful technique, it has a name, albeit one that reflects the
fact that it looks natural to no one the first time they see it. It’s called
the curiously recurring template pattern (CRTP). Honest.
At one point, I published an article suggesting that a better name
would be “Do It For Me,” because when Widget inherits from NewHandlerSupport<Widget>, it’s really saying, “I’m Widget, and I want to
inherit from the NewHandlerSupport class for Widget.” Nobody uses my
proposed name (not even me), but thinking about CRTP as a way of
saying “do it for me” may help you understand what the templatized
inheritance is doing.
Templates like NewHandlerSupport make it easy 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 down that path, you’ll want to read Item 40.
Until 1993, C++ required that operator new return null when it was
unable to allocate the requested memory. operator new is now specified to throw a bad_alloc exception, but a lot of C++ was written before
compilers began supporting the revised specification. The C++ standardization committee didn’t want to abandon the test-for-null code
base, so they provided alternative forms of operator new that offer the
traditional failure-yields-null behavior. These forms are called “nothrow” forms, in part because they employ nothrow objects (defined in
the header <new>) at the point where new is used:
class Widget { ... };
Widget *pw1 = new Widget;
if (pw1 == 0) ...
Widget *pw2 = new (std::nothrow) Widget;
if (pw2 == 0) ...
// throws bad_alloc if
// allocation fails
// this test must fail
// returns 0 if allocation for
// the Widget fails
// this test may succeed
Nothrow new offers a less compelling guarantee about exceptions than
is initially apparent. In the expression “new (std::nothrow) Widget,” two
Customizing new and delete
Item 50 247A
things happen. First, the nothrow version of operator new is called to
allocate enough memory for a Widget object. If that allocation fails,
operator new returns the null pointer, just as advertised. If it succeeds,
however, the Widget constructor is called, and at that point, all bets are
off. The Widget constructor can do whatever it likes. It might itself new
up some memory, and if it does, it’s not constrained to use nothrow
new. Although the operator new call in “new (std::nothrow) Widget” won’t
throw, then, the Widget constructor might. If it does, the exception will
be propagated as usual. Conclusion? Using nothrow new guarantees
only that operator new won’t throw, not that an expression like “new
(std::nothrow) Widget” will never yield an exception. In all likelihood, you
will never have a need for nothrow new.
Regardless of whether you use “normal” (i.e., exception-throwing) new
or its somewhat stunted nothrow cousin, it’s important that you
understand the behavior of the new-handler, because it’s used with
both forms.
Things to Remember
✦
set_new_handler allows you to specify a function to be called when
memory allocation requests cannot be satisfied.
✦
Nothrow new is of limited utility, because it applies only to memory
allocation; associated constructor calls may still throw exceptions.
Item 50: Understand when it makes sense to replace
new and delete.
Let’s return to fundamentals for a moment. Why would anybody want
to replace the compiler-provided versions of operator new or operator
delete in the first place? These are three of the most common reasons:
■
To detect usage errors. Failure to delete memory conjured up by
new leads to memory leaks. Using more than one delete on newed
memory yields undefined behavior. If operator new keeps a list of
allocated addresses and operator delete removes addresses from
the list, it’s easy to detect such usage errors. Similarly, a variety of
programming mistakes can lead to data overruns (writing beyond
the end of an allocated block) and underruns (writing prior to the
beginning of an allocated block). Custom operator news can overallocate blocks so there’s room to put known byte patterns (“signatures”) before and after the memory made available to clients.
operator deletes can check to see if the signatures are still intact. If
they’re not, an overrun or underrun occurred sometime during the
248A Item 50
Chapter 8
life of the allocated block, and operator delete can log that fact,
along with the value of the offending pointer.
■
To improve efficiency. The versions of operator new and operator
delete that ship with compilers are designed for general-purpose
use. They have to be acceptable for long-running programs (e.g.,
web servers), but they also have to be acceptable for programs that
execute for less than a second. They have to handle series of requests for large blocks of memory, small blocks, and mixtures of
the two. They have to accommodate allocation patterns ranging
from the dynamic allocation of a few blocks that exist for the duration of the program to constant allocation and deallocation of a
large number of short-lived objects. They have to worry about
heap fragmentation, a process that, if unchecked, eventually leads
to the inability to satisfy requests for large blocks of memory, even
when ample free memory is distributed across many small blocks.
Given the demands made on memory managers, it’s no surprise
that the operator news and operator deletes that ship with compilers
take a middle-of-the-road strategy. They work reasonably well for
everybody, but optimally for nobody. If you have a good understanding of your program’s dynamic memory usage patterns, you
can often find that custom versions of operator new and operator delete outperform the default ones. By “outperform,” I mean they
run faster — sometimes orders of magnitude faster — and they require less memory — up to 50% less. For some (though by no
means all) applications, replacing the stock new and delete with
custom versions is an easy way to pick up significant performance
improvements.
■
To collect usage statistics. Before heading down the path of writing custom news and deletes, it’s prudent to gather information
about how your software uses its dynamic memory. What is the
distribution of allocated block sizes? What is the distribution of
their lifetimes? Do they tend to be allocated and deallocated in
FIFO (“first in, first out”) order, LIFO (“last in, first out”) order, or
something closer to random order? Do the usage patterns change
over time, e.g., does your software have different allocation/deallocation patterns in different stages of execution? What is the maximum amount of dynamically allocated memory in use at any one
time (i.e., its “high water mark”)? Custom versions of operator new
and operator delete make it easy to collect this kind of information.
In concept, writing a custom operator new is pretty easy. For example,
here’s a quick first pass at a global operator new that facilitates the
Customizing new and delete
Item 50 249A
detection of under- and overruns. There are a lot of little things wrong
with it, but we’ll worry about those in a moment.
static const int signature = 0xDEADBEEF;
typedef unsigned char Byte;
// this code has several flaws — see below
void* operator new(std::size_t size) throw(std::bad_alloc)
{
using namespace std;
size_t realSize = size + 2 * sizeof(int); // increase size of request so 2
// signatures will also fit inside
void *pMem = malloc(realSize);
// call malloc to get the actual
if (!pMem) throw bad_alloc();
// memory
// write signature into first and last parts of the memory
*(static_cast<int*>(pMem)) = signature;
*(reinterpret_cast<int*>(static_cast<Byte*>(pMem)+realSize-sizeof(int))) =
signature;
// return a pointer to the memory just past the first signature
return static_cast<Byte*>(pMem) + sizeof(int);
}
Most of the shortcomings of this operator new have to do with its failure to adhere to the C++ conventions for functions of that name. For
example, Item 51 explains that all operator news should contain a loop
calling a new-handling function, but this one doesn’t. However,
Item 51 is devoted to such conventions, so I’ll ignore them here. I
want to focus on a more subtle issue now: alignment.
Many computer architectures require that data of particular types be
placed in memory at particular kinds of addresses. For example, an
architecture might require that pointers occur at addresses that are a
multiple of four (i.e., be four-byte aligned) or that doubles must occur at
addresses that are a multiple of eight (i.e., be eight-byte aligned). Failure to follow such constraints could lead to hardware exceptions at
runtime. Other architectures are more forgiving, though they may offer
better performance if alignment preferences are satisfied. For example,
doubles may be aligned on any byte boundary on the Intel x86 architecture, but access to them is a lot faster if they are eight-byte aligned.
Alignment is relevant here, because C++ requires that all operator news
return pointers that are suitably aligned for any data type. malloc
labors under the same requirement, so having operator new return a
pointer it gets from malloc is safe. However, in operator new above, we’re
not returning a pointer we got from malloc, we’re returning a pointer we
got from malloc offset by the size of an int. There is no guarantee that
this is safe! If the client called operator new to get enough memory for a
250A Item 50
Chapter 8
double (or, if we were writing operator new[], an array of doubles) and we
were running on a machine where ints were four bytes in size but doubles were required to be eight-byte aligned, we’d probably return a
pointer with improper alignment. That might cause the program to
crash. Or it might just cause it to run more slowly. Either way, it’s
probably not what we had in mind.
Details like alignment are the kinds of things that distinguish professional-quality memory managers from ones thrown together by programmers distracted by the need to get on to other tasks. Writing a
custom memory manager that almost works is pretty easy. Writing
one that works well is a lot harder. As a general rule, I suggest you not
attempt it unless you have to.
In many cases, you don’t have to. Some compilers have switches that
enable debugging and logging functionality in their memory management functions. A quick glance through your compilers’ documentation may eliminate your need to consider writing new and delete. On
many platforms, commercial products can replace the memory management functions that ship with compilers. To avail yourself of their
enhanced functionality and (presumably) improved performance, all
you need do is relink. (Well, you also have to buy them.)
Another option is open source memory managers. They’re available for
many platforms, so you can download and try those. One such open
source allocator is the Pool library from Boost (see Item 55). The Pool
library offers allocators tuned for one of the most common situations
in which custom memory management is helpful: allocation of a large
number of small objects. Many C++ books, including earlier editions
of this one, show the code for a high-performance small-object allocator, but they usually omit such pesky details as portability and alignment considerations, thread safety, etc. Real libraries tend to have
code that’s a lot more robust. Even if you decide to write your own
news and deletes, looking at open source versions is likely to give you
insights into the easy-to-overlook details that separate almost working
from really working. (Given that alignment is one such detail, it’s
worth noting that TR1 (see Item 54) includes support for discovering
type-specific alignment requirements.)
The topic of this Item is knowing when it can make sense to replace
the default versions of new and delete, either globally or on a per-class
basis. We’re now in a position to summarize when in more detail than
we did before.
■
To detect usage errors (as above).
Customizing new and delete
Item 50 251A
■
To collect statistics about the use of dynamically allocated
memory (also as above).
■
To increase the speed of allocation and deallocation. Generalpurpose allocators are often (though not always) a lot slower than
custom versions, especially if the custom versions are designed for
objects of a particular type. Class-specific allocators are an example application of fixed-size allocators such as those offered by
Boost’s Pool library. If your application is single-threaded, but
your compilers’ default memory management routines are threadsafe, you may be able to win measurable speed improvements by
writing thread-unsafe allocators. Of course, before jumping to the
conclusion that operator new and operator delete are worth speeding up, be sure to profile your program to confirm that these functions are truly a bottleneck.
■
To reduce the space overhead of default memory management. General-purpose memory managers are often (though not
always) not just slower than custom versions, they often use more
memory, too. That’s because they often incur some overhead for
each allocated block. Allocators tuned for small objects (such as
those in Boost’s Pool library) essentially eliminate such overhead.
■
To compensate for suboptimal alignment in the default allocator. As I mentioned earlier, it’s fastest to access doubles on the x86
architecture when they are eight-byte aligned. Alas, the operator
news that ship with some compilers don’t guarantee eight-byte
alignment for dynamic allocations of doubles. In such cases, replacing the default operator new with one that guarantees eightbyte alignment could yield big increases in program performance.
■
To cluster related objects near one another. If you know that
particular data structures are generally used together and you’d
like to minimize the frequency of page faults when working on the
data, it can make sense to create a separate heap for the data
structures so they are clustered together on as few pages as possible. Placement versions of new and delete (see Item 52) can make it
possible to achieve such clustering.
■
To obtain unconventional behavior. Sometimes you want operators new and delete to do something that the compiler-provided
versions don’t offer. For example, you might want to allocate and
deallocate blocks in shared memory, but have only a C API
through which to manage that memory. Writing custom versions
of new and delete (probably placement versions — again, see
Item 52) would allow you to drape the C API in C++ clothing. As
252A Item 51
Chapter 8
another example, you might write a custom operator delete that
overwrites deallocated memory with zeros in order to increase the
security of application data.
Things to Remember
✦
There are many valid reasons for writing custom versions of new and
delete, including improving performance, debugging heap usage errors, and collecting heap usage information.
Item 51: Adhere to convention when writing new and
delete.
Item 50 explains when you might want to write your own versions of
operator new and operator delete, but it doesn’t explain the conventions
you must follow when you do it. The rules aren’t hard to follow, but
some of them are unintuitive, so it’s important to know what they are.
We’ll begin with operator new. Implementing a conformant operator new
requires having the right return value, calling the new-handling function when insufficient memory is available (see Item 49), and being
prepared to cope with requests for no memory. You’ll also want to
avoid inadvertently hiding the “normal” form of new, though that’s
more a class interface issue than an implementation requirement; it’s
addressed in Item 52.
The return value part of operator new is easy. If you can supply the
requested memory, you return a pointer to it. If you can’t, you follow
the rule described in Item 49 and throw an exception of type bad_alloc.
It’s not quite that simple, however, because operator new actually tries
to allocate memory more than once, calling the new-handling function
after each failure. The assumption here is that the new-handling function might be able to do something to free up some memory. Only
when the pointer to the new-handling function is null does operator
new throw an exception.
Curiously, C++ requires that operator new return a legitimate pointer
even when zero bytes are requested. (Requiring this odd-sounding
behavior simplifies things elsewhere in the language.) That being the
case, pseudocode for a non-member operator new looks like this:
void* operator new(std::size_t size) throw(std::bad_alloc)
{
// your operator new might
using namespace std;
// take additional params
Customizing new and delete
if (size == 0) {
size = 1;
}
while (true) {
Item 51 253A
// handle 0-byte requests
// by treating them as
// 1-byte requests
attempt to allocate size bytes;
if (the allocation was successful)
return (a pointer to the memory);
// allocation was unsuccessful; find out what the
// current new-handling function is (see below)
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’s simple, 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
new-handling function pointer is set to null, then promptly reset to
what it was originally. Unfortunately, there is no way to get at the
new-handling function pointer directly, so you have to call
set_new_handler to find out what it is. Crude, yes, but also effective, at
least for single-threaded code. In a multithreaded environment, you’ll
probably need some kind of lock to safely manipulate the (global) data
structures behind the new-handling function.
Item 49 remarks that operator new contains an infinite loop, and the
code above shows that loop explicitly; “while (true)” is about as infinite
as it gets. The only way out of the loop is for memory to be successfully allocated or for the new-handling function to do one of the things
described in Item 49: make more memory available, install a different
new-handler, deinstall the new-handler, throw an exception of or
derived from 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 loop inside
operator new will never terminate.
Many people don’t realize that operator new member functions are
inherited by derived classes. That can lead to some interesting complications. In the pseudocode for operator new above, notice that the
function tries to allocate size bytes (unless size is zero). That makes
perfect sense, because that’s the argument that was passed to the
function. However, as Item 50 explains, one of the most common reasons for writing a custom memory manager is to optimize allocation
254A Item 51
Chapter 8
for objects of a specific class, not for a class or any of its derived
classes. That is, given an operator new for a class X, the behavior of
that function is typically tuned for objects of size sizeof(X) — nothing
larger and nothing smaller. Because of inheritance, however, it is possible that the operator new in a base class will be called to allocate
memory for an object of a derived class:
class Base {
public:
static void* operator new(std::size_t size) throw(std::bad_alloc);
...
};
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 that it wasn’t — the best way for it to handle the
situation is to slough off calls requesting the “wrong” amount of memory to the standard operator new, like this:
void* Base::operator new(std::size_t size) throw(std::bad_alloc)
{
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 pathologicalbut-nevertheless-possible case where size is zero!” Actually, I didn’t,
and please stop using hyphens when you cry out. The test is still
there, it’s just been incorporated into the test of size against
sizeof(Base). C++ works in some mysterious ways, and one of those
ways is to decree that all freestanding objects have non-zero size (see
Item 39). By definition, sizeof(Base) can never be zero, so if size is zero,
the request will be forwarded to ::operator new, and it will become that
function’s responsibility to treat the request in a reasonable fashion.
If you’d like to control memory allocation for arrays on a per-class
basis, you need to implement operator new’s array-specific cousin,
operator new[]. (This function is usually called “array new,” because
it’s hard to figure out how to pronounce “operator new[]”.) If you decide
to write operator new[], remember that all you’re doing is allocating a
chunk of raw memory — you can’t do anything to the as-yet-nonexistent objects in the array. In fact, you can’t even figure out how many
Customizing new and delete
Item 51 255A
objects will be in the array. First, 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 derived class objects, and
derived class objects are usually bigger than base class objects.
Hence, you can’t assume inside Base::operator new[] that the size of
each object going into the array is sizeof(Base), and that means you
can’t assume that the number of objects in the array is (bytes
requested)/sizeof(Base). Second, the size_t parameter passed to operator
new[] may be for more memory than will be filled with objects,
because, as Item 16 explains, dynamically allocated arrays may
include extra space to store the number of array elements.
So much for the conventions you need to follow when writing operator
new. For operator delete, things are simpler. About all you need to
remember is that C++ guarantees it’s always safe to delete the null
pointer, so you need to honor that guarantee. Here’s pseudocode for a
non-member operator delete:
void operator delete(void *rawMemory) throw()
{
if (rawMemory == 0) return;
// do nothing if the null
// pointer is being deleted
deallocate the memory pointed to by rawMemory;
}
The member version of this function is simple, too, except you’ve got
to be sure to check the size of what’s being deleted. 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:
// operator delete is declared
static void* operator new(std::size_t size) throw(std::bad_alloc);
static void operator delete(void *rawMemory, std::size_t size) throw();
...
};
void Base::operator delete(void *rawMemory, std::size_t size) throw()
{
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;
256A Item 52
Chapter 8
return;
}
Interestingly, the size_t value C++ passes to operator delete may be
incorrect if the object being deleted was derived from a base class
lacking a virtual destructor. This is reason enough for making sure
your base classes have virtual destructors, but Item 7 describes a second, arguably better reason. For now, simply note that if you omit virtual destructors in base classes, operator delete functions may not
work correctly.
Things to Remember
✦
operator new should contain an infinite loop trying to allocate memory, should call the new-handler if it can’t satisfy a memory request,
and should handle requests for zero bytes. Class-specific versions
should handle requests for larger blocks than expected.
✦
operator delete should do nothing if passed a pointer that is null.
Class-specific versions should handle blocks that are larger than expected.
Item 52: Write placement delete if you write placement
new.
Placement new and placement delete aren’t the most commonly
encountered beasts in the C++ menagerie, so don’t worry if you’re not
familiar with them. Instead, recall from Items 16 and 17 that when
you write a new expression such as this,
Widget *pw = new Widget;
two functions are called: one to operator new to allocate memory, a
second to Widget’s default constructor.
Suppose that the first call succeeds, but the second call results in an
exception being thrown. In that case, the memory allocation performed in step 1 must be undone. Otherwise we’ll have a memory
leak. Client code can’t deallocate the memory, because if the Widget
constructor throws an exception, pw is never assigned. There’d be no
way for clients to get at the pointer to the memory that should be deallocated. The responsibility for undoing step 1 must therefore fall on
the C++ runtime system.
The runtime system is happy to call the operator delete that corresponds to the version of operator new it called in step 1, but it can do
that only if it knows which operator delete — there may be many — is
Customizing new and delete
Item 52 257A
the proper one to call. This isn’t an issue if you’re dealing with the versions of new and delete that have the normal signatures, because the
normal operator new,
void* operator new(std::size_t) throw(std::bad_alloc);
corresponds to the normal operator delete:
void operator delete(void *rawMemory) throw();
void operator delete(void *rawMemory,
std::size_t size) throw();
// normal signature
// at global scope
// typical normal
// signature at class
// scope
When you’re using only the normal forms of new and delete, then, the
runtime system has no trouble finding the delete that knows how to
undo what new did. The which-delete-goes-with-this-new issue does
arise, however, when you start declaring non-normal forms of operator
new — forms that take additional parameters.
For example, suppose you write a class-specific operator new that
requires specification of an ostream to which allocation information
should be logged, and you also write a normal class-specific operator
delete:
class Widget {
public:
...
static void* operator new(std::size_t size,
std::ostream& logStream)
throw(std::bad_alloc);
static void operator delete(void *pMemory,
std::size_t size) throw();
// non-normal
// form of new
// normal class// specific form
// of delete
...
};
This design is problematic, but before we see why, we need to make a
brief terminological detour.
When an operator new function takes extra parameters (other than the
mandatory size_t argument), that function is known as a placement
version of new. The operator new above is thus a placement version. A
particularly useful placement new is the one that takes a pointer specifying where an object should be constructed. That operator new looks
like this:
void* operator new(std::size_t, void *pMemory) throw();
// “placement
// new”
258A Item 52
Chapter 8
This version of new is part of C++’s standard library, and you have
access to it whenever you #include <new>. Among other things, this
new is used inside vector to create objects in the vector’s unused
capacity. It’s also the original placement new. In fact, that’s how this
function is known: as placement new. Which means that the term
“placement new” is overloaded. Most of the time when people talk
about placement new, they’re talking about this specific function, the
operator new taking a single extra argument of type void*. Less commonly, they’re talking about any version of operator new that takes
extra arguments. Context generally clears up any ambiguity, but it’s
important to understand that the general term “placement new”
means any version of new taking extra arguments, because the phrase
“placement delete” (which we’ll encounter in a moment) derives
directly from it.
But let’s get back to the declaration of the Widget class, the one whose
design I said was problematic. The difficulty is that this class will give
rise to subtle memory leaks. Consider this client code, which logs allocation information to cerr when dynamically creating a Widget:
Widget *pw = new (std::cerr) Widget; // call operator new, passing cerr as
// the ostream; this leaks memory
// if the Widget constructor throws
Once again, if memory allocation succeeds and the Widget constructor
throws an exception, the runtime system is responsible for undoing
the allocation that operator new performed. However, the runtime system can’t really understand how the called version of operator new
works, so it can’t undo the allocation itself. Instead, the runtime system looks for a version of operator delete that takes the same number
and types of extra arguments as operator new, and, if it finds it, that’s
the one it calls. In this case, operator new takes an extra argument of
type ostream&, so the corresponding operator delete would have this
signature:
void operator delete(void*, std::ostream&) throw();
By analogy with placement versions of new, versions of operator delete
that take extra parameters are known as placement deletes. In this
case, Widget declares no placement version of operator delete, so the
runtime system doesn’t know how to undo what the call to placement
new does. As a result, it does nothing. In this example, no operator
delete is called if the Widget constructor throws an exception!
The rule is simple: if an operator new with extra parameters isn’t
matched by an operator delete with the same extra parameters, no
operator delete will be called if a memory allocation by the new needs to
Customizing new and delete
Item 52 259A
be undone. To eliminate the memory leak in the code above, Widget
needs to declare a placement delete that corresponds to the logging
placement new:
class Widget {
public:
...
static void* operator new(std::size_t size, std::ostream& logStream)
throw(std::bad_alloc);
static void operator delete(void *pMemory) throw();
static void operator delete(void *pMemory, std::ostream& logStream)
throw();
...
};
With this change, if an exception is thrown from the Widget constructor in this statement,
Widget *pw = new (std::cerr) Widget;
// as before, but no leak this time
the corresponding placement delete is automatically invoked, and that
allows Widget to ensure that no memory is leaked.
However, consider what happens if no exception is thrown (which will
usually be the case) and we get to a delete in client code:
delete pw;
// invokes the normal
// operator delete
As the comment indicates, this calls the normal operator delete, not
the placement version. Placement delete is called only if an exception
arises from a constructor call that’s coupled to a call to a placement
new. Applying delete to a pointer (such as pw above) never yields a call
to a placement version of delete. Never.
This means that to forestall all memory leaks associated with placement versions of new, you must provide both the normal operator
delete (for when no exception is thrown during construction) and a
placement version that takes the same extra arguments as operator
new does (for when one is). Do that, and you’ll never lose sleep over
subtle memory leaks again. Well, at least not these subtle memory
leaks.
Incidentally, because member function names hide functions with the
same names in outer scopes (see Item 33), you need to be careful to
avoid having class-specific news hide other news (including the normal versions) that your clients expect. For example, if you have a base
class that declares only a placement version of operator new, clients
will find that the normal form of new is unavailable to them:
260A Item 52
Chapter 8
class Base {
public:
...
static void* operator new(std::size_t size,
// this new hides
std::ostream& logStream)
// the normal
throw(std::bad_alloc);
// global forms
...
};
Base *pb = new Base;
// error! the normal form of
// operator new is hidden
Base *pb = new (std::cerr) Base;
// fine, calls Base’s
// placement new
Similarly, operator news in derived classes hide both global and inherited versions of operator new:
class Derived: public Base {
public:
...
static void* operator new(std::size_t size)
throw(std::bad_alloc);
...
};
Derived *pd = new (std::clog) Derived;
Derived *pd = new Derived;
// inherits from Base above
// redeclares the normal
// form of new
// error! Base’s placement
// new is hidden
// fine, calls Derived’s
// operator new
Item 33 discusses this kind of name hiding in considerable detail, but
for purposes of writing memory allocation functions, what you need to
remember is that by default, C++ offers the following forms of operator
new at global scope:
void* operator new(std::size_t) throw(std::bad_alloc);
void* operator new(std::size_t, void*) throw();
void* operator new(std::size_t,
const std::nothrow_t&) throw();
// normal new
// placement new
// nothrow new —
// see Item 49
If you declare any operator news in a class, you’ll hide all these standard forms. Unless you mean to prevent class clients from using these
forms, be sure to make them available in addition to any custom operator new forms you create. For each operator new you make available,
of course, be sure to offer the corresponding operator delete, too. If you
want these functions to behave in the usual way, just have your classspecific versions call the global versions.
Customizing new and delete
Item 52 261A
An easy way to do this is to create a base class containing all the normal forms of new and delete:
class StandardNewDeleteForms {
public:
// normal new/delete
static void* operator new(std::size_t size) throw(std::bad_alloc)
{ return ::operator new(size); }
static void operator delete(void *pMemory) throw()
{ ::operator delete(pMemory); }
// placement new/delete
static void* operator new(std::size_t size, void *ptr) throw()
{ return ::operator new(size, ptr); }
static void operator delete(void *pMemory, void *ptr) throw()
{ return ::operator delete(pMemory, ptr); }
// nothrow new/delete
static void* operator new(std::size_t size, const std::nothrow_t& nt) throw()
{ return ::operator new(size, nt); }
static void operator delete(void *pMemory, const std::nothrow_t&) throw()
{ ::operator delete(pMemory); }
};
Clients who want to augment the standard forms with custom forms
can then just use inheritance and using declarations (see Item 33) to
get the standard forms:
class Widget: public StandardNewDeleteForms {
public:
using StandardNewDeleteForms::operator new;
using StandardNewDeleteForms::operator delete;
static void* operator new(std::size_t size,
std::ostream& logStream)
throw(std::bad_alloc);
static void operator delete(void *pMemory,
std::ostream& logStream)
throw();
...
};
// inherit std forms
// make those
// forms visible
// add a custom
// placement new
// add the corres// ponding place// ment delete
Things to Remember
✦
When you write a placement version of operator new, be sure to write
the corresponding placement version of operator delete. If you don’t,
your program may experience subtle, intermittent memory leaks.
✦
When you declare placement versions of new and delete, be sure not
to unintentionally hide the normal versions of those functions.
Miscellany
Chapter 9: Miscellany
Welcome to the catch-all “Miscellany” chapter. There are only three
Items here, but don’t let their diminutive number or unglamorous setting fool you. They’re important.
Miscellany
The first Item emphasizes that compiler warnings are not to be trifled
with, at least not if you want your software to behave properly. The
second offers an overview of the contents of the standard C++ library,
including the significant new functionality being introduced in TR1.
Finally, the last Item provides an overview of Boost, arguably the most
important general-purpose C++-related web site. Trying to write effective C++ software without the information in these Items is, at best, an
uphill battle.
Item 53: Pay attention to compiler warnings.
Many programmers routinely ignore compiler warnings. After all, if
the problem were serious, it would be an error, right? This thinking
may be relatively harmless in other languages, but in C++, it’s a good
bet compiler writers have a better grasp of what’s going on than you
do. For example, here’s an error everybody makes at one time or
another:
class B {
public:
virtual void f() const;
};
class D: public B {
public:
virtual void f();
};
Miscellany
Item 54 263A
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 hides B::f — that’s what it’s supposed to do!” Wrong. This compiler is trying to tell you that the f
declared in B has not been redeclared in D; instead, it’s been hidden
entirely (see Item 33 for a description of why this is so). Ignoring this
compiler warning will almost certainly lead to erroneous program
behavior, followed by a lot of debugging to discover something this
compiler detected in the first place.
After you gain experience with the warning messages from a particular compiler, you’ll learn to understand what the different messages
mean (which is often very different from what they seem to mean,
alas). Once you have that experience, you may choose to ignore a
whole range of warnings, though it’s generally considered better practice to write code that compiles warning-free, even at the highest
warning level. Regardless, it’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’s not a good idea to get
sloppy in your programming, relying on compilers to spot your mistakes for you. The function-hiding code above, for instance, goes
through a different (but widely used) compiler with nary a squawk.
Things to Remember
✦
Take compiler warnings seriously, and strive to compile warningfree at the maximum warning level supported by your compilers.
✦
Don’t become dependent on compiler warnings, because different
compilers warn about different things. Porting to a new compiler
may eliminate warning messages you’ve come to rely on.
Item 54: Familiarize yourself with the standard
library, including TR1.
The standard for C++ — the document defining the language and its
library — was ratified in 1998. In 2003, a minor “bug-fix” update was
issued. The standardization committee continues its work, however,
and a “Version 2.0” C++ standard is expected in 2009 (though all substantive work is likely to be completed by the end of 2007). Until
264A Item 54
Chapter 9
recently, the expected year for the next version of C++ was undecided,
and that explains why people usually refer to the next version of C++
as “C++0x” — the year 200x version of C++.
C++0x will probably include some interesting new language features,
but most new C++ functionality will come in the form of additions to
the standard library. We already know what some of the new library
functionality will be, because it’s been specified in a document known
as TR1 (“Technical Report 1” from the C++ Library Working Group).
The standardization committee reserves the right to modify TR1 functionality before it’s officially enshrined in C++0x, but significant
changes are unlikely. For all intents and purposes, TR1 heralds the
beginning of a new release of C++ — what we might call standard C++
1.1. You can’t be an effective C++ programmer without being familiar
with TR1 functionality, because that functionality is a boon to virtually every kind of library and application.
Before surveying what’s in TR1, it’s worth reviewing the major parts of
the standard C++ library specified by C++98:
■
The Standard Template Library (STL), including containers (vector, string, map, etc.); iterators; algorithms (find, sort, transform,
etc.); function objects (less, greater, etc.); and various container and
function object adapters (stack, priority_queue, mem_fun, not1, etc.).
■
Iostreams, including support for user-defined buffering, internationalized IO, and the predefined objects cin, cout, cerr, and clog.
■
Support for internationalization, including the ability to have
multiple active locales. Types like wchar_t (usually 16 bits/char)
and wstring (strings of wchar_ts) facilitate working with Unicode.
■
Support for numeric processing, including templates for complex numbers (complex) and arrays of pure values (valarray).
■
An exception hierarchy, including the base class exception, its
derived classes logic_error and runtime_error, and various classes
that inherit from those.
■
C89’s standard library. Everything in the 1989 C standard library
is also in C++.
If any of the above is unfamiliar to you, I suggest you schedule some
quality time with your favorite C++ reference to rectify the situation.
TR1 specifies 14 new components (i.e., pieces of library functionality).
All are in the std namespace, more precisely, in the nested namespace
tr1. The full name of the TR1 component shared_ptr (see below) is thus
std::tr1::shared_ptr. In this book, I customarily omit the std:: when dis-
Miscellany
Item 54 265A
cussing components of the standard library, but I always prefix TR1
components with tr1::.
This book shows examples of the following TR1 components:
■
The smart pointers tr1::shared_ptr and tr1::weak_ptr. tr1::shared_ptrs
act like built-in pointers, but they keep track of how many
tr1::shared_ptrs point to an object. This is known as reference counting. When the last such pointer is destroyed (i.e., when the reference count for an object becomes zero), the object is automatically
deleted. This works well in preventing resource leaks in acyclic
data structures, but if two or more objects contain tr1::shared_ptrs
such that a cycle is formed, the cycle may keep each object’s reference count above zero, even when all external pointers to the cycle
have been destroyed (i.e., when the group of objects as a whole is
unreachable). That’s where tr1::weak_ptrs come in. tr1::weak_ptrs
are designed to act as cycle-inducing pointers in otherwise acyclic
tr1::shared_ptr -based data structures. tr1::weak_ptrs don’t participate in reference counting. When the last tr1::shared_ptr to an object is destroyed, the object is deleted, even if tr1::weak_ptrs
continue to point there. Such tr1::weak_ptrs are automatically
marked as invalid, however.
tr1::shared_ptr may be the most widely useful component in TR1. I
use it many times in this book, including in Item 13, where I explain why it’s so important. (The book contains no uses of
tr1::weak_ptr, sorry.)
■
tr1::function, which makes it possible to represent any callable entity (i.e., any function or function object) whose signature is consistent with a target signature. If we wanted to make it possible to
register callback functions that take an int and return a string, we
could do this:
void registerCallback(std::string func(int));
// param type is a function
// taking an int and
// returning a string
The parameter name func is optional, so registerCallback could be
declared this way, instead:
void registerCallback(std::string (int));
// same as above; param
// name is omitted
Note here that “std::string (int)” is a function signature.
266A Item 54
Chapter 9
tr1::function makes it possible to make registerCallback much more
flexible, accepting as its argument any callable entity that takes
an int or anything an int can be converted into and that returns a
string or anything convertible to a string. tr1::function takes as a template parameter its target function signature:
void registerCallback(std::tr1::function<std::string (int)> func);
// the param “func” will
// take any callable entity
// with a sig consistent
// with “std::string (int)”
This kind of flexibility is astonishingly useful, something I do my
best to demonstrate in Item 35.
■
tr1::bind, which does everything the STL binders bind1st and
bind2nd do, plus much more. Unlike the pre-TR1 binders, tr1::bind
works with both const and non-const member functions. Unlike the
pre-TR1 binders, tr1::bind works with by-reference parameters.
Unlike the pre-TR1 binders, tr1::bind handles function pointers
without help, so there’s no need to mess with ptr_fun, mem_fun, or
mem_fun_ref before calling tr1::bind. Simply put, tr1::bind is a second-generation binding facility that is significantly better than its
predecessor. I show an example of its use in Item 35.
I divide the remaining TR1 components into two sets. The first group
offers fairly discrete standalone functionality:
■
Hash tables used to implement sets, multisets, maps, and multimaps. Each new container has an interface modeled on that of its
pre-TR1 counterpart. The most surprising thing about TR1’s hash
tables are their names: tr1::unordered_set, tr1::unordered_multiset,
tr1::unordered_map, and tr1::unordered_multimap. These names emphasize that, unlike the contents of a set, multiset, map, or multimap, the elements in a TR1 hash-based container are not in any
predictable order.
■
Regular expressions, including the ability to do regular expression-based search and replace operations on strings, to iterate
through strings from match to match, etc.
■
Tuples, a nifty generalization of the pair template that’s already in
the standard library. Whereas pair objects can hold only two objects, however, tr1::tuple objects can hold an arbitrary number. Expat Python and Eiffel programmers, rejoice! A little piece of your
former homeland is now part of C++.
Miscellany
Item 54 267A
■
tr1::array, essentially an “STLified” array, i.e., an array supporting
member functions like begin and end. The size of a tr1::array is fixed
during compilation; the object uses no dynamic memory.
■
tr1::mem_fn, a syntactically uniform way of adapting member
function pointers. Just as tr1::bind subsumes and extends the capabilities of C++98’s bind1st and bind2nd, tr1::mem_fn subsumes
and extends the capabilities of C++98’s mem_fun and mem_fun_ref.
■
tr1::reference_wrapper, a facility to make references act a bit more
like objects. Among other things, this makes it possible to create
containers that act as if they hold references. (In reality, containers can hold only objects or pointers.)
■
Random number generation facilities that are vastly superior to
the rand function that C++ inherited from C’s standard library.
■
Mathematical special functions, including Laguerre polynomials, Bessel functions, complete elliptic integrals, and many more.
■
C99 compatibility extensions, a collection of functions and templates designed to bring many new C99 library features to C++.
The second set of TR1 components consists of support technology for
more sophisticated template programming techniques, including template metaprogramming (see Item 48):
■
Type traits, a set of traits classes (see Item 47) to provide compile-time information about types. Given a type T, TR1’s type traits
can reveal whether T is a built-in type, offers a virtual destructor,
is an empty class (see Item 39), is implicitly convertible to some
other type U, and much more. TR1’s type traits can also reveal the
proper alignment for a type, a crucial piece of information for programmers writing custom memory allocation functions (see
Item 50).
■
tr1::result_of, a template to deduce the return types of function
calls. When writing templates, it’s often important to be able to refer to the type of object returned from a call to a function (template), but the return type can depend on the function’s parameter
types in complex ways. tr1::result_of makes referring to function return types easy. tr1::result_of is used in several places in TR1 itself.
Although the capabilities of some pieces of TR1 (notably tr1::bind and
tr1::mem_fn) subsume those of some pre-TR1 components, TR1 is a
pure addition to the standard library. No TR1 component replaces an
existing component, so legacy code written with pre-TR1 constructs
continues to be valid.
268A Item 54
Chapter 9
TR1 itself is just a document.† To take advantage of the functionality it
specifies, you need access to code that implements it. Eventually, that
code will come bundled with compilers, but as I write this in 2005,
there is a good chance that if you look for TR1 components in your
standard library implementations, at least some will be missing. Fortunately, there is someplace else to look: 10 of the 14 components in
TR1 are based on libraries freely available from Boost (see Item 55), so
that’s an excellent resource for TR1-like functionality. I say “TR1-like,”
because, though much TR1 functionality is based on Boost libraries,
there are places where Boost functionality is currently not an exact
match for the TR1 specification. It’s possible that by the time you read
this, Boost not only will have TR1-conformant implementations for
the TR1 components that evolved from Boost libraries, it will also offer
implementations of the four TR1 components that were not based on
Boost work.
If you’d like to use Boost’s TR1-like libraries as a stopgap until compilers ship with their own TR1 implementations, you may want to avail
yourself of a namespace trick. All Boost components are in the
namespace boost, but TR1 components are supposed to be in std::tr1.
You can tell your compilers to treat references to std::tr1 the same as
references to boost. This is how:
namespace std {
namespace tr1 = ::boost;
}
// namespace std::tr1 is an alias
// for namespace boost
Technically, this puts you in the realm of undefined behavior,
because, as Item 25 explains, you’re not allowed to add anything to
the std namespace. In practice, you’re unlikely to run into any trouble.
When your compilers provide their own TR1 implementations, all
you’ll need to do is eliminate the above namespace alias; code referring to std::tr1 will continue to be valid.
Probably the most important part of TR1 not based on Boost libraries
is hash tables, but hash tables have been available for many years
from several sources under the names hash_set, hash_multiset,
hash_map, and hash_multimap. There is a good chance that the libraries shipping with your compilers already contain these templates. If
not, fire up your favorite search engine and search for these names (as
well as their TR1 appellations), because you’re sure to find several
sources for them, both commercial and freeware.
† As I write this in early 2005, the document has not been finalized, and its URL is subject to change. I therefore suggest you consult the Effective C++ TR1 Information Page,
http://aristeia.com/EC3E/TR1_info.html. That URL will remain stable.
Miscellany
Item 55 269A
Things to Remember
✦
The primary standard C++ library functionality consists of the STL,
iostreams, and locales. The C89 standard library is also included.
✦
TR1 adds support for smart pointers (e.g., tr1::shared_ptr), generalized function pointers (tr1::function), hash-based containers, regular
expressions, and 10 other components.
✦
TR1 itself is only a specification. To take advantage of TR1, you need
an implementation. One source for implementations of TR1 components is Boost.
Item 55: Familiarize yourself with Boost.
Searching for a collection of high-quality, open source, platform- and
compiler-independent libraries? Look to Boost. Interested in joining a
community of ambitious, talented C++ developers working on state-ofthe-art library design and implementation? Look to Boost. Want a
glimpse of what C++ might look like in the future? Look to Boost.
Boost is both a community of C++ developers and a collection of freely
downloadable C++ libraries. Its web site is http://boost.org. You should
bookmark it now.
There are many C++ organizations and web sites, of course, but Boost
has two things going for it that no other organization can match. First,
it has a uniquely close and influential relationship with the C++ standardization committee. Boost was founded by committee members,
and there continues to be strong overlap between the Boost and committee memberships. In addition, Boost has always had as one of its
goals to act as a testing ground for capabilities that could be added to
Standard C++. One result of this relationship is that of the 14 new
libraries introduced into C++ by TR1 (see Item 54), more than twothirds are based on work done at Boost.
The second special characteristic of Boost is its process for accepting
libraries. It’s based on public peer review. If you’d like to contribute a
library to Boost, you start by posting to the Boost developers mailing
list to gauge interest in the library and initiate the process of preliminary examination of your work. Thus begins a cycle that the web site
summarizes as “Discuss, refine, resubmit. Repeat until satisfied.”
Eventually, you decide that your library is ready for formal submission. A review manager confirms that your library meets Boost’s minimal requirements. For example, it must compile under at least two
compilers (to demonstrate nominal portability), and you have to attest
270A Item 55
Chapter 9
that the library can be made available under an acceptable license
(e.g., the library must allow free commercial and non-commercial
use). Then your submission is made available to the Boost community
for official review. During the review period, volunteers go over your
library materials (e.g., source code, design documents, user documentation, etc.) and consider questions such as these:
■
How good are the design and implementation?
■
Is the code portable across compilers and operating systems?
■
Is the library likely to be of use to its target audience, i.e., people
working in the domain the library addresses?
■
Is the documentation clear, complete, and accurate?
These comments are posted to a Boost mailing list, so reviewers and
others can see and respond to one another’s remarks. At the end of
the review period, the review manager decides whether your library is
accepted, conditionally accepted, or rejected.
Peer reviews do a good job of keeping poorly written libraries out of
Boost, but they also help educate library authors in the considerations that go into the design, implementation, and documentation of
industrial-strength cross-platform libraries. Many libraries require
more than one official review before being declared worthy of acceptance.
Boost contains dozens of libraries, and more are added on a continuing basis. From time to time, some libraries are also removed, typically because their functionality has been superseded by a newer
library that offers greater functionality or a better design (e.g., one
that is more flexible or more efficient).
The libraries vary widely in size and scope. At one extreme are libraries that conceptually require only a few lines of code (but are typically
much longer after support for error handling and portability is added).
One such library is Conversion, which provides safer or more convenient cast operators. Its numeric_cast function, for example, throws an
exception if converting a numeric value from one type to another leads
to overflow or underflow or a similar problem, and lexical_cast makes it
possible to cast any type supporting operator<< into a string — very
useful for diagnostics, logging, etc. At the other extreme are libraries
offering such extensive capabilities, entire books have been written
about them. These include the Boost Graph Library (for programming with arbitrary graph structures) and the Boost MPL Library
(“metaprogramming library”).
Miscellany
Item 55 271A
Boost’s bevy of libraries addresses a cornucopia of topics, grouped
into over a dozen general categories. Those categories include:
■
String and text processing, including libraries for type-safe
printf-like formatting, regular expressions (the basis for similar
functionality in TR1 — see Item 54), and tokenizing and parsing.
■
Containers, including libraries for fixed-size arrays with an STLlike interface (see Item 54), variable-sized bitsets, and multidimensional arrays.
■
Function objects and higher-order programming, including several libraries that were used as the basis for functionality in TR1.
One interesting library is the Lambda library, which makes it so
easy to create function objects on the fly, you’re unlikely to realize
that’s what you’re doing:
using namespace boost::lambda;
// make boost::lambda
// functionality visible
std::vector<int> v;
...
std::for_each(v.begin(), v.end(),
// for each element x in
std::cout << _1 * 2 + 10 << "\n"); // v, print x*2+10;
// “_1” is the Lambda
// library’s placeholder
// for the current element
■
Generic programming, including an extensive set of traits
classes. (See Item 47 for information on traits).
■
Template metaprogramming (TMP — see Item 48), including a library for compile-time assertions, as well as the Boost MPL Library. Among the nifty things in MPL is support for STL-like data
structures of compile-time entities like types, e.g.,
// create a list-like compile-time container of three types (float,
// double, and long double) and call the container “floats”
typedef boost::mpl::list<float, double, long double> floats;
// create a new compile-time list of types consisting of the types in
// “floats” plus “int” inserted at the front; call the new container “types”
typedef boost::mpl::push_front<floats, int>::type types;
Such containers of types (often known as typelists, though they
can also be based on an mpl::vector as well as an mpl::list) open the
door to a wide range of powerful and important TMP applications.
■
Math and numerics, including libraries for rational numbers; octonions and quaternions; greatest common divisor and least com-
272A Item 55
Chapter 9
mon multiple computations; and random numbers (yet another
library that influenced related functionality in TR1).
■
Correctness and testing, including libraries for formalizing implicit template interfaces (see Item 41) and for facilitating test-first
programming.
■
Data structures, including libraries for type-safe unions (i.e.,
storing variant “any” types) and the tuple library that led to the
corresponding TR1 functionality.
■
Inter-language support, including a library to allow seamless interoperability between C++ and Python.
■
Memory, including the Pool library for high-performance fixedsize allocators (see Item 50); and a variety of smart pointers (see
Item 13), including (but not limited to) the smart pointers in TR1.
One such non-TR1 smart pointer is scoped_array, an auto_ptr -like
smart pointer for dynamically allocated arrays; Item 44 shows an
example use.
■
Miscellaneous, including libraries for CRC checking, date and
time manipulations, and traversing file systems.
Remember, that’s just a sampling of the libraries you’ll find at Boost.
It’s not an exhaustive list.
Boost offers libraries that do many things, but it doesn’t cover the
entire programming landscape. For example, there is no library for
GUI development, nor is there one for communicating with databases.
At least there’s not now — not as I write this. By the time you read it,
however, there might be. The only way to know for sure is to check. I
suggest you do it right now: http://boost.org. Even if you don’t find
exactly what you’re looking for, you’re certain to find something interesting there.
Things to Remember
✦
Boost is a community and web site for the development of free, open
source, peer-reviewed C++ libraries. Boost plays an influential role
in C++ standardization.
✦
Boost offers implementations of many TR1 components, but it also
offers many other libraries, too.
Beyond Effective C++
Appendix A: Beyond Effective C++
Effective C++ covers what I consider to be the most important general
guidelines for practicing C++ programmers, but if you’re interested in
more ways to improve your effectiveness, I encourage you to examine
my other C++ books, More Effective C++ and Effective STL.
More Effective C++ covers additional programming guidelines and includes extensive treatments of topics such as efficiency and programming with exceptions. It also describes important C++ programming
techniques like smart pointers, reference counting, and proxy objects.
Effective STL is a guideline-oriented book like Effective C++, but it focuses exclusively on making effective use of the Standard Template
Library.
Tables of contents for both books are summarized below.
Contents of More Effective C++
Basics
Item
Item
Item
Item
1:
2:
3:
4:
Distinguish between pointers and references
Prefer C++-style casts
Never treat arrays polymorphically
Avoid gratuitous default constructors
Operators
Item 5:
Item 6:
Item 7:
Item 8:
Be wary of user-defined conversion functions
Distinguish between prefix and postfix forms of
increment and decrement operators
Never overload &&, ||, or ,
Understand the different meanings of new and delete
274A
Beyond Effective C++
Exceptions
Item
Item
Item
Item
9:
10:
11:
12:
Use destructors to prevent resource leaks
Prevent resource leaks in constructors
Prevent exceptions from leaving destructors
Understand how throwing an exception differs from
passing a parameter or calling a virtual function
Item 13: Catch exceptions by reference
Item 14: Use exception specifications judiciously
Item 15: Understand the costs of exception handling
Efficiency
Item
Item
Item
Item
Item
Item
Item
Item
Item
16:
17:
18:
19:
20:
21:
22:
23:
24:
Remember the 80-20 rule
Consider using lazy evaluation
Amortize the cost of expected computations
Understand the origin of temporary objects
Facilitate the return value optimization
Overload to avoid implicit type conversions
Consider using op= instead of stand-alone op
Consider alternative libraries
Understand the costs of virtual functions, multiple
inheritance, virtual base classes, and RTTI
Techniques
Item
Item
Item
Item
Item
Item
Item
25:
26:
27:
28:
29:
30:
31:
Virtualizing constructors and non-member functions
Limiting the number of objects of a class
Requiring or prohibiting heap-based objects
Smart pointers
Reference counting
Proxy classes
Making functions virtual with respect to more
than one object
Miscellany
Item 32: Program in the future tense
Item 33: Make non-leaf classes abstract
Item 34: Understand how to combine C++ and C in the same
program
Item 35: Familiarize yourself with the language standard
Beyond Effective C++
275A
Contents of Effective STL
Chapter 1: Containers
Item 1:
Item 2:
Item 3:
Choose your containers with care.
Beware the illusion of container-independent code.
Make copying cheap and correct for objects
in containers.
Item 4: Call empty instead of checking size() against zero.
Item 5: Prefer range member functions to their single-element
counterparts.
Item 6: Be alert for C++’s most vexing parse.
Item 7: When using containers of newed pointers, remember to
delete the pointers before the container is destroyed.
Item 8: Never create containers of auto_ptrs.
Item 9: Choose carefully among erasing options.
Item 10: Be aware of allocator conventions and restrictions.
Item 11: Understand the legitimate uses of custom allocators.
Item 12: Have realistic expectations about the thread safety
of STL containers.
Chapter 2: vector and string
Item
Item
Item
Item
Item
Item
13:
14:
15:
16:
17:
18:
Prefer vector and string to dynamically allocated arrays.
Use reserve to avoid unnecessary reallocations.
Be aware of variations in string implementations.
Know how to pass vector and string data to legacy APIs.
Use “the swap trick” to trim excess capacity.
Avoid using vector<bool>.
Chapter 3: Associative Containers
Item 19: Understand the difference between equality and
equivalence.
Item 20: Specify comparison types for associative containers
of pointers.
Item 21: Always have comparison functions return false for
equal values.
Item 22: Avoid in-place key modification in set and multiset.
Item 23: Consider replacing associative containers with
sorted vectors.
Item 24: Choose carefully between map::operator[] and
map::insert when efficiency is important.
Item 25: Familiarize yourself with the nonstandard hashed
containers.
276A
Beyond Effective C++
Chapter 4: Iterators
Item 26: Prefer iterator to const_iterator, reverse_iterator, and
const_reverse_iterator.
Item 27: Use distance and advance to convert a container’s
const_iterators to iterators.
Item 28: Understand how to use a reverse_iterator’s base iterator.
Item 29: Consider istreambuf_iterators for character-by-character
input.
Chapter 5: Algorithms
Item 30: Make sure destination ranges are big enough.
Item 31: Know your sorting options.
Item 32: Follow remove-like algorithms by erase if you really
want to remove something.
Item 33: Be wary of remove-like algorithms on containers of
pointers.
Item 34: Note which algorithms expect sorted ranges.
Item 35: Implement simple case-insensitive string
comparisons via mismatch or lexicographical_compare.
Item 36: Understand the proper implementation of copy_if.
Item 37: Use accumulate or for_each to summarize ranges.
Chapter 6: Functors, Functor Classes, Functions, etc.
Item
Item
Item
Item
38:
39:
40:
41:
Design functor classes for pass-by-value.
Make predicates pure functions.
Make functor classes adaptable.
Understand the reasons for ptr_fun, mem_fun, and
mem_fun_ref.
Item 42: Make sure less<T> means operator<.
Chapter 7: Programming with the STL
Item 43: Prefer algorithm calls to hand-written loops.
Item 44: Prefer member functions to algorithms with the
same names.
Item 45: Distinguish among count, find, binary_search,
lower_bound, upper_bound, and equal_range.
Item 46: Consider function objects instead of functions as
algorithm parameters.
Item 47: Avoid producing write-only code.
Item 48: Always #include the proper headers.
Item 49: Learn to decipher STL-related compiler diagnostics.
Item 50: Familiarize yourself with STL-related web sites.
Item Mappings
Between Second
and Third Editions
Appendix B: Item Mappings Between Second and Third Editions
This third edition of Effective C++ differs from the second edition in
many ways, most significantly in that it includes lots of new information. However, most of the second edition’s content remains in the
third edition, albeit often in a modified form and location. In the tables
on the pages that follow, I show where information in second edition
Items may be found in the third edition and vice versa.
The tables show a mapping of information, not text. For example, the
ideas in Item 39 of the second edition (“Avoid casts down the inheritance hierarchy”) are now found in Item 27 of the current edition
(“Minimize casting”), even though the third edition text and examples
for that Item are entirely new. A more extreme example involves the
second edition’s Item 18 (“Strive for class interfaces that are complete
and minimal”). One of the primary conclusions of that Item was that
prospective member functions that need no special access to the nonpublic parts of the class should generally be non-members. In the
third edition, that same result is reached via different (stronger) reasoning, so Item 18 in the second edition maps to Item 23 in the third
edition (“Prefer non-member non-friend functions to member functions”), even though about the only thing the two Items have in common is their conclusion.
278A
Item Mappings Between Second and Third Editions
Second Edition to Third Edition
2nd Ed.
3rd Ed.
2nd Ed.
3rd Ed.
2nd Ed.
3rd Ed.
1
2
18
23
35
32
2
—
19
24
36
34
3
—
20
22
37
36
4
—
21
3
38
37
5
16
22
20
39
27
6
13
23
21
40
38
7
49
24
—
41
41
8
51
25
—
42
39, 44
9
52
26
—
43
40
10
50
27
6
44
—
11
14
28
—
45
5
12
4
29
28
46
18
13
4
30
28
47
4
14
7
31
21
48
53
15
10
32
26
49
54
16
12
33
30
50
—
17
11
34
31
Item Mappings Between Second and Third Editions
279A
Third Edition to Second Edition
3rd Ed.
2nd Ed.
3rd Ed.
2nd Ed.
3rd Ed.
2nd Ed.
1
—
20
22
39
42
2
1
21
23, 31
40
43
3
21
22
20
41
41
4
12, 13, 47
23
18
42
—
5
45
24
19
43
—
6
27
25
—
44
42
7
14
26
32
45
—
8
—
27
39
46
—
9
—
28
29, 30
47
—
10
15
29
—
48
—
11
17
30
33
49
7
12
16
31
34
50
10
13
6
32
35
51
8
14
11
33
9
52
9
15
—
34
36
53
48
16
5
35
—
54
49
17
—
36
37
55
—
18
46
37
38
19
pp. 77–79
38
40
Index
Operators are listed under operator. That is, operator<< is listed under
operator<<, not under <<, etc.
Example classes, structs, and class or struct templates are indexed
under example classes/templates. Example function and function
templates are indexed under example functions/templates.
Before A
.NET 7A, 81A, 135A, 145A, 194A
see also C#
=, in initialization vs. assignment 6A
1066 150A
2nd edition of this book
compared to 3rd edition 277–279A
see also inside back cover
3rd edition of this book
compared to 2nd edition 277–279A
see also inside back cover
80-20 rule 139A, 168A
A
abstract classes 43A
accessibility
control over data members’ 95A
name, multiple inheritance and 193A
accessing names, in templatized
bases 207–212A
addresses
inline functions 136A
objects 118A
aggregation, see composition
aliasing 54A
alignment 249–250A
allocators, in the STL 240A
alternatives to virtual functions 169–177A
ambiguity
multiple inheritance and 192A
nested dependent names and types 205A
argument-dependent lookup 110A
arithmetic, mixed-mode 103A, 222–226A
array layout, vs. object layout 73A
array new 254–255A
array, invalid index and 7A
ASPECT_RATIO 13A
assignment
see also operator=
chaining assignments 52A
copy-and-swap and 56A
generalized 220A
to self, operator= and 53–57A
vs. initialization 6A, 27–29A, 114A
assignment operator, copy 5A
auto_ptr, see std::auto_ptr
automatically generated functions 34–37A
copy constructor and copy assignment
operator 221A
disallowing 37–39A
avoiding code duplication 50A, 60A
Effective C++
Index 281A
B
Barry, Dave, allusion to 229A
base classes
copying 59A
duplication of data in 193A
lookup in, this-> and 210A
names hidden in derived classes 263A
polymorphic 44A
polymorphic, destructors and 40–44A
templatized 207–212A
virtual 193A
basic guarantee, the 128A
Battle of Hastings 150A
bidirectional iterators 227A
bidirectional_iterator_tag 228A
binary upgradeability, inlining and 138A
binding
dynamic, see dynamic binding
static, see static binding
birds and penguins 151–153A
bitwise const member functions 21–22A
books
Effective STL 273A, 275–276A
More Effective C++ 273A, 273–274A
Some Must Watch While Some Must
Sleep 150A
Boost 10A, 269–272A
containers 271A
Conversion library 270A
correctness and testing support 272A
data structures 272A
function objects and higher-order programming utilities 271A
functionality not provided 272A
generic programming support 271A
Graph library 270A
inter-language support 272A
Lambda library 271A
math and numerics utilities 271A
memory management utilities 272A
MPL library 270A, 271A
noncopyable base class 39A
Pool library 250A, 251A
scoped_array 65A, 216A, 272A
shared_array 65A
shared_ptr implementation, costs 83A
smart pointers 65A, 272A
string and text utilities 271A
template metaprogramming
support 271A
TR1 and 9–10A, 268A, 269A
typelist support 271A
web site 10A, 269A, 272
boost, as synonym for std::tr1 268A
breakpoints, and inlining 139A
built-in types 26–27A
efficiency and passing 89A
incompatibilities with 80A
C
C standard library and C++ standard
library 264A
C# 43A, 76A, 97A, 100A, 116A, 118A, 190A
see also .NET
C++ standard library 263–269A
<iosfwd> and 144A
array replacements and 75A
C standard library and 264A
C89 standard library and 264A
header organization of 101A
list template 186A
logic_error and 113A
set template 185A
vector template 75A
C++, as language federation 11–13A
C++0x 264A
C++-style casts 117A
C, as sublanguage of C++ 12A
C99 standard library, TR1 and 267A
caching
const and 22A
mutable and 22A
calling swap 110A
calls to base classes, casting and 119A
casting 116–123A
see also const_cast, static_cast,
dynamic_cast, and reinterpret_cast
base class calls and 119A
constness away 24–25A
encapsulation and 123A
grep and 117A
syntactic forms 116–117A
type systems and 116A
undefined behavior and 119A
chaining assignments 52A
class definitions
artificial client dependencies,
eliminating 143A
class declarations vs. 143A
object sizes and 141A
class design, see type design
282A Index
class names, explicitly specifying 162A
class, vs. typename 203A
classes
see also class definitions, interfaces
abstract 43A, 162A
base
see also base classes
duplication of data in 193A
polymorphic 44A
templatized 207–212A
virtual 193A
defining 4A
derived
see also inheritance
virtual base initialization of 194A
Handle 144–145A
Interface 145–147A
meaning of no virtual functions 41A
RAII, see RAII
specification, see interfaces
traits 226–232A
client 7A
clustering objects 251A
code
bloat 24A, 135A, 230A
avoiding, in templates 212–217A
copy assignment operator 60A
duplication, see duplication
exception-safe 127–134A
factoring out of templates 212–217A
incorrect, efficiency and 90A
reuse 195A
sharing, see duplication, avoiding
common features and inheritance 164A
commonality and variability analysis 212A
compatibility, vptrs and 42A
compatible types, accepting 218–222A
compilation dependencies 140–148A
minimizing 140–148A, 190A
pointers, references, and objects
and 143A
compiler warnings 262–263A
calls to virtuals and 50A
inlining and 136A
partial copies and 58A
compiler-generated functions 34–37A
disallowing 37–39A
functions compilers may generate 221A
compilers
parsing nested dependent names 204A
programs executing within, see template metaprogramming
Effective C++
register usage and 89A
reordering operations 76A
typename and 207A
when errors are diagnosed 212A
compile-time polymorphism 201A
composition 184–186A
meanings of 184A
replacing private inheritance with 189A
synonyms for 184A
vs. private inheritance 188A
conceptual constness, see const, logical
consistency with the built-in types 19A, 86A
const 13A, 17–26A
bitwise 21–22A
caching and 22A
casting away 24–25A
function declarations and 18A
logical 22–23A
member functions 19–25A
duplication and 23–25A
members, initialization of 29A
overloading on 19–20A
pass by reference and 86–90A
passing std::auto_ptr and 220A
pointers 17A
return value 18A
uses 17A
vs. #define 13–14A
const_cast 25A, 117A
see also casting
const_iterator, vs. iterators 18A
constants, see const
constraints on interfaces, from
inheritance 85A
constructors 84A
copy 5A
default 4A
empty, illusion of 137A
explicit 5A, 85A, 104A
implicitly generated 34A
inlining and 137–138A
operator new and 137A
possible implementation in derived
classes 138A
relationship to new 73A
static functions and 52A
virtual 146A, 147A
virtual functions and 48–52A
with vs. without arguments 114A
containers, in Boost 271A
containment, see composition
continue, delete and 62A
Effective C++
Index 283A
control over data members’
accessibility 95A
convenience functions 100A
Conversion library, in Boost 270A
conversions, type, see type conversions
copies, partial 58A
copy assignment operator 5A
code in copy constructor and 60A
derived classes and 60A
copy constructors
default definition 35A
derived classes and 60A
generalized 219A
how used 5A
implicitly generated 34A
pass-by-value and 6A
copy-and-swap 131A
assignment and 56A
exception-safe code and 132A
copying
base class parts 59A
behavior, resource management
and 66–69A
functions, the 57A
objects 57–60A
correctness
designing interfaces for 78–83A
testing and, Boost support 272A
corresponding forms of new and
delete 73–75A
corrupt data structures, exception-safe
code and 127A
cows, coming home 139A
crimes against English 39A, 204A
cross-DLL problem 82A
CRTP 246A
C-style casts 116A
ctor 8A
curiously recurring template pattern 246A
D
dangling handles 126A
data members
adding, copying functions and 58A
control over accessibility 95A
protected 97A
static, initialization of 242A
why private 94–98A
data structures
exception-safe code and 127A
in Boost 272A
deadly MI diamond 193A
debuggers
#define and 13A
inline functions and 139A
declarations 3A
inline functions 135A
replacing definitions 143A
static const integral members 14A
default constructors 4A
construction with arguments vs. 114A
implicitly generated 34A
default implementations
for virtual functions, danger of 163–167A
of copy constructor 35A
of operator= 35A
default initialization, unintended 59A
default parameters 180–183A
impact if changed 183A
static binding of 182A
#define
debuggers and 13A
disadvantages of 13A, 16A
vs. const 13–14A
vs. inline functions 16–17A
definitions 4A
classes 4A
deliberate omission of 38A
functions 4A
implicitly generated functions 35A
objects 4A
pure virtual functions 162A, 166–167A
replacing with declarations 143A
static class members 242A
static const integral members 14A
templates 4A
variable, postponing 113–116A
delete
see also operator delete
forms of 73–75A
operator delete and 73A
relationship to destructors 73A
usage problem scenarios 62A
delete [], std::auto_ptr and tr1::shared_ptr
and 65A
deleters
std::auto_ptr and 68A
tr1::shared_ptr and 68A, 81–83A
Delphi 97A
Dement, William 150A
dependencies, compilation 140–148A
dependent names 204A
dereferencing a null pointer, undefined
behavior of 6A
284A Index
Effective C++
derived classes
copy assignment operators and 60A
copy constructors and 60A
hiding names in base classes 263A
implementing constructors in 138A
virtual base initialization and 194A
design
contradiction in 179A
of interfaces 78–83A
of types 78–86A
design patterns
curiously recurring template
(CRTP) 246A
encapsulation and 173A
generating from templates 237A
Singleton 31A
Strategy 171–177A
Template Method 170A
TMP and 237A
destructors 84A
exceptions and 44–48A
inlining and 137–138A
pure virtual 43A
relationship to delete 73A
resource managing objects and 63A
static functions and 52A
virtual
operator delete and 255A
polymorphic base classes and 40–44A
virtual functions and 48–52A
dimensional unit correctness, TMP
and 236A
DLLs, delete and 82A
dtor 8A
duplication
avoiding 23–25A, 29A, 50A, 60A, 164A,
183A, 212–217A
base class data and 193A
init function and 60A
dynamic binding
definition of 181A
of virtual functions 179A
dynamic type, definition of 181A
dynamic_cast 50A, 117A, 120–123A
see also casting
efficiency of 120A
E
early binding 180A
easy to use correctly and hard to use
incorrectly 78–83A
EBO, see empty base optimization
Effective C++, compared to More Effective
C++ and Effective STL 273A
Effective STL 273A, 275–276A
compared to Effective C++ 273A
contents of 275–276A
efficiency
assignment vs. construction and
destruction 94A
default parameter binding 182A
dynamic_cast 120A
Handle classes 147A
incorrect code and 90A, 94A
init. with vs. without args 114A
Interface classes 147A
macros vs. inline functions 16A
member init. vs. assignment 28A
minimizing compilation
dependencies 147A
operator new/operator delete and 248A
pass-by-reference and 87A
pass-by-value and 86–87A
passing built-in types and 89A
runtime vs. compile-time tests 230A
template metaprogramming and 233A
template vs. function parameters 216A
unused objects 113A
virtual functions 168A
Eiffel 100A
embedding, see composition
empty base optimization (EBO) 190–191A
encapsulation 95A, 99A
casts and 123A
design patterns and 173A
handles and 124A
measuring 99A
protected members and 97A
RAII classes and 72A
enum hack 15–16A, 236A
errors
detected during linking 39A, 44A
runtime 152A
evaluation order, of parameters 76A
example classes/templates
A 4A
ABEntry 27A
AccessLevels 95A
Address 184A
Airplane 164A, 165A, 166A
Airport 164A
AtomicClock 40A
AWOV 43A
B 4A, 178A, 262A
Base 54A, 118A, 137A, 157A, 158A, 159A,
160A, 254A, 255A, 259A
Effective C++
BelowBottom 219A
bidirectional_iterator_tag 228A
Bird 151A, 152A, 153A
Bitmap 54A
BorrowableItem 192A
Bottom 218A
BuyTransaction 49A, 51A
C 5A
Circle 181A
CompanyA 208A
CompanyB 208A
CompanyZ 209A
CostEstimate 15A
CPerson 198A
CTextBlock 21A, 22A, 23A
Customer 57A, 58A
D 178A, 262A
DatabaseID 197A
Date 58A, 79A
Day 79A
DBConn 45A, 47A
DBConnection 45A
deque 229A
deque::iterator 229A
Derived 54A, 118A, 137A, 157A, 158A,
159A, 160A, 206A, 254A, 260A
Directory 31A
ElectronicGadget 192A
Ellipse 161A
Empty 34A, 190A
EvilBadGuy 172A, 174A
EyeCandyCharacter 175A
Factorial 235A
Factorial<0> 235A
File 193A, 194A
FileSystem 30A
FlyingBird 152A
Font 71A
forward_iterator_tag 228A
GameCharacter 169A, 170A, 172A, 173A, 176A
GameLevel 174A
GamePlayer 14A, 15A
GraphNode 4A
GUIObject 126A
HealthCalcFunc 176A
HealthCalculator 174A
HoldsAnInt 190A, 191A
HomeForSale 37A, 38A, 39A
input_iterator_tag 228A
input_iterator_tag<Iter*> 230A
InputFile 193A, 194A
Investment 61A, 70A
IOFile 193A, 194A
IPerson 195A, 197A
Index 285A
iterator_traits 229A
see also std::iterator_traits
list 229A
list::iterator 229A
Lock 66A, 67A, 68A
LoggingMsgSender 208A, 210A, 211A
Middle 218A
ModelA 164A, 165A, 167A
ModelB 164A, 165A, 167A
ModelC 164A, 166A, 167A
Month 79A, 80A
MP3Player 192A
MsgInfo 208A
MsgSender 208A
MsgSender<CompanyZ> 209A
NamedObject 35A, 36A
NewHandlerHolder 243A
NewHandlerSupport 245A
output_iterator_tag 228A
OutputFile 193A, 194A
Penguin 151A, 152A, 153A
Person 86A, 135A, 140A, 141A, 142A, 145A,
146A, 150A, 184A, 187A
PersonInfo 195A, 197A
PhoneNumber 27A, 184A
PMImpl 131A
Point 26A, 41A, 123A
PrettyMenu 127A, 130A, 131A
PriorityCustomer 58A
random_access_iterator_tag 228A
Rational 90A, 102A, 103A, 105A, 222A, 223A,
224A, 225A, 226A
RealPerson 147A
Rectangle 124A, 125A, 154A, 161A, 181A, 183A
RectData 124A
SellTransaction 49A
Set 185A
Shape 161A, 162A, 163A, 167A, 180A, 182A, 183A
SmartPtr 218A, 219A, 220A
SpecialString 42A
SpecialWindow 119A, 120A, 121A, 122A
SpeedDataCollection 96A
Square 154A
SquareMatrix 213A, 214A, 215A, 216A
SquareMatrixBase 214A, 215A
StandardNewDeleteForms 260A
Student 86A, 150A, 187A
TextBlock 20A, 23A, 24A
TimeKeeper 40A, 41A
Timer 188A
Top 218A
Transaction 48A, 50A, 51A
Uncopyable 39A
WaterClock 40A
286A Index
WebBrowser 98A, 100A, 101A
Widget 4A, 5A, 44A, 52A, 53A, 54A, 56A, 107A,
108A, 109A, 118A, 189A, 199A, 201A,
242A, 245A, 246A, 257A, 258A, 261A
Widget::WidgetTimer 189A
WidgetImpl 106A, 108A
Window 88A, 119A, 121A, 122A
WindowWithScrollBars 88A
WristWatch 40A
X 242A
Y 242A
Year 79A
example functions/templates
ABEntry::ABEntry 27A, 28A
AccessLevels::getReadOnly 95A
AccessLevels::getReadWrite 95A
AccessLevels::setReadOnly 95A
AccessLevels::setWriteOnly 95A
advance 228A, 230A, 232A, 233A, 234A
Airplane::defaultFly 165A
Airplane::fly 164A, 165A, 166A, 167A
askUserForDatabaseID 195A
AWOV::AWOV 43A
B::mf 178A
Base::operator delete 255A
Base::operator new 254A
Bird::fly 151A
BorrowableItem::checkOut 192A
boundingBox 126A
BuyTransaction::BuyTransaction 51A
BuyTransaction::createLogString 51A
calcHealth 174A
callWithMax 16A
changeFontSize 71A
Circle::draw 181A
clearAppointments 143A, 144A
clearBrowser 98A
CPerson::birthDate 198A
CPerson::CPerson 198A
CPerson::name 198A
CPerson::valueDelimClose 198A
CPerson::valueDelimOpen 198A
createInvestment 62A, 70A, 81A, 82A, 83A
CTextBlock::length 22A, 23A
CTextBlock::operator[] 21A
Customer::Customer 58A
Customer::operator= 58A
D::mf 178A
Date::Date 79A
Day::Day 79A
daysHeld 69A
DBConn::~DBConn 45A, 46A, 47A
DBConn::close 47A
defaultHealthCalc 172A, 173A
Derived::Derived 138A, 206A
Effective C++
Derived::mf1 160A
Derived::mf4 157A
Directory::Directory 31A, 32A
doAdvance 231A
doMultiply 226A
doProcessing 200A, 202A
doSomething 5A, 44A, 54A, 110A
doSomeWork 118A
eat 151A, 187A
ElectronicGadget::checkOut 192A
Empty::~Empty 34A
Empty::Empty 34A
Empty::operator= 34A
encryptPassword 114A, 115A
error 152A
EvilBadGuy::EvilBadGuy 172A
f 62A, 63A, 64A
FlyingBird::fly 152A
Font::~Font 71A
Font::Font 71A
Font::get 71A
Font::operator FontHandle 71A
GameCharacter::doHealthValue 170A
GameCharacter::GameCharacter 172A,
174A, 176A
GameCharacter::healthValue 169A, 170A,
172A, 174A, 176A
GameLevel::health 174A
getFont 70A
hasAcceptableQuality 6A
HealthCalcFunc::calc 176A
HealthCalculator::operator() 174A
lock 66A
Lock::~Lock 66A
Lock::Lock 66A, 68A
logCall 57A
LoggingMsgSender::sendClear 208A,
210A, 211A
loseHealthQuickly 172A
loseHealthSlowly 172A
main 141A, 142A, 236A, 241A
makeBigger 154A
makePerson 195A
max 135A
ModelA::fly 165A, 167A
ModelB::fly 165A, 167A
ModelC::fly 166A, 167A
Month::Dec 80A
Month::Feb 80A
Month::Jan 80A
Month::Month 79A, 80A
MsgSender::sendClear 208A
MsgSender::sendSecret 208A
MsgSender<CompanyZ>::sendSecret 209A
NewHandlerHolder::~NewHandlerHolder 243A
Effective C++
NewHandlerHolder::NewHandlerHolder 243A
NewHandlerSupport::operator new 245A
NewHandlerSupport::set_new_handler 245A
numDigits 4A
operator delete 255A
operator new 249A, 252A
operator* 91A, 92A, 94A, 105A, 222A,
224A, 225A, 226A
operator== 93A
outOfMem 240A
Penguin::fly 152A
Person::age 135A
Person::create 146A, 147A
Person::name 145A
Person::Person 145A
PersonInfo::theName 196A
PersonInfo::valueDelimClose 196A
PersonInfo::valueDelimOpen 196A
PrettyMenu::changeBackground 127A,
128A, 130A, 131A
print 20A
print2nd 204A, 205A
printNameAndDisplay 88A, 89A
priority 75A
PriorityCustomer::operator= 59A
PriorityCustomer::PriorityCustomer 59A
processWidget 75A
RealPerson::~RealPerson 147A
RealPerson::RealPerson 147A
Rectangle::doDraw 183A
Rectangle::draw 181A, 183A
Rectangle::lowerRight 124A, 125A
Rectangle::upperLeft 124A, 125A
releaseFont 70A
Set::insert 186A
Set::member 186A
Set::remove 186A
Set::size 186A
Shape::doDraw 183A
Shape::draw 161A, 162A, 180A, 182A, 183A
Shape::error 161A, 163A
Shape::objectID 161A, 167A
SmartPtr::get 220A
SmartPtr::SmartPtr 220A
someFunc 132A, 156A
SpecialWindow::blink 122A
SpecialWindow::onResize 119A, 120A
SquareMatrix::invert 214A
SquareMatrix::setDataPtr 215A
SquareMatrix::SquareMatrix 215A, 216A
StandardNewDeleteForms::operator
delete 260A, 261A
StandardNewDeleteForms::operator
new 260A, 261A
std::swap 109A
Index 287A
std::swap<Widget> 107A, 108A
study 151A, 187A
swap 106A, 109A
tempDir 32A
TextBlock::operator[] 20A, 23A, 24A
tfs 32A
Timer::onTick 188A
Transaction::init 50A
Transaction::Transaction 49A, 50A, 51A
Uncopyable::operator= 39A
Uncopyable::Uncopyable 39A
unlock 66A
validateStudent 87A
Widget::onTick 189A
Widget::operator new 244A
Widget::operator+= 53A
Widget::operator= 53A, 54A, 55A, 56A, 107A
Widget::set_new_handler 243A
Widget::swap 108A
Window::blink 122A
Window::onResize 119A
workWithIterator 206A, 207A
Year::Year 79A
exception specifications 85A
exceptions 113A
delete and 62A
destructors and 44–48A
member swap and 112A
standard hierarchy for 264A
swallowing 46A
unused objects and 114A
exception-safe code 127–134A
copy-and-swap and 132A
legacy code and 133A
pimpl idiom and 131A
side effects and 132A
exception-safety guarantees 128–129A
explicit calls to base class functions 211A
explicit constructors 5A, 85A, 104A
generalized copy construction and 219A
explicit inline request 135A
explicit specification, of class names 162A
explicit type conversions vs. implicit 70–72A
expression templates 237A
expressions, implicit interfaces and 201A
F
factoring code, out of templates 212–217A
factory function 40A, 62A, 69A, 81A, 146A,
195A
federation, of languages, C++ as 11–13A
final classes, in Java 43A
288A Index
Effective C++
final methods, in Java 190A
fixed-size static buffers, problems of 196A
forms of new and delete 73–75A
FORTRAN 42A
forward iterators 227A
forward_iterator_tag 228A
forwarding functions 144A, 160A
friend functions 38A, 85A, 105A, 135A,
generic programming support, in
Boost 271A
get, smart pointers and 70A
goddess, see Urbano, Nancy L.
goto, delete and 62A
Graph library, in Boost 270A
grep, casts and 117A
guarantees, exception safety 128–129A
173A, 223–225A
vs. member functions 98–102A
friendship
in real life 105A
without needing special access
rights 225A
FUDGE_FACTOR 15A
function declarations, const in 18A
function objects
definition of 6A
higher-order programming utilities
and, in Boost 271A
functions
convenience 100A
copying 57A
defining 4A
deliberately not defining 38A
factory, see factory function
forwarding 144A, 160A
implicitly generated 34–37A, 221A
disallowing 37–39A
inline, declaring 135A
member
templatized 218–222A
vs. non-member 104–105A
non-member
templates and 222–226A
type conversions and 102–105A, 222–
H
Handle classes 144–145A
handles 125A
dangling 126A
encapsulation and 124A
operator[] and 126A
returning 123–126A
has-a relationship 184A
hash tables, in TR1 266A
Hastings, Battle of 150A
head scratching, avoiding 95A
header files, see headers
headers
for declarations vs. for definitions 144A
inline functions and 135A
namespaces and 100A
of C++ standard library 101A
templates and 136A
usage, in this book 3A
hello world, template metaprogramming
and 235A
hiding names, see name hiding
higher-order programming and function
object utilities, in Boost 271A
highlighting, in this book 5A
226A
non-member non-friend, vs
member 98–102A
non-virtual, meaning 168A
return values, modifying 21A
signatures, explicit interfaces and 201A
static
ctors and dtors and 52A
virtual, see virtual functions
function-style casts 116A
G
generalized assignment 220A
generalized copy constructors 219A
generative programming 237A
I
identity test 55A
if...else for types 230A
#ifdef 17A
#ifndef 17A
implementation-dependent behavior,
warnings and 263A
implementations
decoupling from interfaces 165A
default, danger of 163–167A
inheritance of 161–169A
of derived class constructors and
destructors 137A
of Interface classes 147A
references 89A
Effective C++
std::max 135A
std::swap 106A
implicit inline request 135A
implicit interfaces 199–203A
implicit type conversions vs. explicit 70–
72A
implicitly generated functions 34–37A, 221A
disallowing 37–39A
#include directives 17A
compilation dependencies and 140A
incompatibilities, with built-in types 80A
incorrect code and efficiency 90A
infinite loop, in operator new 253A
inheritance
accidental 165–166A
combining with templates 243–245A
common features and 164A
intuition and 151–155A
mathematics and 155A
mixin-style 244A
name hiding and 156–161A
of implementation 161–169A
of interface 161–169A
of interface vs. implementation 161–169A
operator new and 253–254A
penguins and birds and 151–153A
private 187–192A
protected 151A
public 150–155A
rectangles and squares and 153–155A
redefining non-virtual functions
and 178–180A
scopes and 156A
sharing features and 164A
inheritance, multiple 192–198A
ambiguity and 192A
combining public and private 197A
deadly diamond 193A
inheritance, private 214A
combining with public 197A
eliminating 189A
for redefining virtual functions 197A
meaning 187A
vs. composition 188A
inheritance, public
combining with private 197A
is-a relationship and 150–155A
meaning of 150A
name hiding and 159A
virtual inheritance and 194A
inheritance, virtual 194A
init function 60A
initialization 4A, 26–27A
assignment vs. 6A
Index 289A
built-in types 26–27A
const members 29A
const static members 14A
default, unintended 59A
in-class, of static const integral
members 14A
local static objects 31A
non-local static objects 30A
objects 26–33A
reference members 29A
static members 242A
virtual base classes and 194A
vs. assignment 27–29A, 114A
with vs. without arguments 114A
initialization order
class members 29A
importance of 31A
non-local statics 29–33A
inline functions
see also inlining
address of 136A
as request to compiler 135A
debuggers and 139A
declaring 135A
headers and 135A
optimizing compilers and 134A
recursion and 136A
vs. #define 16–17A
vs. macros, efficiency and 16A
inlining 134–139A
constructors/destructors and 137–138A
dynamic linking and 139A
Handle classes and 148A
inheritance and 137–138A
Interface classes and 148A
library design and 138A
recompiling and 139A
relinking and 139A
suggested strategy for 139A
templates and 136A
time of 135A
virtual functions and 136A
input iterators 227A
input_iterator_tag 228A
input_iterator_tag<Iter*> 230A
insomnia 150A
instructions, reordering by compilers 76A
integral types 14A
Interface classes 145–147A
interfaces
decoupling from implementations 165A
definition of 7A
design considerations 78–86A
explicit, signatures and 201A
290A Index
Effective C++
implicit 199–203A
expressions and 201A
inheritance of 161–169A
new types and 79–80A
separating from implementations 140A
template parameters and 199–203A
undeclared 85A
inter-language support, in Boost 272A
internationalization, library support
for 264A
invalid array index, undefined behavior
and 7A
invariants
NVI and 171A
over specialization 168A
<iosfwd> 144A
is-a relationship 150–155A
is-implemented-in-terms-of 184–186A, 187A
istream_iterators 227A
iterator categories 227–228A
iterator_category 229A
iterators as handles 125A
iterators, vs. const_iterators 18A
J
Java 7A, 43A, 76A, 81A, 100A, 116A, 118A,
142A, 145A, 190A, 194A
K
Koenig lookup 110A
L
Lambda library, in Boost 271A
languages, other, compatibility with 42A
late binding 180A
layering, see composition
layouts, objects vs. arrays 73A
leaks, exception-safe code and 127A
legacy code, exception-safety and 133A
lemur, ring-tailed 196A
lhs, as parameter name 8A
link-time errors 39A, 44A
link-time inlining 135A
list 186A
local static objects
definition of 30A
initialization of 31A
locales 264A
locks, RAII and 66–68A
logic_error class 113A
logically const member functions 22–23A
M
maintenance
common base classes and 164A
delete and 62A
managing resources, see resource management
math and numerics utilities, in Boost 271A
mathematical functions, in TR1 267A
mathematics, inheritance and 155A
matrix operations, optimizing 237A
max, std, implementation of 135A
meaning
of classes without virtual functions 41A
of composition 184A
of non-virtual functions 168A
of pass-by-value 6A
of private inheritance 187A
of public inheritance 150A
of pure virtual functions 162A
of references 91A
of simple virtual functions 163A
measuring encapsulation 99A
member data, see data members
member function templates 218–222A
member functions
bitwise const 21–22A
common design errors 168–169A
const 19–25A
duplication and 23–25A
encapsulation and 99A
implicitly generated 34–37A, 221A
disallowing 37–39A
logically const 22–23A
private 38A
protected 166A
vs. non-member functions 104–105A
vs. non-member non-friends 98–102A
member initialization
for const static integral members 14A
lists 28–29A
vs. assignment 28–29A
order 29A
memory allocation
arrays and 254–255A
error handling for 240–246A
memory leaks, new expressions and 256A
memory management
functions, replacing 247–252A
multithreading and 239A, 253A
utilities, in Boost 272A
Effective C++
Index 291A
metaprogramming, see template metaprogramming
mf, as identifier 9A
minimizing compilation
dependencies 140–148A, 190A
mixed-mode arithmetic 103A, 104A, 222–226A
mixin-style inheritance 244A
modeling is-implemented-in-termsof 184–186A
modifying function return values 21A
Monty Python, allusion to 91A
More Effective C++ 273A, 273–274A
compared to Effective C++ 273A
contents of 273–274A
MPL library, in Boost 270A, 271A
multiparadigm programming language,
C++ as 11A
multiple inheritance, see inheritance
multithreading
memory management routines
and 239A, 253A
non-const static objects and 32A
treatment in this book 9A
mutable 22–23A
mutexes, RAII and 66–68A
N
name hiding
inheritance and 156–161A
operators new/delete and 259–261A
using declarations and 159A
name lookup
this-> and 210A
using declarations and 211A
name shadowing, see name hiding
names
accessing in templatized bases 207–212A
available in both C and C++ 3A
dependent 204A
hidden by derived classes 263A
nested, dependent 204A
non-dependent 204A
namespaces 110A
headers and 100A
namespace pollution in a class 166A
Nancy, see Urbano, Nancy L.
nested dependent names 204A
nested dependent type names, typename
and 205A
new
see also operator new
expressions, memory leaks and 256A
forms of 73–75A
operator new and 73A
relationship to constructors 73A
smart pointers and 75–77A
new types, interface design and 79–80A
new-handler 240–247A
definition of 240A
deinstalling 241A
identifying 253A
new-handling functions, behavior of 241A
new-style casts 117A
noncopyable base class, in Boost 39A
non-dependent names 204A
non-local static objects, initialization
of 30A
non-member functions
member functions vs. 104–105A
templates and 222–226A
type conversions and 102–105A, 222–226A
non-member non-friend functions 98–102A
non-type parameters 213A
non-virtual
functions 178–180A
static binding of 178A
interface idiom, see NVI
nothrow guarantee, the 129A
nothrow new 246A
null pointer
deleting 255A
dereferencing 6A
set_new_handler and 241A
NVI 170–171A, 183A
O
object-oriented C++, as sublanguage of
C++ 12A
object-oriented principles, encapsulation
and 99A
objects
alignment of 249–250A
clustering 251A
compilation dependencies and 143A
copying all parts 57–60A
defining 4A
definitions, postponing 113–116A
handles to internals of 123–126A
initialization, with vs. without
arguments 114A
layout vs. array layout 73A
multiple addresses for 118A
partial copies of 58A
placing in shared memory 251A
resource management and 61–66A
292A Index
Effective C++
returning, vs. references 90–94A
size, pass-by-value and 89A
sizes, determining 141A
vs. variables 3A
old-style casts 117A
operations, reordering by compilers 76A
order
initialization of non-local statics 29–33A
member initialization 29A
ostream_iterators 227A
other languages, compatibility with 42A
output iterators 227A
operator delete 84A
see also delete
output_iterator_tag 228A
behavior of 255A
efficiency of 248A
name hiding and 259–261A
non-member, pseudocode for 255A
placement 256–261A
replacing 247–252A
standard forms of 260A
virtual destructors and 255A
operator delete[] 84A, 255A
operator new 84A
see also new
arrays and 254–255A
bad_alloc and 246A, 252A
behavior of 252–255A
efficiency of 248A
infinite loop within 253A
inheritance and 253–254A
member, and “wrongly sized”
requests 254A
name hiding and 259–261A
new-handling functions and 241A
non-member, pseudocode for 252A
out-of-memory conditions and 240–
241A, 252–253A
placement 256–261A
replacing 247–252A
returning 0 and 246A
standard forms of 260A
std::bad_alloc and 246A, 252A
operator new[] 84A, 254–255A
operator() (function call operator) 6A
operator=
const members and 36–37A
default implementation 35A
implicit generation 34A
reference members and 36–37A
return value of 52–53A
self-assignment and 53–57A
when not implicitly generated 36–37A
operator[] 126A
overloading on const 19–20A
return type of 21A
optimization
by compilers 94A
during compilation 134A
inline functions and 134A
overloading
as if...else for types 230A
on const 19–20A
std::swap 109A
overrides of virtuals, preventing 189A
ownership transfer 68A
P
parameters
see also pass-by-value, pass-by-reference
default 180–183A
evaluation order 76A
non-type, for templates 213A
type conversions and, see type conversions
Pareto Principle, see 80-20 rule
parsing problems, nested dependent
names and 204A
partial copies 58A
partial specialization
function templates 109A
std::swap 108A
parts, of objects, copying all 57–60A
pass-by-reference, efficiency and 87A
pass-by-reference-to-const, vs pass-byvalue 86–90A
pass-by-value
copy constructor and 6A
efficiency of 86–87A
meaning of 6A
object size and 89A
vs. pass-by-reference-to-const 86–90A
patterns
see design patterns
penguins and birds 151–153A
performance, see efficiency
Persephone 36A
pessimization 93A
physical constness, see const, bitwise
pimpl idiom
definition of 106A
exception-safe code and 131A
placement delete, see operator delete
Effective C++
Index 293A
placement new, see operator new
Plato 87A
pointer arithmetic and undefined
behavior 119A
pointers
see also smart pointers
as handles 125A
bitwise const member functions and 21A
compilation dependencies and 143A
const 17A
in headers 14A
null, dereferencing 6A
template parameters and 217A
to single vs. multiple objects, and
delete 73A
polymorphic base classes, destructors
and 40–44A
polymorphism 199–201A
compile-time 201A
runtime 200A
Pool library, in Boost 250A, 251A
postponing variable definitions 113–116A
preconditions, NVI and 171A
pregnancy, exception-safe code and 133A
private data members, why 94–98A
private inheritance, see inheritance
private member functions 38A
private virtual functions 171A
properties 97A
protected
data members 97A
inheritance, see inheritance
member functions 166A
members, encapsulation of 97A
public inheritance, see inheritance
pun, really bad 152A
pure virtual destructors
defining 43A
implementing 43A
pure virtual functions 43A
defining 162A, 166–167A
meaning 162A
R
RAII 63A, 70A, 243A
classes 72A
copying behavior and 66–69A
encapsulation and 72A
mutexes and 66–68A
random access iterators 227A
random number generation, in TR1 267A
random_access_iterator_tag 228A
RCSP, see smart pointers
reading uninitialized values 26A
rectangles and squares 153–155A
recursive functions, inlining and 136A
redefining inherited non-virtual
functions 178–180A
references
as handles 125A
compilation dependencies and 143A
functions returning 31A
implementation 89A
meaning 91A
members, initialization of 29A
returning 90–94A
to static object, as function return
value 92–94A
register usage, objects and 89A
regular expressions, in TR1 266A
reinterpret_cast 117A, 249A
see also casting
relationships
has-a 184A
is-a 150–155A
is-implemented-in-terms-of 184–186A,
187A
reordering operations, by compilers 76A
replacing definitions with
declarations 143A
replacing new/delete 247–252A
replication, see duplication
Resource Acquisition Is Initialization, see
RAII
resource leaks, exception-safe code
and 127A
resource management
see also RAII
copying behavior and 66–69A
objects and 61–66A
raw resource access and 69–73A
resources, managing objects and 69–73A
return by reference 90–94A
return types
const 18A
objects vs. references 90–94A
of operator[] 21A
return value of operator= 52–53A
returning handles 123–126A
reuse, see code reuse
revenge, compilers taking 58A
294A Index
Effective C++
rhs, as parameter name 8A
rule of 80-20 139A, 168A
runtime
errors 152A
inlining 135A
polymorphism 200A
S
Satch 36A
scoped_array 65A, 216A, 272A
scopes, inheritance and 156A
sealed classes, in C# 43A
sealed methods, in C# 190A
second edition, see 2nd edition
self-assignment, operator= and 53–57A
set 185A
set_new_handler
class-specific, implementing 243–245A
using 240–246A
set_unexpected function 129A
shadowing, names, see name shadowing
Shakespeare, William 156A
shared memory, placing objects in 251A
shared_array 65A
shared_ptr implementation in Boost,
costs 83A
sharing code, see duplication, avoiding
sharing common features 164A
side effects, exception safety and 132A
signatures
definition of 3A
explicit interfaces and 201A
simple virtual functions, meaning of 163A
Singleton pattern 31A
size_t 3A
sizeof 253A, 254A
empty classes and 190A
freestanding classes and 254A
sizes
of freestanding classes 254A
of objects 141A
sleeping pills 150A
slist 227A
Smalltalk 142A
smart pointers 63A, 64A, 70A, 81A, 121A, 146A, 237A
see also std::auto_ptr and tr1::shared_ptr
get and 70A
in Boost 65A, 272A
in TR1 265A
newed objects and 75–77A
type conversions and 218–220A
Socrates 87A
Some Must Watch While Some Must
Sleep 150A
specialization
invariants over 168A
partial, of std::swap 108A
total, of std::swap 107A, 108A
specification, see interfaces
squares and rectangles 153–155A
standard exception hierarchy 264A
standard forms of operator new/delete 260A
standard library, see C++ standard
library, C standard library
standard template library, see STL
statements using new, smart pointers
and 75–77A
static
binding
of default parameters 182A
of non-virtual functions 178A
objects, returning references to 92–94A
type, definition of 180A
static functions, ctors and dtors and 52A
static members
const member functions and 21A
definition 242A
initialization 242A
static objects
definition of 30A
multithreading and 32A
static_cast 25A, 82A, 117A, 119A, 249A
see also casting
std namespace, specializing templates
in 107A
std::auto_ptr 63–65A, 70A
conversion to tr1::shared_ptr and 220A
delete [] and 65A
pass by const and 220A
std::auto_ptr, deleter support and 68A
std::char_traits 232A
std::iterator_traits, pointers and 230A
std::list 186A
std::max, implementation of 135A
std::numeric_limits 232A
std::set 185A
std::size_t 3A
std::swap
see also swap
implementation of 106A
Effective C++
Index 295A
overloading 109A
partial specialization of 108A
total specialization of 107A, 108A
std::tr1, see TR1
stepping through functions, inlining
and 139A
STL
allocators 240A
as sublanguage of C++ 12A
containers, swap and 108A
definition of 6A
iterator categories in 227–228A
Strategy pattern 171–177A
string and text utilities, in Boost 271A
strong guarantee, the 128A
swallowing exceptions 46A
swap 106–112A
see also std::swap
calling 110A
exceptions and 112A
STL containers and 108A
when to write 111A
symbols, available in both C and C++ 3A
T
template C++, as sublanguage of C++ 12A
template metaprogramming 233–238A
efficiency and 233A
hello world in 235A
pattern implementations and 237A
support in Boost 271A
support in TR1 267A
Template Method pattern 170A
templates
code bloat, avoiding in 212–217A
combining with inheritance 243–245A
defining 4A
errors, when detected 212A
expression 237A
headers and 136A
in std, specializing 107A
inlining and 136A
instantiation of 222A
member functions 218–222A
names in base classes and 207–212A
non-type parameters 213A
parameters, omitting 224A
pointer type parameters and 217A
shorthand for 224A
specializations 229A, 235A
partial 109A, 230A
total 107A, 209A
type conversions and 222–226A
type deduction for 223A
temporary objects, eliminated by
compilers 94A
terminology, used in this book 3–8A
testing and correctness, Boost support
for 272A
text and string utilities, in Boost 271A
third edition, see 3rd edition
this->, to force base class lookup 210A
threading, see multithreading
TMP, see template metaprogramming
total class template specialization 209A
total specialization of std::swap 107A, 108A
total template specializations 107A
TR1 9A, 264–267A
array component 267A
bind component 266A
Boost and 9–10A, 268A, 269A
boost as synonym for std::tr1 268A
C99 compatibility component 267A
function component 265A
hash tables component 266A
math functions component 267A
mem_fn component 267A
random numbers component 267A
reference_wrapper component 267A
regular expression component 266A
result_of component 267A
smart pointers component 265A
support for TMP 267A
tuples component 266A
type traits component 267A
URL for information on 268A
tr1::array 267A
tr1::bind 175A, 266A
tr1::function 173–175A, 265A
tr1::mem_fn 267A
tr1::reference_wrapper 267A
tr1::result_of 267A
tr1::shared_ptr 53A, 64–65A, 70A, 75–77A
construction from other smart pointers
and 220A
cross-DLL problem and 82A
delete [] and 65A
deleter support in 68A, 81–83A
member template ctors in 220–221A
tr1::tuple 266A
tr1::unordered_map 43A, 266A
tr1::unordered_multimap 266A
tr1::unordered_multiset 266A
tr1::unordered_set 266A
296A Index
Effective C++
tr1::weak_ptr 265A
traits classes 226–232A
transfer, ownership 68A
translation unit, definition of 30A
tuples, in TR1 266A
type conversions 85A, 104A
explicit ctors and 5A
implicit 104A
implicit vs. explicit 70–72A
non-member functions and 102–105A,
222–226A
private inheritance and 187A
smart pointers and 218–220A
templates and 222–226A
type deduction, for templates 223A
type design 78–86A
type traits, in TR1 267A
typedef, typename and 206–207A
typedefs, new/delete and 75A
typeid 50A, 230A, 234A, 235A
typelists 271A
typename 203–207A
compiler variations and 207A
typedef and 206–207A
vs. class 203A
types
built-in, initialization 26–27A
compatible, accepting all 218–222A
if...else for 230A
integral, definition of 14A
traits classes and 226–232A
U
undeclared interface 85A
undefined behavior
advance and 231A
array deletion and 73A
casting + pointer arithmetic and 119A
definition of 6A
destroyed objects and 91A
exceptions and 45A
initialization order and 30A
invalid array index and 7A
multiple deletes and 63A, 247A
null pointers and 6A
object deletion and 41A, 43A, 74A
uninitialized values and 26A
undefined values of members before construction and after destruction 50A
unexpected function 129A
uninitialized
data members, virtual functions and 49A
values, reading 26A
unnecessary objects, avoiding 115A
unused objects
cost of 113A
exceptions and 114A
URLs
Boost 10A, 269A, 272A
Effective C++ TR1 Info. Page 268A
usage statistics, memory management
and 248A
using declarations
name hiding and 159A
name lookup and 211A
V
valarray 264A
value, pass by, see pass-by-value
variable, vs. object 3A
variables definitions, postponing 113–116A
vector template 75A
virtual base classes 193A
virtual constructors 146A, 147A
virtual destructors
operator delete and 255A
polymorphic base classes and 40–44A
virtual functions
alternatives to 169–177A
ctors/dtors and 48–52A
default implementations and 163–167A
default parameters and 180–183A
dynamic binding of 179A
efficiency and 168A
explict base class qualification and 211A
implementation 42A
inlining and 136A
language interoperability and 42A
meaning of none in class 41A
preventing overrides 189A
private 171A
pure, see pure virtual functions
simple, meaning of 163A
uninitialized data members and 49A
virtual inheritance, see inheritance
virtual table 42A
virtual table pointer 42A
vptr 42A
vtbl 42A
Effective C++
Index 297A
W
warnings, from compiler 262–263A
calls to virtuals and 50A
inlining and 136A
partial copies and 58A
web sites, see URLs
Widget class, as used in this book 8A
Wizard of Oz, allusion to 154A
X
XP, allusion to 225A
XYZ Airlines 163A
This page intentionally left blank
More Effective C++
Praise for More Effective C++: 35 New Ways
to Improve Your Programs and Designs
“This is an enlightening book on many aspects of C++: both the regions of the
language you seldom visit, and the familiar ones you THOUGHT you understood.
Only by understanding deeply how the C++ compiler interprets your code can
you hope to write robust software using this language. This book is an invaluable
resource for gaining that level of understanding. After reading this book, I feel like
I've been through a code review with a master C++ programmer, and picked up
many of his most valuable insights.”
— Fred Wild, Vice President of Technology,
Advantage Software Technologies
“This book includes a great collection of important techniques for writing
programs that use C++ well. It explains how to design and implement the ideas,
and what hidden pitfalls lurk in some obvious alternative designs. It also includes
clear explanations of features recently added to C++. Anyone who wants to use
these new features will want a copy of this book close at hand for ready reference.”
— Christopher J. Van Wyk, Professor,
Mathematics and Computer Science, Drew University
“Industrial strength C++ at its best. The perfect companion to those who have
read Effective C++.”
— Eric Nagler, C++ Instructor and Author,
University of California Santa Cruz Extension
“More Effective C++ is a thorough and valuable follow-up to Scott's first book,
Effective C++. I believe that every professional C++ developer should read and
commit to memory the tips in both Effective C++ and More Effective C++. I've
found that the tips cover poorly understood, yet important and sometimes arcane
facets of the language. I strongly recommend this book, along with his first, to
developers, testers, and managers ... everyone can benefit from his expert
knowledge and excellent presentation.”
— Steve Burkett, Software Consultant
More Effective C++
35 New Ways to Improve Your Programs and Designs
Scott Meyers
▲
▼▼
ADDISON-WESLEY
Boston • San Francisco • New York • Toronto • Montreal
London • Munich • Paris • Madrid
Capetown • Sydney • Tokyo • Singapore • Mexico City
Many of the designations used by manufacturers and sellers to distinguish their products are claimed as trademarks.
Where those designations appear in this book, and the publisher was aware of a trademark claim, the designations have
been printed with initial capital letters or in all capitals.
The author and publisher have taken care in the preparation of this book, but make no expressed or implied warranty of
any kind and assume no responsibility for errors or omissions. No liability is assumed for incidental or consequential
damages in connection with or arising out of the use of the information or programs contained herein.
The publisher offers excellent discounts on this book when ordered in quantity for bulk purchases or special sales, which
may include electronic versions and/or custom covers and content particular to your business, training goals, marketing
focus, and branding interests. For more information, please contact:
U.S. Corporate and Government Sales
(800) 382-3419
corpsales@pearsontechgroup.com
For sales outside the United States please contact:
International Sales
international@pearsoned.com
Library of Congress Cataloging-in-Publication Data
Meyers, Scott (Scott Douglas)
More effective C++: 35 new ways to improve your programs and
designs / Scott Meyers.
p. cm. — (Addison-Wesley professional computing series)
Includes bibliographical references and index.
ISBN 0-201-63371-X (paperback: alk. paper)
1. C++ (Computer program language) I. Series.
QA76.73.C153M495 1996
005.13'3—dc20
95–47354
CIP
Copyright © 1996 by Addison-Wesley
All rights reserved. Printed in the United States of America. This publication is protected by copyright, and permission
must be obtained from the publisher prior to any prohibited reproduction, storage in a retrieval system, or transmission in
any form or by any means, electronic, mechanical, photocopying, recording, or likewise. For information regarding permissions, write to:
Pearson Education, Inc
Rights and Contracts Department
501 Boylston Street, Suite 900
Boston, MA 02116
Fax (617) 671-3447
ISBN-13: 978-0-201-63371-9
ISBN-10:
0-201-63371-X
Text printed in the United States on recycled paper at Courier in Stoughton, Massachusetts.
Twenty-fourth printing
April 2007
For Clancy,
my favorite enemy within.
This page intentionally left blank
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 on it.
When the number of contributors to a book is large, it is not uncommon to dispense with individual acknowledgments in favor of a generic “Contributors to this book are too numerous to mention.” I
prefer to follow the expansive lead of John L. Hennessy and David A.
Patterson in Computer Architecture: A Quantitative Approach (Morgan
Kaufmann, first edition 1990). In addition to motivating the comprehensive acknowledgments 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 I discuss came from others. I have
done my best to keep track of who contributed what, but I know I have
included information 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 note only 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 I describe in Item 2.
In Item 3, my warning about deleting an array of derived class objects
through a base class pointer is based on material in Dan Saks’ “Gotchas” talk, which he’s given at several conferences and trade shows.
viii
Acknowledgments
In Item 5, the proxy class technique for preventing unwanted application of single-argument constructors is based 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 via the 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 distinction between operator new and the
new operator that is the crux of Item 8. Even after reading his letter, I
didn’t really understand 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 Margaret A. Ellis’ and Bjarne
Stroustrup’s The Annotated C++ Reference Manual (see page 285B).
There the technique is called resource acquisition is initialization. Tom
Cargill suggested I shift the focus of the approach from resource acquisition to resource release.
Some of my discussion in Item 11 was inspired by material in Chapter
4 of Taligent’s Guide to Designing Programs (Addison-Wesley, 1994).
My description of over-eager memory allocation for the DynArray class
in Item 18 is based on Tom Cargill’s article, “A Dynamic vector is
harder than it looks,” in the June 1992 C++ Report. A more sophisticated design for a dynamic array class can be found in Cargill’s followup 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 multiple overloadings 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 Jamshid Afshar.
The idea of a mixin class to keep track of pointers from operator new
(see Item 27) is based on a suggestion by Don Box. Steve Clamage
made the idea practical by explaining how dynamic_cast can be used
to find the beginning 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++ Oracle column in the October 1993 C++
Report; on Daniel R. Edelson’s classic paper, “Smart Pointers: They’re
Smart, but They’re Not Pointers,” in the proceedings of the 1992
Acknowledgments
ix
USENIX C++ Conference; on section 15.9.1 of Bjarne Stroustrup’s The
Design and Evolution of C++ (see page 285B); on Gregory Colvin’s “C++
Memory Management” class notes from C/C++ Solutions ’95; and on
Cay Horstmann’s column in the March-April 1993 issue 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 is based 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 286B). The design for
adding reference counting to existing classes follows that presented 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 (now the C/C++ Users Journal) of January 1993. The observation that non-proxy member functions are unavailable when called 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 ideas put forward by Bjarne
Stroustrup in postings to comp.lang.c++ and in section 13.8.1 of his
The Design and Evolution of C++ (see page 285B).
The material in Item 33 is based on several of my C++ Report columns
in 1994 and 1995. Those columns, in turn, included comments I received from Klaus Kreft about how to use dynamic_cast to implement
a virtual operator= 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 strdup was 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 were willing to invest their
time and energy on my behalf. I am especially grateful to Jill Huchital,
Tim Johnson, Brian Kernighan, Eric Nagler, and Chris Van Wyk, as
they read the book (or large portions of it) more than once. In addition
to these gluttons for punishment, complete drafts of the manuscript
were read by Katrina Avery, Don Box, 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,
x
Acknowledgments
Jeff Jackson, and Nancy L. Urbano. Each of these reviewers made
comments that greatly improved the accuracy, utility, and presentation of the material you find here.
Once the book came out, I received corrections and suggestions from
many people: Luis Kida, John Potter, Tim Uttormark, Mike Fulkerson,
Dan Saks, Wolfgang Glunz, Clovis Tondo, Michael Loftus, Liz Hanks, Wil
Evers, Stefan Kuhlins, Jim McCracken, 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, Oleg Shteynbuk, 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,
Chris Morley, Rainer Baumschlager, Christopher Tavares, Brian Kernighan, Charles Green, Mark Rodgers, Bobby Schmidt, Sivaramakrishnan J., Eric Anderson, Phil Brabbin, Feliks Kluzniak, Evan McLean,
Kurt Miller, Niels Dekker, Balog Pal, Dean Stanton, William Mattison,
Chulsu Park, Pankaj Datta, John Newell, Ani Taggu, Christopher
Creutzi, Chris Wineinger, Alexander Bogdanchikov, Michael Tegtmeyer,
Aharon Robbins, Davide Gennaro, Adrian Spermezan, Matthias Hofmann, Chang Chen, John Wismar, Mark Symonds, Thomas Kim, Ita
Ryan, Rice Yeh, and Colas Schretter. Their suggestions allowed me to
improve More Effective C++ in updated printings (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 I am grateful to Steve
Clamage and Dan Saks for taking the time to respond to my incessant
email queries.
John Max Skaller and Steve Rumsby conspired to get me the HTML for
the draft ANSI C++ standard before it was widely available. Vivian Neou
pointed me to the Netscape WWW browser as a stand-alone HTML
viewer under (16 bit) Microsoft Windows, and I am deeply grateful to the
folks at Netscape Communications for making their 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 of the MetaWare C++ compiler
so I could check the code in this book using the latest features of the
language. Cay Horstmann helped me get the compiler up and running
in the very foreign world of DOS and DOS extenders. Borland provided
a beta copy of their most advanced compiler, and Eric Nagler and
Chris Sells provided invaluable help in testing code for me on compilers to which I had no access.
Acknowledgments
xi
Without the staff at the Corporate and Professional Publishing Division of Addison-Wesley, there would be no book, 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 research on low-temperature physics
to critique later versions of that text.
Tom Cargill graciously agreed to make his C++ Report article on exceptions (see page 287B) available at the Addison-Wesley Internet site.
The People
Kathy Reed was responsible for my introduction to programming;
surely she didn’t deserve to have to put up with a kid like me. Donald
French had faith in my ability to develop and present C++ teaching
materials when I had no track record. He also introduced me to John
Wait, my editor at Addison-Wesley, an act for which I will always be
grateful. The triumvirate at Beaver Ridge — Jayni Besaw, Lorri Fields,
and Beth McKee — provided untold 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 do something after the book was done? Now the book is
done, and we will do those things. She amazes me. I love her.
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 less comic relief.
This page intentionally left blank
Introduction
These are heady days for C++ programmers. Commercially available
less than a decade, C++ has nevertheless emerged 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 the question faced
by those who do not use C++ is often when they will start, not if. Standardization of C++ is complete, 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 common algorithms and data structures
from scratch. C++ compilers continue to proliferate, the features they
offer continue to expand, and the quality of the code they generate continues to improve. Tools and environments for C++ development grow
ever more abundant, powerful, and robust. Commercial libraries all
but obviate the need to write code in many application areas.
Introduction
As the language has matured and our experience with it has increased,
our needs for information about it have changed. 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 future demands? How can
I improve the efficiency of my code without compromising its correctness or making it harder 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 behave correctly; more robust in the face
of exceptions; more efficient; more portable; makes better use of language features; adapts to change more gracefully; works better in a
mixed-language environment; is easier to use correctly; is harder to
use incorrectly. In short, software that’s just better.
2B
Introduction
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 explanation accompanying each guideline describes why the guideline exists, what happens if you fail to follow it, and under what conditions it
may make sense to violate the guideline anyway.
Items fall into several categories. Some concern particular language
features, especially newer features with which you may have little experience. For example, Items 9 through 15 are devoted to exceptions.
Other Items explain how to combine the features of the language to
achieve higher-level goals. Items 25 through 31, for instance, describe
how to constrain the number or placement of objects, how to create
functions that act “virtual” on the type of more than one object, how to
create “smart pointers,” and more. Still other Items address broader
topics; Items 16 through 24 focus on efficiency. No matter what the
topic of a particular Item, each takes a no-nonsense approach to the
subject. In More Effective C++, you learn how to use C++ more effectively. The descriptions of language features 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 for granted that you understand
classes, protection levels, virtual and nonvirtual functions, etc., and I
assume you are acquainted with the concepts behind templates and
exceptions. At the same time, I don’t expect you to be a language 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 1998
International Standard for C++. This means I may use a few features
your compilers don’t yet support. Don’t worry. The only “new” feature
I assume you have is templates, and templates are 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’s okay. It
won’t affect your ability to take advantage of the material in the other
parts of the book. Furthermore, you should read Items 9 through 15
even if you don’t have support for exceptions, because those items examine issues you need to understand in any case.
I recognize that just because the standardization committee blesses a
feature or endorses a practice, there’s no guarantee that the feature is
present in current compilers or the practice is applicable to existing
Introduction
3B
environments. When faced with a discrepancy between theory (what
the committee says) and practice (what actually works), I discuss both,
though my bias is toward things that work. Because I discuss both,
this book will aid you as your compilers approach conformance with
the standard. It will show you how to use existing constructs to approximate language features your compilers don’t yet support, and it
will guide you when you decide to transform workarounds into newlysupported features.
Notice that I refer to your compilers — plural. Different compilers implement varying approximations to the standard, so I encourage you to
develop your code under at least two compilers. Doing so will help you
avoid inadvertent 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
features supported 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 trails can be exciting, but when your goal is producing reliable code, it’s often best to let others test the waters before
jumping in.
There are two constructs you’ll see in this book that may not be familiar to you. Both are relatively recent language extensions. Some compilers support them, but if your compilers don’t, you can easily
approximate them with 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’t implemented 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 the disadvantage that the built-in comparison operators (i.e., ==, <, >=, etc.) still return ints. As a result, code
like the 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 that truly supports bool.
4B
Introduction
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 this approximation won’t change when
they’re ported to bool-supporting compilers. The drawback is that you
can’t differentiate between bool and int when overloading functions.
Both approximations are reasonable. Choose the one that best fits
your circumstances.
The second new construct is really four constructs, the casting forms
static_cast, const_cast, dynamic_cast, and reinterpret_cast.
If you’re not familiar with these casts, you’ll want to turn to Item 2 and
read all about them. Not only do they do more than the C-style casts
they replace, they do it better. I use these new casting forms whenever
I need to perform a cast in this book.
There is more to C++ than the language itself. There is also the standard library. Where possible, I employ the standard string type instead of using raw char* pointers, and I encourage you to do the
same. string objects are no more difficult to manipulate than char*based strings, and they relieve you of most memory-management concerns. Furthermore, string objects are less susceptible to memory
leaks if an exception is thrown (see Items 9 and 10). A well-implemented string type can hold its own in an efficiency contest with its
char* equivalent, and it may even do better. (For insight into how this
could be, see Item 29.) If you don’t have access to an implementation
of the standard string type, you almost certainly have access to some
string-like class. 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 Standard Template 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 otherwise be
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 STL web site: http://www.sgi.com/tech/stl/.
Introduction
5B
If you currently use a library of algorithms and data structures and are
happy with it, there’s no need to switch to the STL just because it’s
“standard.” However, if you have a choice between using an STL component or writing your own code from scratch, you should lean toward
using the STL. Remember code reuse? STL (and the rest of the 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.
If I don’t mean public inheritance, I’ll say so explicitly. When drawing
inheritance hierarchies, I depict base-derived relationships by drawing
arrows from derived classes to base classes. For example, here is a hierarchy from Item 31:
GameObject
SpaceShip
SpaceStation
Asteroid
This notation is the reverse of the convention I employed in the first
(but not the second) edition of Effective C++. I’m now convinced that
most C++ practitioners draw inheritance arrows from derived to base
classes, and I am happy to follow suit. Within such diagrams, abstract
classes (e.g., GameObject) are shaded and concrete classes (e.g.,
SpaceShip) are unshaded.
Inheritance gives rise to pointers and references with two different
types, a static type and a dynamic type. The static type of a pointer or
reference is its declared type. The dynamic type is determined by the
type of object it actually refers to. Here are some examples based on
the classes above:
GameObject *pgo =
new SpaceShip;
// static type of pgo is
// 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*
6B
Introduction
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 a pointer-to-Asteroid; rgo is a reference-to-GameObject. I often concoct pointer and reference names in
this fashion.
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 rational numbers:
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 appears on 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 support for 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 constructors when 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 Widget constructor allocates additional re-
Introduction
7B
sources that are to be released when the Widget is destroyed (such as
file descriptors, semaphores, window handles, database locks, etc.),
those resources are lost just as surely as the memory is. To emphasize
that memory leaks in C++ often leak other resources, too, I usually
speak of resource leaks 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 that inline functions are an important feature of C++. However, the criteria for determining whether a
function should be inlined can be complex, subtle, and platform-dependent. As a result, I avoid inlining unless there is a point about inlining I wish to make. When you see a non-inline function in More
Effective C++, that doesn’t mean I think it would be a bad idea to declare the function inline, it just means the decision to inline that
function 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 for eventual removal from the language, because newer features have been added that do what the
deprecated features 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 about their use. In the interest of preserving
backward compatibility for their customers, compiler vendors are
likely to support 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 your clients as well. Clients are important. In fact, clients are the name of the game! If nobody
uses the software you write, why write it? You will find I worry a lot
about making things easier for clients, often at the expense of making
things more difficult for you, because good software is “clientcentric”
— it revolves around clients. If this strikes you as unreasonably philanthropic, view it instead through a lens of self-interest. Do you ever
use the classes or functions you write? If so, you’re your own client, so
making things easier for clients in general also makes them easier for
you.
When discussing class or function templates and the classes or functions generated from them, I reserve the right to be sloppy about the
difference between the templates and their instantiations. For example, if Array is a class template taking a type parameter T, I may refer
to a particular instantiation of the template as an Array, even though
8B
Introduction
Array<T> is really the name of the class. Similarly, if swap is a function template taking a type parameter T, I may refer to an instantiation
as swap instead of swap<T>. In cases where this kind of shorthand
might be unclear, 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 for improvement. If you find an error
of any kind — technical, grammatical, typographical, whatever —
please tell me about 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, I welcome those, too.
I continue to collect guidelines for effective programming in C++. If you
have ideas for new guidelines, I’d be delighted if you’d share them with
me. Send your guidelines, your comments, your criticisms, and your
bug reports to:
Scott Meyers
c/o Editor-in-Chief, Corporate and Professional Publishing
Addison-Wesley Publishing Company
1 Jacob Way
Reading, MA 01867
U. S. A.
Alternatively, you may send electronic mail to mec++@aristeia.com.
I maintain a list of changes to this book since its first printing, including bug-fixes, clarifications, and technical updates. This list, along
with other book-related information, is available from Addison-Wesley
at World Wide Web URL http://www.awl.com/cp/mec++.html. It is
also available via anonymous FTP from ftp.awl.com in the directory
cp/mec++. If you would like a copy of the list of changes to this book,
but you lack access to the Internet, please send a request to one of the
addresses above, and I will see that the list is sent to you.
If you’d like to be notified when I make changes to this book, consider
joining my mailing list. For details, consult http://www.aristeia.com/
MailingList/index.html.
Enough preliminaries. On with the show!
Basics
Ah, the basics. Pointers, references, casts, arrays, constructors — you
can’t get much more basic than that. All but the simplest C++ programs use most of these features, and many programs use them all.
Basics
In spite of our familiarity with these parts of the language, sometimes
they can still surprise us. This is especially 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 Cstyle casts they replace. It examines the C notion of arrays and the C++
notion of polymorphism, and it describes why mixing the two is an idea
whose time will never come. Finally, it considers the pros and cons of
default constructors and suggests 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 software that expresses your design intentions clearly and correctly.
Item 1:
Distinguish between pointers and references.
Pointers and references look different enough (pointers use the “*” and
“->” operators, references use “.”), but they 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?
Pointers versus References
First, recognize that there is no such thing as a null reference. A reference must always refer to some object. As a result, if you have a variable whose purpose is to refer to another object, but it is possible that
there might not be an object to refer to, you should make the variable
10B
Item 1
a pointer, because then you can set it to null. On the other hand, if the
variable must always refer to an object, i.e., if your design does not
allow for the possibility that the variable 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 they like), and people who write
this kind of code should be shunned until they agree to cease and desist. If you have to worry about things like this in your software, you’re
probably best off avoiding references entirely. Either that or finding a
better class of programmers to work with. We’ll henceforth ignore the
possibility that a reference can 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 than to 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;
}
}
Pointers versus References
11B
Another important difference between pointers and references is that
pointers may be reassigned to refer to different 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
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 to refer to (in which case
you can set the pointer to null) or whenever you need to be able to refer
to different things at different times (in which case you can change
where the pointer points). You should use a reference whenever you
know there will always be an object to refer to and you also know that
once you’re referring to that 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 certain operators. The most common
example is operator[]. This operator typically needs to return something that can be 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 want operator[] 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 and when you’ll never want to refer to anything
else. They’re also appropriate when implementing operators whose
syntactic requirements make the use of pointers undesirable. In all
other cases, stick with pointers.
12B
Item 2:
Item 2
Prefer C++-style casts.
Consider the lowly cast. Nearly as much a programming pariah as the
goto, it nonetheless endures, because when worse comes to worst and
push comes to shove, casts can be necessary. Casts are especially necessary when worse comes to worst and push comes to shove.
New Casting Operators
Still, C-style casts are not all they might be. For one thing, they’re
rather crude beasts, letting you cast pretty much any type to pretty
much any other type. It would be nice to be able to specify more precisely the purpose of each cast. There is a great difference, for example,
between a cast that changes a pointer-to-const-object into a pointerto-non-const-object (i.e., a cast that changes only the constness of an
object) and a cast that changes a pointer-to-base-class-object into a
pointer-to-derived-class-object (i.e., a cast that completely changes an
object’s type). Traditional C-style casts make no such distinctions.
(This is hardly a surprise. C-style casts were designed 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 of parentheses and an
identifier, and parentheses and identifiers are used everywhere in C++.
This makes it tough to 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 from non-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 what you 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 a floating 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;
New Casting Operators
13B
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 same kind of restrictions. For
example, you can’t cast a struct into an int or a double into a
pointer using static_cast any 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 or volatileness of
an expression. By using a const_cast, you emphasize (to both humans and compilers) that the only thing you want to change through
the cast is the constness or volatileness of something. This meaning is enforced by compilers. If you try to employ const_cast for anything other than modifying the constness or volatileness of an
expression, your cast will be rejected. Here are some examples:
class Widget { ... };
class SpecialWidget: public Widget { ... };
void update(SpecialWidget *psw);
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
// hierarchy
By far the most common use of const_cast is to cast away the con-
stness of an object.
14B
Item 2
The second specialized type of cast, dynamic_cast, is used to perform
safe casts down or across an inheritance hierarchy. That is, you use
dynamic_cast to cast pointers or references to base class objects into
pointers or references to derived or sibling base class objects in such a
way that you can determine whether the casts succeeded.† Failed casts
are indicated by a null pointer (when casting pointers) or an exception
(when casting references):
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 types lacking virtual functions
(see also Item 24), nor can they cast away constness:
int firstNumber, secondNumber;
...
double result = dynamic_cast<double>(firstNumber)/secondNumber;
// error! int has no virtual functions
const SpecialWidget sw;
...
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. To cast 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 conversions whose result is nearly always implementation-defined. As a result, reinterpret_casts are
rarely portable.
† A second, unrelated use of dynamic_cast is to find the beginning of the memory occupied by an object. We explore that capability in Item 27.
New Casting Operators
15B
The most common use of reinterpret_cast is to cast between function pointer types. For example, suppose you have 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 into funcPtrArray:
int doSomething();
You can’t do what you want without a cast, because doSomething has
the wrong type for funcPtrArray. The functions 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 the same way), and in some cases
such casts yield incorrect results (see Item 31), so you should avoid
casting function 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 of upgrading your code when your
compilers support the new casts.
16B
Item 3
There is no easy way to emulate the behavior of a dynamic_cast, but
many libraries provide functions to perform safe inheritance-based
casts for you. If you lack such functions and you must perform this
type of cast, you can fall back on C-style casts for those, too, but then
you forego the ability to tell if the casts fail. Needless to say, 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 per for ming 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 in the knowledge that Cstyle casts continue to be valid. However, what the new casts lack in
beauty they make up for in precision of meaning and easy recognizability. Programs that use the new casts are easier to parse (both for
humans and for tools), and they allow compilers to diagnose casting
errors that would otherwise go undetected. These are powerful arguments for abandoning C-style casts, and there may also be a third:
perhaps making casts ugly and hard to type is a good thing.
Item 3:
Never treat arrays polymorphically.
One of the most important features of inheritance is that you can manipulate derived class objects through pointers 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 base class pointers and
references. This is no feature at all, because it almost never works the
way you want it to.
Arrays and Polymorphism
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 template syntax just makes things
harder to read. For this discussion, we’ll assume BST and BalancedBST objects contain only ints.
Consider a function to print out the contents of each BST in an array
of BSTs:
Arrays and Polymorphism
17B
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 must generate 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 location pointed to by array is the memory location pointed to by
array+i? The distance between them is i*sizeof(an object in the
array), because there are i objects between array[0] and array[i].
In order for compilers to emit code 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 the array 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 to printBSTArray, your compilers are
probably wrong. In that case, they’d assume each object in the array is
the size of a BST, but each object would actually be the size of a BalancedBST. Derived classes usually have more data members than their
base classes, so derived class objects are usually larger than base
class objects. We thus expect a BalancedBST object to be larger than a
18B
Item 3
BST object. If it is, the pointer arithmetic generated for printBSTArray
will be wrong for arrays of BalancedBST objects, and there’s no telling
what will happen when printBSTArray 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 base class 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 =
new BalancedBST[50];
// create a BalancedBST
// 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 each element 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 class pointer 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 pointer arithmetic, 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 concrete class (like BalancedBST) in-
Default Constructors
19B
herit from another concrete class (such as BST). As Item 33 explains,
designing your software so that concrete classes never inherit from one
another has many benefits. I encourage you to turn to Item 33 and
read all about them.
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 can get something for nothing.
Constructors initialize objects, so default constructors initialize objects without any information from the place where the object is being
created. Sometimes this makes perfect sense. Objects that act like
numbers, for example, may reasonably be initialized to zero or to undefined values. Objects that act like pointers (see 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.
Default Constructors
Not all objects fall into this category. For many objects, there is no reasonable way to perform a complete initialization in the absence of outside information. For example, an object representing an entry in an
address book makes no sense unless the name of the thing being entered is provided. In some companies, all equipment must be tagged
with a corporate ID number, and creating an object to model a piece of
equipment in such companies 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 default constructors and classes in
which information was required for object construction would not.
Alas, ours is not the best of all possible worlds, so we must take additional concerns into account. In particular, if a class lacks a default
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 mandatory constructor argument:
class EquipmentPiece {
public:
EquipmentPiece(int IDNumber);
...
};
Because EquipmentPiece lacks a default constructor, its use may be
problematic in three contexts. The first is the creation of arrays. There
20B
Item 4
is, in general, no way to specify constructor arguments for objects in
arrays, so it is not usually possible to create arrays of EquipmentPiece objects:
EquipmentPiece bestPieces[10];
EquipmentPiece *bestPieces =
new EquipmentPiece[10];
// error! No way to call
// EquipmentPiece ctors
// error! same problem
There are three ways to get around this restriction. A solution for nonheap arrays is to provide the necessary arguments at the point where
the array is defined:
int ID1, ID2, ID3, ..., ID10;
// variables to hold
// equipment ID numbers
...
EquipmentPiece bestPieces[] = {
EquipmentPiece(ID1),
EquipmentPiece(ID2),
EquipmentPiece(ID3),
...,
EquipmentPiece(ID10)
};
// fine, ctor arguments
// are provided
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
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 to by 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:
Default Constructors
21B
// 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 (as well as the array-of-pointers idea) allows you to create arrays of objects when a class lacks a default constructor; it doesn’t show you how to bypass required
constructor arguments. There is no way to do that. If there 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 (which will make maintenance
more difficult), is that you must manually call destructors on the objects in the array when you want them to go out of existence, then you
must manually deallocate the raw memory by calling operator 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 behave unpredictably. That’s because the result
of deleting a pointer that didn’t come from the new operator is undefined:
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 and destructors, see Item 8.
22B
Item 4
The second problem with classes lacking default constructors is that
they are ineligible for use with many template-based container classes.
That’s because it’s a common requirement for such templates that the
type used to instantiate the template provide a default constructor.
This requirement almost always grows out of the fact that inside the
template, an array of the template parameter type is being created. For
example, a template for an Array 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, the standard vector template (which
generates classes that act like extensible arrays) has no requirement
that its type parameter have a default constructor. Unfortunately,
many templates are designed in a manner that is anything but careful.
That being the case, classes without default constructors will be incompatible with many templates. As C++ programmers learn more
about template design, this problem should recede in significance.
How long it will take for that to happen, however, is anyone’s guess.
The final consideration in the to-provide-a-default-constructor-or-notto-provide-a-default-constructor dilemma has to do with virtual base
classes. Virtual base classes lacking default constructors are a pain to
work with. That’s because the arguments for virtual base class constructors must be provided by the most derived class of the object
being constructed. As a result, a virtual base class lacking a default
constructor requires that all classes derived from that class — no matter how far removed — must know about, understand the meaning of,
and provide for the virtual base class’s constructors’ arguments. Authors of derived classes neither expect nor appreciate this requirement.
Because of the restrictions imposed on classes lacking default constructors, some people believe all classes should have them, even if a
Default Constructors
23B
default constructor doesn’t have enough information to fully initialize
objects of that class. 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 no longer any guarantee that
the fields of an EquipmentPiece object have been meaningfully initialized. Assuming it makes no sense to have an EquipmentPiece without
an ID field, most member functions must check to see if the ID 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 they call a function that terminates the program. When that happens, it’s difficult to argue that the overall quality of the software 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 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 and libraries bigger.
They also have to pay for the code that handles the cases where the
tests fail. All those costs are avoided if a class’s constructors ensure
that all fields of an object are correctly initialized. Often default constructors 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 such classes, you can expect that the objects they generate are
fully initialized and are efficiently implemented.
Operators
Overloadable operators — you gotta love ’em! They allow you to give
your types the same syntax as C++’s built-in types, yet they let you put
a measure of power into the functions behind the operators that’s unheard of for the built-ins. Of course, the fact that you can make symbols like “+” and “==” do anything you want also means 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 the incomprehensible.
Operators
Regrettably, it is easy to make the descent. Single-argument constructors and implicit type conversion operators are particularly troublesome, because they can be invoked without there being any source
code showing the calls. This can lead to program behavior that is difficult to understand. A different problem arises when you overload operators like && and ||, because the shift from built-in operator to userdefined function yields a subtle change in semantics that’s easy to
overlook. Finally, many operators are related to one another in standard ways, 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 a pro.
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 a short to a function that expects a double and still have the call
succeed. The more frightening conversions in C — those that may lose
Conversion Functions
Conversion Functions
25B
information — are also present in C++, including conversion of int to
short and double to (of all things) char.
You can’t do anything about such conversions, because they’re hardcoded into the language. When you add your own types, however, you
have more control, because you can choose whether to provide the
functions compilers are allowed to use for implicit type conversions.
Two kinds of functions allow compilers to perform such conversions:
single-argument constructors and implicit type conversion operators. A
single-argument constructor is a constructor that may be called with
only one argument. Such a constructor may declare a single parameter
or it may declare multiple parameters, with each parameter after the
first having a default value. Here are two examples:
class Name {
public:
Name(const string& s);
// for names of things
// converts string to
// Name
...
};
class Rational {
public:
Rational(int numerator = 0,
int denominator = 1);
...
// for rational numbers
// converts int to
// Rational
};
An implicit type conversion operator is simply a member function with
a strange-looking name: the word operator 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 Rational objects to be implicitly
converted to doubles (which might be useful for mixed-mode arithmetic involving Rational 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
26B
Item 5
Perhaps all this is review. That’s fine, because what I really want to explain is why you usually don’t want to provide type conversion functions of any ilk.
The fundamental problem is that such functions often end up being
called when you neither want nor expect them to 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 you have a class for rational numbers
similar to the one above, and you’d like to print Rational objects as if
they were a built-in type. That is, you’d like to be able to do this:
Rational r(1, 2);
cout << r;
// should print "1/2"
Further suppose you forgot to write an operator<< for Rational objects. You would probably expect that the attempt to print r would fail,
because there is no appropriate operator<< to call. You would be mistaken. Your compilers, faced with a call to a function called operator<< that takes a Rational, would find that no such function
existed, but they would then try to find an acceptable sequence of implicit type conversions they could apply to make the call succeed. The
rules defining which sequences of conversions are acceptable are complicated, but in this case your compilers would discover they could
make the call succeed by implicitly converting r to a double by calling
Rational::operator double. The result of the code above would be to
print r as a floating point number, not as a rational number. This is
hardly a disaster, but it demonstrates the disadvantage 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 function called 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
Conversion Functions
cout << r.asDouble();
27B
// fine, prints r as a
// double
In most cases, the inconvenience of having to call conversion functions
explicitly is more than compensated for by 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 the committee working on the
standard C++ library (see Item 35), for example, are among the most
experienced in the business, and perhaps that’s why the string type
they added to the library contains no implicit conversion from a
string object to a C-style char*. Instead, there’s an explicit member
function, c_str, that performs that conversion. Coincidence? I think
not.
Implicit conversions via single-argument constructors are more difficult to eliminate. Furthermore, the problems these 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 and lower 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
second constructor, 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, and that can lead to endless anguish.
For example, consider a template specialization for comparing Ar-
ray<int> objects and some code that uses such objects:
bool operator==( const Array<int>& lhs,
const Array<int>& rhs);
28B
Item 5
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 the subscripting syntax when
we typed a. Certainly we expect this to elicit all manner of unpleasant
commentary from our compilers, but they will complain not at all.
That’s because they see a call to operator== with arguments of type
Array<int> (for a) and int (for b[i]), and though there is no operator== function taking those types, 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 we never 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 size b[i] (whose contents are presumably undefined). Not only is this unlikely to behave in a satisfactory manner, it is also tremendously inefficient, because each time
through the loop we both create and destroy a temporary Array<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
offer single-argument constructors to your clients. At the same time,
you may wish to prevent compilers from calling such constructors indiscriminately. Fortunately, there is a way to have it all. In fact, there
are two ways: the easy way 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 was introduced specifically to address
the problem of implicit type conversion, and its use is about as
straightforward as can be. Constructors can be declared explicit,
and if they are, compilers are prohibited from invoking them for pur-
Conversion Functions
29B
poses 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
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 the statement 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 space between 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 the use of single-argument
constructors as implicit type conversion functions. Such methods are
obvious only after you’ve seen them.
I mentioned earlier that there are complicated rules governing which
sequences of implicit type conversions are legitimate and which are
not. One of those rules is that no sequence of conversions is allowed to
contain more than one user-defined conversion (i.e., a call to a singleargument constructor or an implicit type conversion operator). By con-
30B
Item 5
structing your classes properly, you can take advantage of this rule so
that the object constructions 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 be used as a constructor argument,
but you must at the same time prevent the implicit conversion of an integer into a temporary Array object. You accomplish this by first creating a new class, ArraySize. Objects of this type have only one
purpose: they represent the size of an array that’s about to be created.
You then modify Array’s single-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 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 such constructor. Compilers realize
they can convert the int argument into a temporary ArraySize object, and that ArraySize object is just what the Array<int> constructor needs, so compilers perform the conversion with their usual 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 good unless the type conver-
Increment and Decrement Operators
31B
sions 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== for Array<int> objects, but there
is no single-argument constructor taking an int argument. Furthermore, compilers cannot 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 ArraySize and one from ArraySize to Array<int>. Such a conversion sequence is verboten, so compilers must
issue an error 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 specific instance of a more general technique. Classes like ArraySize are often called proxy classes,
because each object of such a class stands for (is a proxy for) some
other object. An ArraySize object is really just a stand-in for the integer used to specify the size of the Array being created. Proxy objects
can give you control over aspects of your software’s behavior — in this
case implicit type conversions — that is otherwise beyond your grasp,
so it’s well worth your while to learn how to use them. How, you might
wonder, can you acquire such learning? One way 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 to perform implicit type conversions usually leads to more harm than good, so don’t provide conversion functions unless you’re sure you want them.
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 distinguish between prefix and postfix invocations of the ++ and -- operators. Programmers being programmers,
Increment and Decrement Operators
32B
Item 6
they kvetched about this omission, and C++ was extended to allow
overloading both forms of increment and decrement operators.
There was a syntactic problem, however, and that was that overloaded
functions are differentiated on the basis of the parameter types they
take, but neither prefix nor postfix increment or decrement takes an
argument. To surmount this linguistic pothole, it was decreed that
postfix forms take an int argument, and compilers silently pass 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;
i++;
// calls i.operator++();
// calls i.operator++(0);
--i;
i--;
// calls i.operator--();
// 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, is this: the prefix and postfix forms
of these operators return different types. In particular, prefix forms return a reference, postfix forms return a const object. We’ll focus here
on the prefix and postfix ++ operators, but the story for the -- operators
is analogous.
From your days as a C programmer, you may recall that the prefix
form of the increment operator is sometimes called “increment and
fetch,” while the postfix form is often known as “fetch and increment.”
These two phrases are important to remember, because they all but act
as formal specifications for how prefix and postfix increment should be
implemented:
// prefix form: increment and fetch
UPInt& UPInt::operator++()
{
*this += 1;
// increment
return *this;
// fetch
}
Increment and Decrement Operators
33B
// postfix form: fetch and increment
const UPInt UPInt::operator++(int)
{
const UPInt oldValue = *this;
// fetch
++(*this);
// increment
return oldValue;
}
// return what was
// fetched
Note how the postfix operator makes no use of its parameter. This is
typical. The only purpose of the parameter is to distinguish prefix from
postfix function invocation. Many compilers issue warnings if you fail
to use named parameters in the body of the function to which they apply, and this can be annoying. To avoid such warnings, a common
strategy is to omit names for parameters you don’t plan to use; that’s
what’s been done above.
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 the first invocation.
There are two reasons to abhor this. First, it’s inconsistent with the behavior of the built-in types. A good rule to follow when designing
classes is when in doubt, do as the ints do, and the ints most certainly do not allow double 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 object returned 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.
34B
Item 6
C++ prohibits it for ints, but you must prohibit it yourself for classes
you write. The easiest way to do this is to make 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 from postfix operator++ — can’t call it.† If you’ve ever
wondered if it makes sense to have functions return const objects,
now you know: sometimes it does, and postfix increment and decrement are examples.
If you’re the kind who worries about efficiency, you probably broke into
a sweat when you first saw the postfix increment function. That function has to create a temporary object for its return value (see Item 19),
and the implementation above also creates an explicit temporary object (oldValue) that has to be constructed and destructed. The prefix
increment function has no such temporaries. This leads to the possibly
startling conclusion that, for efficiency reasons alone, clients of UPInt
should prefer prefix increment to postfix increment unless they really
need the behavior of postfix increment. Let us be explicit about this.
When dealing with user-defined types, 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 return values, they do the same thing:
they increment a value. That is, they’re supposed to do the same thing.
How can you be sure the behavior of postfix increment is consistent
with that of prefix increment? What guarantee do you have that their
implementations won’t diverge over time, possibly as a result of different programmers maintaining and enhancing them? Unless you’ve followed the design principle embodied by the code above, you have no
such guarantee. That principle is that postfix increment and decrement should be implemented in terms of their prefix counterparts. You
then need only maintain the prefix versions, because the postfix versions will automatically behave in a consistent fashion.
As you can see, mastering prefix and postfix increment and decrement
is easy. Once you know their proper return types and that the postfix
operators should be implemented in terms of the prefix operators,
there’s very little more to learn.
† Alas, it is not uncommon for compilers to fail to enforce this restriction. Before you
write programs that rely on it, test your compilers to make sure they behave correctly.
Overloading &&, ||, and ,
Item 7:
35B
Never overload &&, ||, or ,.
Like C, C++ employs short-circuit evaluation of boolean expressions.
This means that once the truth or falsehood of an expression has been
determined, evaluation of the expression ceases, even if some parts of
the expression haven’t yet been examined. For example, in this case,
Overloading &&, ||, and ,
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 what they expect. Furthermore,
they write programs whose correct behavior depends on short-circuit
evaluation. In the 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 null pointer is undefined.
C++ allows you to customize the behavior of the && and || operators
for user-defined types. You do it by overloading the functions operator&& and operator||, and you can do this at the global scope or on
a per-class basis. If you decide to take advantage of this opportunity,
however, you must be aware that you are changing the rules 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
36B
Item 7
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 two crucial ways. First, when a
function call is made, all parameters must be evaluated, so when calling the functions 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, so there is no way of knowing whether
expression1 or expression2 will be evaluated first. This stands in
stark contrast 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 and have 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 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 a for loop. The following
function, for example, is based on one in the second edition of Kernighan’s and Ritchie’s classic The C Programming Language (PrenticeHall, 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 comma operator here, because only an
expression is valid in the final part of a for loop; separate statements
to change the values of i and j would be illegal.
Overloading &&, ||, and ,
37B
Just as there are rules in C++ defining how && and || behave for builtin types, there are rules defining how the comma operator behaves for
such types. An expression containing a comma is evaluated by first
evaluating the part of the expression to the left of the comma, then
evaluating the expression to the right of the comma; the result of 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 this behavior if you’re going to take
it upon yourself to write your own comma operator. Unfortunately, you
can’t perform the requisite mimicry.
If you write operator, as a non-member function, you’ll never be able
to guarantee that the left-hand expression is evaluated before the
right-hand expression, because both expressions will be passed as arguments in a function call (to operator,). But you have no control
over the order in which a function’s arguments are evaluated. So the
non-member approach is definitely out.
That leaves only the possibility of writing operator, as a member
function. Even here you can’t rely on the left-hand operand to the
comma operator being evaluated first, because compilers are not constrained to do things that way. Hence, you can’t overload the comma
operator and also guarantee it will behave the way it’s supposed 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 comma operator, what can’t you overload? As it turns out, there are limits. You can’t overload the following
operators:
.
new
static_cast
.*
::
delete
sizeof
dynamic_cast const_cast
?:
typeid
reinterpret_cast
You can overload these:
operator new
operator new[]
+
*
!
=
<
^=
&=
|=
<=
>=
&&
()
[]
operator delete
operator delete[]
/
%
^
>
+=
-=
<<
>>
>>=
||
++
--
&
*=
<<=
,
|
/=
==
->*
~
%=
!=
->
38B
Item 8
(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 of operator overloading is to make
programs easier to read, write, and understand, not to dazzle others
with your knowledge that comma is an operator. If you don’t have a
good reason for overloading an operator, don’t overload 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.
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. Case in point: the difference between the new operator and operator new.
The Different Meanings of new and delete
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’t change its meaning: it always
does the same thing. What it does is twofold. First, it allocates enough
memory to hold 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 always does 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 perform the requisite memory allocation, and you can rewrite or overload that function to change its behavior. The name of 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, you can write a version of operator new that initializes the memory to some value before returning
a pointer to it, but this is not commonly done.) The size_t parameter
specifies how much memory to allocate. You can overload operator
The Different Meanings of new and delete
39B
new by adding additional parameters, but the first parameter must always be of type size_t.
You’ll probably never want to call operator new directly, but on the off
chance you do, you’ll call it just like any other 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. All operator new understands is
memory allocation. It is the job of the new operator to take the raw
memory that operator 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:
void *memory =
operator new(sizeof(string));
// get raw memory
// for a string
// object
call string::string("Memory Management")
on *memory;
// initialize the
// object in the
// memory
string *ps =
static_cast<string*>(memory);
// make ps point to
// the new object
Notice that the second step above involves calling a constructor, something you, a mere programmer, are prohibited from doing. Your compilers are unconstrained by mortal limits, however, and they can do
whatever they like. That’s why you must use the new operator if you
want to conjure up a heap-based object: you can’t directly 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 object makes no sense, because
constructors initialize objects, and an object can only be initialized —
given its first value — once. But occasionally you have some raw memory that’s already been allocated, and you need to construct an object
in the memory you have. A special version of operator new called
placement new allows you to do it.
As an example of how placement new might be used, consider this:
40B
Item 8
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 objects in such applications must be placed at specific
addresses or in memory allocated by special routines. (For a different
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 thus called must, in addition to the mandatory size_t argument,
accept a void* parameter that points to the memory the object 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 of operator 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 is supposed
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.
To use placement new, all you have to do is #include <new> (or, if your
compilers don’t yet support the new-style header names, <new.h>).
If we step back from placement new for a moment, we’ll see that the relationship between the new operator and operator new, though perhaps terminologically confusing, is conceptually straightforward. If
The Different Meanings of new and delete
41B
you want to create an object on the heap, use the new operator. It both
allocates memory and calls a constructor for the object. If you only
want to allocate memory, call operator new; no constructor will be
called. If you want to customize the memory allocation that takes place
when heap objects are created, write your own version of operator
new and use the new operator; it will automatically invoke your custom
version of operator new. If you want to construct an object in memory
you’ve already got a pointer to, use placement new.
Deletion and Memory Deallocation
To avoid resource leaks, every dynamic allocation must be matched by
an equal and opposite deallocation. The function operator delete is
to the built-in delete operator as operator new is to the new operator.
When you say something 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 memory occupied 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 the new and delete operators entirely. Instead, you should call operator new to get the memory and
operator delete to return it to the system:
void *buffer =
operator new(50*sizeof(char));
// allocate enough
// memory to hold 50
// chars; call no ctors
...
operator delete(buffer);
// deallocate the memory;
// call no dtors
42B
Item 8
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 that memory. That’s because the delete operator calls operator delete to deallocate the
memory, but the memory containing the object wasn’t allocated by operator new in the first place; placement new just returned the pointer
that was passed to it. Who knows where that pointer came from? Instead, you should undo the effect of the constructor 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);
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.
Arrays
So far so good, but there’s farther to go. Everything we’ve examined so
far concerns itself with only one object at 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 behaves slightly differently from the
case of single-object creation. For one thing, memory is no longer allocated by operator new. Instead, it’s allocated by the array-allocation
equivalent, a function called operator new[] (often referred to as “ar-
The Different Meanings of new and delete
43B
ray new.”) Like operator new, operator new[] can be overloaded.
This allows you to seize control of memory allocation for arrays in the
same way you can control memory allocation for single objects.
(operator new[] is a relatively recent addition to C++, so your compilers may not support it yet. If they don’t, the global version of operator
new will be used to allocate memory for every array, regardless of the
type of objects in the array. Customizing array-memory allocation
under such compilers is daunting, because it requires that you rewrite
the global operator new. This is not a task to be undertaken lightly.
By default, the global operator new handles all dynamic memory allocation in a program, so any change in its behavior has a dramatic and
pervasive effect. Furthermore, there is only one global operator new
with the “normal” signature (i.e., taking the single size_t parameter),
so if you decide to claim it as your own, you instantly render your software incompatible with any library that makes the same decision. (See
also Item 27.) As a result of these considerations, custom memory
management for arrays is not usually a reasonable design decision for
compilers lacking support for operator new[].)
The second way in which the new operator behaves differently for arrays than for objects is in the number of constructor calls it makes. For
arrays, a constructor must be called for each object in the array:
string *ps =
new string[10];
//
//
//
//
call operator
memory for 10
then call the
ctor for each
new[] to allocate
string objects,
default string
array element
Similarly, when the delete operator is used on an array, it calls a destructor for each array element and then calls 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 are some restrictions on how
they can be overloaded, however; consult a good C++ text for details.
(For ideas on good C++ texts, see the recommendations beginning on
page 285B.)
So there you have it. The new and delete operators are built-in and
beyond your control, but the memory allocation and deallocation functions they call are not. When you think about customizing the behavior
of the new and delete operators, remember that you can’t really do it.
You can modify how they do what they do, but what they do is fixed by
the language.
Exceptions
The addition of exceptions to C++ changes things. Profoundly. Radically. Possibly uncomfortably. The use of raw, unadorned pointers, for
example, becomes risky. Opportunities for resource leaks increase in
number. It becomes more difficult to write constructors and destructors that behave the way we want them to. Special care must be taken
to prevent program execution from abruptly halting. Executables and
libraries typically increase in size and decrease in speed.
Exceptions
And these are just the things we know. There is much the C++ community does not know about writing programs using exceptions, including, for the most part, how to do it correctly. There is as yet no
agreement on a body of techniques that, when applied routinely, leads
to software that behaves predictably and reliably when exceptions are
thrown. (For insight into some of the issues involved, see the article by
Tom Cargill I refer to on page 287.)
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 a program behaving well in the presence of exceptions when
it was not designed for exceptions are about the same as the chances
of a program behaving well in the presence of multiple threads of control when it was not designed for multi-threaded execution: about zero.
That being the case, why use exceptions? Error codes have sufficed for
C programmers ever since C was invented, 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 or returning an error code, there
is no way to guarantee the function’s caller will check the variable or
examine the code. As a result, execution may continue long past the
point where the condition was encountered. If the function signals the
Using Destructors to Prevent Resource Leaks
45B
condition by throwing an exception, however, and that exception is not
caught, program execution immediately ceases.
This is behavior that C programmers can approach only by using
setjmp and longjmp. But longjmp exhibits a serious 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 for true exceptions. If you need a way of signaling exceptional conditions that cannot
be ignored, and if you must ensure 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 an incomplete guide to writing exception-safe software. Nevertheless, they introduce important considerations for anyone using exceptions in C++. By heeding the guidance in
the material below, you’ll improve the correctness, robustness, and efficiency of the software you write, and you’ll sidestep many problems
that commonly arise when working with exceptions.
Item 9:
Use destructors to prevent resource leaks.
Say good-bye to pointers. Admit it: you never really liked them that
much anyway.
Using Destructors to Prevent Resource Leaks
Okay, you don’t have to say good-bye to all pointers, but you do need
to say sayonara to pointers that are used to manipulate local resources. Suppose, for example, you’re writing software at the Shelter
for Adorable Little Animals, an organization that finds homes for puppies and kittens. Each day the shelter creates a file containing information on the adoptions it arranged that day, and your job is to write
a program to read these files and do the appropriate processing for
each adoption.
A reasonable approach to this task is to define an abstract base class,
ALA (“Adorable Little Animal”), plus concrete derived classes for puppies and kittens. A virtual function, processAdoption, handles the
necessary species-specific processing:
ALA
Puppy
Kitten
46B
Item 9
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 Kitten object, depending on the information in the file. This is a perfect job for a virtual constructor, a kind
of function described 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
}
This function loops through the information in dataSource, processing each entry as it goes. The only mildly tricky thing is the need to remember to delete pa at the end of each iteration. This is necessary
because readALA creates a new heap object each time it’s called. Without the call to delete, the loop would contain a resource leak.
Now consider what would happen if pa->processAdoption threw an
exception. processAdoptions fails to catch exceptions, so the exception would propagate to processAdoptions’s caller. In doing so, all
statements in processAdoptions after the call to pa->processAdoption would be skipped, and that means pa would never be deleted. As
Using Destructors to Prevent Resource Leaks
47B
a result, anytime pa->processAdoption throws an exception, processAdoptions contains a resource leak.
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 duplicate cleanup code that is common
to both normal and exceptional paths of control. In this case, the call
to delete must be 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 to delete 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 for an 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 this shortcoming
of longjmp is the primary reason why C++ has support for exceptions
in the first place.) Our real concern, then, is moving the delete from
processAdoptions into a destructor for an object local to processAdoptions.
The solution is to replace the pointer pa with an object that acts like a
pointer. That way, when the pointer-like object is (automatically) destroyed, we can have its destructor call delete. Objects that act like
pointers, but do more, 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
48B
Item 9
pointer-like object that knows enough to delete 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 contains a class template called auto_ptr
that does just what we want. Each auto_ptr class takes a pointer to a
heap object in its constructor and deletes that object in its destructor.
Boiled down to these essential functions, auto_ptr looks like this:
template<class T>
class auto_ptr {
public:
auto_ptr(T *p = 0): ptr(p) {}
~auto_ptr() { delete ptr; }
private:
T *ptr;
};
// save ptr to object
// delete ptr to object
// raw ptr to object
The standard version of auto_ptr is much fancier, and this strippeddown implementation isn’t suitable for real use† (we must add at least
the copy constructor, assignment operator, and pointer-emulating
functions discussed in Item 28), but the concept behind it should be
clear: use auto_ptr objects instead of raw pointers, and you won’t
have to worry about heap objects not being deleted, not even when exceptions are thrown. (Because the auto_ptr destructor uses the single-object form of delete, auto_ptr is not suitable for use with
pointers to arrays of 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 an auto_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 normal pointers. Easy, huh?
† A complete implementation of an almost-standard auto_ptr appears on pages 291-294.
Using Destructors to Prevent Resource Leaks
49B
The idea behind auto_ptr — using an object to store a resource that
needs to be automatically released and relying on that object’s destructor to release it — applies to more than just pointer-based resources. Consider a function in 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 to acquire and release window resources. If an exception is thrown during the process of displaying
info in w, the window 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 release the resource:
// class for acquiring and releasing a window handle
class WindowHandle {
public:
WindowHandle(WINDOW_HANDLE handle): w(handle) {}
~WindowHandle() { destroyWindow(w); }
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, and there is an implicit conversion operator that can be used to turn a WindowHandle into a
WINDOW_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 raw
WINDOW_HANDLE. (See Item 5, however, for why you should generally be
leery of implicit type conversion operators.)
50B
Item 10
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† be destroyed.
By adhering to the rule that resources should be encapsulated inside
objects, you can usually avoid resource leaks in the presence of exceptions. But what happens if an exception is thrown while you’re in the
process of acquiring a resource, e.g., while you’re in the constructor of
a resource-acquiring class? What happens if an exception is thrown
during the automatic destruction of such resources? Don’t constructors and destructors call for special techniques? They do, and you can
read about them in Items 10 and 11.
Item 10: Prevent resource leaks in constructors.
Imagine you’re developing software for a multimedia address book.
Such an address book might hold, in addition to the usual textual information of a person’s name, address, and phone numbers, a picture
of the person and the sound of their voice (possibly giving the proper
pronunciation of their name).
Avoiding Resource Leaks in Constructors
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
† Well, almost always. If the exception is not caught, the program will terminate. In that
case, there is no guarantee that local objects (such as w in the example) will have their
destructors called. Some compilers call them, some do not. Both behaviors are valid.
Avoiding Resource Leaks in Constructors
class BookEntry {
public:
BookEntry(const
const
const
const
~BookEntry();
51B
// for each entry in the
// address book
string&
string&
string&
string&
name,
address = "",
imageFileName = "",
audioClipFileName = "");
// phone numbers are added via this function
void addPhoneNumber(const PhoneNumber& number);
...
private:
string theName;
string theAddress;
list<PhoneNumber> thePhones;
Image *theImage;
AudioClip *theAudioClip;
};
//
//
//
//
//
person’s name
their address
their phone numbers
their image
an audio clip from them
Each BookEntry must have name data, so you require that as a constructor argument (see Item 4), but the other fields — the person’s address and the names of files containing image and audio data — are
optional. Note the use of the list class to hold the person’s phone
numbers. This is one of several container classes that are part of the
standard C++ library (see 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);
}
if (audioClipFileName != "") {
theAudioClip = new AudioClip(audioClipFileName);
}
}
BookEntry::~BookEntry()
{
delete theImage;
delete theAudioClip;
}
52B
Item 10
The constructor initializes the pointers theImage and theAudioClip
to null, then makes them point to real objects if the corresponding arguments are non-empty strings. The destructor deletes both pointers,
thus ensuring that a BookEntry 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 an AudioClip 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 be propagated 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 (thus transferring 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’s destructor will never be
called. Never.
C++ destroys only fully constructed objects, and an object isn’t fully
constructed until its constructor has run to completion. 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 to take matters into your own
hands by allocating b on the heap and then calling delete if an exception is thrown,
Avoiding Resource Leaks in Constructors
53B
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
// 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 is made to pb unless the
new operation succeeds. If BookEntry’s constructor throws an exception, pb will be the null pointer, so deleting it in the catch block does
nothing except make you feel better about yourself. Using the smart
pointer class auto_ptr<BookEntry> (see Item 9) instead of a raw
BookEntry* won’t do you any good either, 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 not simply to make your life
more difficult. It’s because it would, in many cases, be a nonsensical
thing — possibly a harmful thing — to do. If a destructor were invoked
on an object that wasn’t fully constructed, how would the destructor
know what to do? The only way it could know would be if bits had been
added to each object indicating 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 object larger, too. C++ avoids this
overhead, but the price you pay is that partially constructed objects
aren’t automatically destroyed.
Because C++ won’t clean up after objects that throw exceptions during
construction, you must design your constructors 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 be incorporated into the
BookEntry constructor like this:
54B
Item 10
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;
delete theAudioClip;
// perform necessary
// cleanup actions
throw;
// propagate the exception
}
}
There is no need to worry about BookEntry’s non-pointer data members. Data members are automatically initialized before a class’s constructor is called, so if a BookEntry constructor body begins
executing, the object’s theName, theAddress, and thePhones data
members have already been fully constructed. As fully constructed objects, these data members will be automatically destroyed even if an
exception arises in the BookEntry constructor†. Of course, if these objects’ constructors call functions that might throw exceptions, those
constructors have to worry about catching the exceptions and performing any necessary 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’s destructor. Code duplication here is no more tolerable than it is anywhere else, so the best
way to structure things is to move the common code into a private
helper function and have both the constructor and the destructor call
it:
class BookEntry {
public:
...
private:
...
void cleanup();
};
† Provided, again, that the exception is caught.
// as before
// common cleanup statements
Avoiding Resource Leaks in Constructors
55B
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 slightly differently so that theImage and
theAudioClip are constant pointers:
class BookEntry {
public:
...
private:
...
Image * const theImage;
AudioClip * const theAudioClip;
};
// as above
// pointers are now
// const
Such pointers must be initialized via the member initialization lists of
BookEntry’s constructors, because there is no other way to give const
pointers a value. A common temptation is to initialize theImage and
theAudioClip like this,
56B
Item 10
// an implementation that may leak resources if an
// exception is thrown
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 of theAudioClip, the object
pointed to by theImage is never destroyed. Furthermore, we can’t
solve the problem by adding try and catch blocks to the constructor,
because try and catch are statements, and member initialization lists
allow only expressions. (That’s why we had to use the ?: syntax instead of the if-then-else syntax in the initialization of theImage and
theAudioClip.)
Nevertheless, the only way to perform cleanup chores before exceptions propagate out of a constructor is to catch those exceptions, so if
we can’t put try and catch in a member initialization list, we’ll have
to put them somewhere else. One possibility is inside private member
functions that return pointers with which theImage and 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))
{}
Avoiding Resource Leaks in Constructors
57B
// 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);
}
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 that code that conceptually belongs in a constructor is now dispersed across several functions, and
that’s a maintenance headache.
A better solution is to adopt the advice of Item 9 and treat the objects
pointed to by theImage and theAudioClip as resources to be managed by local objects. This solution takes advantage of the facts that
both theImage and theAudioClip are pointers to dynamically allocated objects and that those objects should be deleted when the pointers 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_ptr equivalents:
class BookEntry {
public:
...
// as above
private:
...
const auto_ptr<Image> theImage;
// these are now
const auto_ptr<AudioClip> theAudioClip; // auto_ptr objects
};
58B
Item 11
Doing this makes BookEntry’s constructor leak-safe in the presence of
exceptions, and it lets us initialize theImage 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 fully constructed 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 the BookEntry
object containing them is. Hence there’s no need to manually delete
what they point to. That simplifies 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 fortify your constructors against
resource leaks in the presence of exceptions, you eliminate the need to
manually deallocate resources in destructors, and you allow const
member pointers to be handled in the same graceful fashion as nonconst pointers.
Dealing with the possibility of exceptions during construction can be
tricky, but auto_ptr (and auto_ptr-like classes) can eliminate most
of the drudgery. Their use leaves behind code that’s not only easy to
understand, it’s robust in the face of exceptions, too.
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 by the exception-handling mechanism during the stackunwinding part of exception propagation.
Exceptions and Destructors
Exceptions and Destructors
59B
That being the case, an exception may or may not be active when a destructor is invoked. Regrettably, there is no way to distinguish between
these conditions from inside a destructor.† As a result, you must write
your destructors under the conservative assumption that an exception
is active, because if control leaves a destructor due to an exception
while another exception is active, C++ calls the terminate function.
That function does just what its name suggests: it terminates execution of your program. Furthermore, it terminates it immediately; not
even local objects are destroyed.
As an example, consider a Session class for monitoring on-line computer sessions, i.e., things that happen from the time you log in
through the time you log out. Each Session object notes the date and
time of its creation and destruction:
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 would not be caught in Session’s
destructor, so it would be propagated to the caller of that destructor.
But if the destructor was itself being called because some other exception had been thrown, the terminate function would automatically 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’s destruction can’t be logged, it
might even be a major inconvenience, but is it really so horrific a pros† 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.
60B
Item 11
pect that the program can’t continue running? If not, you’ll have to
prevent the exception thrown by logDestruction from propagating
out of Session’s destructor. The only way to do that is by using try
and catch blocks. A naive attempt might look like this,
Session::~Session()
{
try {
logDestruction(this);
}
catch (...) {
cerr << "Unable to log destruction of Session object "
<< "at address "
<< this
<< ".\n";
}
}
but this is probably no safer than our original code. If one of the calls
to operator<< in the catch block results in an 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 forget about 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 exceptions thrown from logDestruction
from propagating beyond Session’s destructor. That’s all it needs to
do. We can now rest 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 an exception is thrown from a destructor and is not caught there, that destructor won’t run to completion. (It
will stop at the point where the exception is thrown.) If the destructor
doesn’t run to completion, it won’t do everything it’s supposed to do.
For example, consider a modified version of the Session class where
the creation of a session starts a database transaction and the termination of a session ends that transaction:
Throwing Exceptions Compared to Calling Functions
Session::Session()
{
logCreation(this);
startTransaction();
61B
// to keep things simple,
// this ctor handles no
// exceptions
// 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 be ended. In this case, we might
be able to reorder the function calls in Session’s destructor to eliminate the problem, 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, it helps ensure that destructors always accomplish everything they are supposed to accomplish. Each argument is
convincing in its own right, but together, the case is ironclad.
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:
Throwing Exceptions Compared to Calling Functions
class Widget { ... };
// some class; it makes no
// difference what it is
void
void
void
void
void
//
//
//
//
all these functions
take parameters of
type Widget, Widget&, or
Widget*
//
//
//
//
all these catch clauses
catch exceptions of
type Widget, Widget&, or
Widget*
f1(Widget w);
f2(Widget& w);
f3(const Widget& w);
f4(Widget *pw);
f5(const Widget *pw);
catch
catch
catch
catch
catch
(Widget w) ...
(Widget& w) ...
(const Widget& w) ...
(Widget *pw) ...
(const Widget *pw) ...
You might therefore assume that passing an exception from a throw
site to a catch clause is basically the same as passing an argument
62B
Item 12
from a function call site to the function’s parameter. There are some
similarities, to be sure, but there are significant differences, too.
Let us begin with a similarity. You can pass both function parameters
and exceptions by value, by reference, or by pointer. What happens
when you pass parameters and exceptions, however, is quite different.
This difference grows out of the fact that when you call a function, control eventually returns to the call site (unless the function fails 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 per formed. Instead, the reference w inside operator>> is bound to localWidget, and anything done to w is really done to localWidget. It’s
a different story when localWidget is thrown as an exception. Regardless of whether the exception is caught by value or by reference (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
of scope once control leaves passAndThrowWidget, and when localWidget goes out of scope, its destructor will be called. If localWidget
itself were passed to a catch clause, the clause would receive a destructed Widget, an ex-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 copied.
This copying occurs even if the object being thrown is not in danger of
being destroyed. For example, if passAndThrowWidget declares localWidget to be static,
void passAndThrowWidget()
{
static Widget localWidget;
// this is now static; it
// will exist until the
// end of the program
Throwing Exceptions Compared to Calling Functions
63B
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 the exception is caught by reference,
it is not possible for the catch block to modify localWidget; it can
only modify a copy of localWidget. This mandatory copying of exception objects† helps explain another difference between parameter 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. For example, 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 is Widget, not SpecialWidget. That rw actually refers to a SpecialWidget is of no concern
to your compilers; all they care about is rw’s static type. This behavior
may not be what you want, but it’s consistent with all other cases in
which C++ copies objects. Copying is always based on an object’s static
type (but see Item 25 for a technique 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 catch block. Consider these two
catch blocks, which at first glance appear to do the same thing:
† Compiler writers are actually allowed a slight bit of leeway regarding the “mandatory”
nature of the copying; it can be eliminated under certain circumstances. Similar leeway provides the foundation for the return value optimization (see Item 20).
64B
Item 12
catch (Widget& w)
{
...
throw;
}
catch (Widget& w)
{
...
throw w;
}
// catch Widget exceptions
// handle the exception
// rethrow the exception so it
// continues to propagate
// catch Widget exceptions
// handle the exception
// propagate a copy of the
// caught exception
The only difference between these blocks is that the first one rethrows
the current exception, while the second one throws a new copy of the
current exception. Setting aside the performance cost of the additional
copy operation, is there a difference between these approaches?
There is. The first block rethrows the current exception, regardless of
its type. In particular, if the exception originally thrown was of type
SpecialWidget, the first block would propagate a SpecialWidget exception, even though w’s static type is Widget. This is because no copy
is made when the exception is rethrown. The second catch block
throws a new exception, which will always be of type Widget, because
that’s w’s static type. In general, 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 exception being 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 compilers the right to optimize it out of existence. I wouldn’t expect your compilers to work that hard, however.
Exceptions are supposed to be rare, so it makes little sense for compiler vendors to pour a lot of energy into their optimization.)
Let us examine the three kinds of catch clauses that could catch the
Widget exception thrown by passAndThrowWidget. 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 thrown object (which, as explained
Throwing Exceptions Compared to Calling Functions
65B
above, is always a temporary) may be caught by simple reference; it
need not be caught by reference-to-const. Passing a temporary object
to a non-const reference parameter is not allowed for function 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 know that when we pass a function argument by value, we make a copy of the passed object, and we
store 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 all exceptions generate, the second to
copy that temporary into w. Similarly, when we catch an exception by
reference,
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 expect to construct (and later destruct) one more copy of
the thrown object than if we passed the same object to a 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 local object, because that local object will be destroyed
when the exception leaves the local object’s scope. The catch clause
would then be initialized with a pointer to an object that had already
been destroyed. This is the behavior the 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 which argument passing differs
from exception propagation. A second difference lies in what constitutes a type match between 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);
66B
Item 12
There is nothing surprising here. The language allows implicit conversion from int to double, so in the call to sqrt, i is silently converted
to a double, and the result of sqrt corresponds to that double. (See
Item 5 for a fuller discussion of implicit type conversions.) In general,
such conversions are not applied when matching exceptions to catch
clauses. In this code,
void f(int value)
{
try {
if (someFunction()) {
throw value;
}
...
}
catch (double d) {
...
}
// if someFunction() returns
// true, throw an int
// 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. That clause catches only exceptions that are exactly of type double; no type conversions are applied.
As a result, if the int exception is to be caught, it will have to be by
some other (dynamically enclosing) catch clause taking an int 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-based conversions. A catch
clause for base class exceptions is allowed to handle exceptions of (publicly) derived class types, too. For example, consider the diagnostics portion of the hierarchy of exceptions defined by the standard C++ library:
exception
logic_error
domain_error
runtime_error
length_error
invalid_argument
range_error
out_of_range
overflow_error
underflow_error
Throwing Exceptions Compared to Calling Functions
67B
A catch clause for runtime_errors can catch exceptions of type
range_error, underflow_error, and overflow_error, too, and a
catch clause accepting an object of the root class exception can
catch any kind of exception derived from this hierarchy.
This inheritance-based exception-conversion rule applies to values,
references, and pointers in the usual fashion (though Item 13 explains
why catching values or pointers is generally a bad idea):
catch (runtime_error) ...
// can catch errors of type
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 const void* 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 always tried in the order of their appearance. Hence, it is possible for an exception of a (publicly) derived
class type to be handled by a catch clause for one of its base class
types — even when a catch clause for the derived class is associated
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, the function invoked is the
one in the class closest to the dynamic type of the object invoking the
function. You might say 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 an error, because such code used to be illegal
68B
Item 13
in C++), but your best course of action is preemptive: never put a
catch clause for a base class before a catch clause for a derived class.
The code above, for example, should be reordered 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 a virtual 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 to functions. Finally, catch clauses are examined in
the order in which they appear in the source code, and the first one
that can succeed is selected for execution. When an object is used to
invoke a virtual function, the function selected 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 source code.
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. You have three choices, just as
when specifying how parameters should be passed to functions: by
pointer, by value, or by reference.
Catching Exceptions
Let us consider first catch by pointer. In theory, this should be the
least inefficient way to implement the invariably slow process of moving an exception from throw site to catch clause (see Item 15). That’s
because throw by pointer is the only way of moving exception information without copying an object (see Item 12). For example:
class exception { ... };
void someFunction()
{
static exception ex;
...
// from the standard C++
// library exception
// hierarchy (see Item 12)
// exception object
Catching Exceptions
69B
throw &ex;
// throw a pointer to ex
...
}
void doSomething()
{
try {
someFunction();
}
catch (exception *ex) {
...
}
}
// may throw an exception*
// 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 define exception objects in a way
that guarantees the objects exist after control leaves the functions
throwing pointers to 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;
//
//
//
//
...
throw &ex;
...
local exception object;
will be destroyed when
this function’s scope is
exited
// 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 that no longer exists.
An alternative is to throw a pointer to a new heap object:
void someFunction()
{
...
throw new exception;
...
}
//
//
//
//
//
throw a pointer to a new heapbased 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 clauses confront a nasty question: should
they delete the pointer they receive? If the exception object was allocated on the heap, they must, otherwise they suffer a resource leak. If
70B
Item 13
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 the address of an exception
on the heap. Catch by pointer thus gives rise to the Hamlet conundrum: to delete or not to 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 four standard 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 typeid is applied to
a dereferenced null pointer), and bad_exception (available for unexpected exceptions; see Item 14) — are all objects, 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 gives rise to the specter of the slicing problem, whereby derived
class exception objects caught as base class exceptions have their derivedness “sliced off.” Such “sliced” objects are base class objects: they
lack derived class data members, and when virtual functions are called
on them, they resolve to virtual functions of the base class. (Exactly
the same thing happens when an object is passed to a function by
value.) For example, consider an application employing an exception
class hierarchy that extends the standard one:
class exception {
public:
// as above, this is a
// standard exception class
virtual const char * what() const 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:
public exception { ... };
// also from the standard
// C++ exception hierarchy
class Validation_error:
// this is a class added by
public runtime_error {
// a client
public:
virtual const char * what() const throw();
// this is a redefinition
...
// of the function declared
};
// in class exception above
Catching Exceptions
void someFunction()
{
...
71B
// may throw a validation
// exception
if (a validation test fails) {
throw Validation_error();
}
...
}
void doSomething()
{
try {
someFunction();
}
catch (exception ex) {
cerr << ex.what();
...
}
// may throw a validation
// exception
// catches all exceptions
// in or derived from
// the standard hierarchy
// calls exception::what(),
// never
// Validation_error::what()
}
The version of what that is called is that of the base class, even though
t h e t h r o w n e x c e p t i o n i s o f t y p e Validation_error a n d
Validation_error redefines that virtual function. This kind of slicing
behavior is almost never 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 the standard exception types. Unlike catch-by-value, there is
no slicing problem, and exception objects are copied only 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();
}
...
}
72B
Item 14
void doSomething()
{
try {
someFunction();
}
catch (exception& ex) {
cerr << ex.what();
...
// no change here
//
//
//
//
//
here we catch by reference
instead of by value
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. This tiny modification makes
a big difference, however, because virtual functions in the catch block
now work as we expect: functions in Validation_error are invoked if
they redefine those in exception. Of course, if there is no need to
modify the exception object in the handler, you’d catch not just by reference, but by reference to const.
What a happy confluence of events! If you catch by reference, you sidestep questions about object deletion that leave you damned if you do
and damned if you don’t; you avoid slicing exception objects; you retain the ability to catch standard exceptions; and you limit the number
of times exception objects need to be copied. So what are you waiting
for? Catch exceptions by reference!
Item 14: Use exception specifications judiciously.
There’s no denying it: exception specifications have appeal. They make
code easier to understand, because they explicitly state what exceptions a function may throw. But they’re more than just fancy comments. Compilers are sometimes able to detect inconsistent exception
specifications during compilation. Furthermore, if a function throws
an exception not listed in its exception specification, that fault is detected at runtime, and the special function unexpected is automatically invoked. Both as a documentation aid and as an enforcement
mechanism for constraints on exception usage, then, exception specifications seem attractive.
Exception Specifications
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 violated exception specification is to halt. Local variables in active stack frames are not destroyed, because abort shuts down program execution without performing such cleanup. A violated exception
specification is therefore a cataclysmic thing, something that should
almost never happen.
Exception Specifications
73B
Unfortunately, it’s easy to write functions that make this terrible thing
occur. Compilers only partially check exception usage for consistency
with exception specifications. What they do not check for — what the
language standard prohibits them from rejecting (though they may
issue a warning) — is a call to a function that might violate 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 kind of exception:
extern void f1();
// might throw anything
Now consider a function f2 that claims, through its exception specification, it will throw only exceptions of type int:
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’s exception 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 code lacking such specifications.
Because your compilers are content to let you call functions whose exception specifications are inconsistent with those of the routine containing the calls, and because such calls might result in your
program’s execution being terminated, it’s important to write your
software in such a way that these kinds of inconsistencies are minimized. A good way to start is to avoid putting exception specifications
on templates that take type arguments. 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 true if the objects have the
same address, otherwise it returns false.
74B
Item 14
This template contains an exception specification stating that the
functions generated from the template will throw no exceptions. But
that’s not necessarily true, because it’s possible that operator& (the
address-of operator) has been overloaded for some types. If it has, operator& may throw an exception when called from 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 the exceptions thrown by a
template’s type parameters. We can almost never provide a meaningful
exception specification for a template, because templates almost invariably use their type parameter in some way. The conclusion? Templates and exception specifications don’t mix.
A second technique you can use to avoid calls to unexpected is to omit
exception specifications on functions making calls to functions that
themselves lack exception specifications. This is simple common
sense, but there is 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;
void *data;
};
// function to call when
// callback is made
// 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);
}
Exception Specifications
75B
Here the call to func in makeCallBack runs the risk of a violated exception specification, because there is no way of knowing what exceptions func might throw.
This problem can be eliminated by tightening the exception specification in the CallBackPtr typedef:†
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);
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 the language, so don’t be surprised
if your compilers don’t yet support it. If they don’t, it’s up to you to ensure you don’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. Of these exceptions, the most common is bad_alloc, which is thrown by operator new and operator
new[] when a memory allocation fails (see Item 8). If you use the new
operator (again, see Item 8) in any function, you must be prepared 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 is easy. That is, sometimes it’s
easier to cope with unexpected exceptions directly than to prevent
them from arising in the first place. If, for example, you’re writing soft† 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 a typedef.” I don’t know why. If you need a portable
solution, you must — it hurts me to write this — make CallBackPtr a macro, sigh.
76B
Item 14
ware that uses exception specifications rigorously, but you’re forced to
call functions in libraries that don’t use exception specifications, it’s
impractical to prevent unexpected 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 replace unexpected exceptions with exceptions of a different type. For example, suppose you’d like all unexpected exceptions 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 unexpected exception is then replaced
by a new exception of type UnexpectedException. Provided the exception specification that was violated includes UnexpectedException, exception propagation will then continue as if the exception
specification had always been satisfied. (If the exception specification
does not include UnexpectedException, 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 unexpected function’s replacement rethrows the current exception, that exception will be replaced by a new
exception of the standard type bad_exception. Here’s how you’d arrange for that to happen:
void convertUnexpected()
{
throw;
}
//
//
//
//
function to call if
an unexpected exception
is thrown; just rethrow
the current exception
set_unexpected(convertUnexpected);
// install convertUnexpected
// as the unexpected
// replacement
Exception Specifications
77B
If you do this and you include bad_exception (or its base class, the
standard class exception) in all your exception specifications, you’ll
never have to worry about your program halting if an unexpected exception is encountered. Instead, any wayward exception will be replaced by a bad_exception, and that exception will be propagated in
the stead of the original one.
By now you understand that exception specifications can be a lot of
trouble. Compilers perform only partial checks for their consistent usage, they’re problematic in templates, they’re easy to violate inadvertently, and, by default, they lead to abrupt program termination when
they’re violated. Exception specifications have another drawback, too,
and that’s that they result in unexpected being invoked even when a
higher-level caller is prepared to cope with the exception that’s arisen.
For example, consider this code, which is taken almost verbatim from
Item 11:
class Session {
public:
~Session();
...
// for modeling online
// sessions
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 it explicitly catches any exceptions that might be thrown by logDestruction. However, logDestruction comes with an exception specification asserting that it
throws no exceptions. Now, suppose some function called by logDestruction throws an exception that logDestruction fails to catch.
This isn’t supposed to happen, but as we’ve seen, it isn’t difficult to
write code that leads to the violation of exception specifications. When
this unanticipated exception propagates through logDestruction,
unexpected will be called, and, by default, that will result in termination of the program. This is correct behavior, to be sure, but is it the
behavior the author of Session’s destructor wanted? That author took
pains to handle all possible exceptions, so it seems almost unfair to
halt the program without giving Session’s destructor’s catch block a
chance to work. If logDestruction had no exception specification,
78B
Item 15
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 the kinds of exceptions a function
is expected to throw, and for situations in which violating an exception
specification is so dire as to justify immediate program termination,
they offer that behavior by default. At the same 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 know how to.
That being the case, exception specifications are a tool to be applied judiciously. Before adding them to your functions, consider whether the
behavior they impart to your software is really the behavior you want.
Item 15: Understand the costs of exception handling.
To handle exceptions at runtime, programs must do a fair amount of
bookkeeping. At each point during execution, they must be able to
identify the objects that require destruction if an exception is thrown;
they must make note of each entry to and exit from a try block; and
for each try block, they must keep track of the associated catch
clauses and the types of exceptions those clauses can handle. This
bookkeeping is not free. Nor are the runtime comparisons necessary to
ensure that exception specifications are satisfied. Nor is the work expended 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.
The Costs of Exception Handling
Let us begin with the things you pay for even if you never use any exception-handling features. You pay for the space used by the data
structures needed to keep track of which objects are fully constructed
(see Item 10), and you 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 their counterparts 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-handling features, because programs are typically composed
of multiple independently generated object files, and just because one
object file doesn’t do anything with exceptions doesn’t mean others
The Costs of Exception Handling
79B
don’t. Furthermore, even if none of 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 be possible to provide correct
exception-handling behavior at runtime.
That’s the theory. In practice, most vendors who support exception
handling allow you to control whether support 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 as well
compile without exception-handling support and save yourself the size
and speed penalty you’d otherwise probably be assessed for a feature
you’re not using. As time goes on and libraries employing exceptions
become more 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 decided not to use exceptions. It may also be an attractive optimization for libraries that eschew exceptions, provided
they can guarantee that exceptions thrown from client code never
propagate into the library. This is a difficult guarantee to make, as it
precludes client redefinitions of library-declared virtual functions; it
also rules out client-defined callback functions.
A second cost of exception-handling arises from try blocks, and you
pay it whenever you use one, i.e., whenever you 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 by 5-10% and your
runtime to go up by a similar amount if you use try blocks. This assumes no exceptions are thrown; 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 exception specification generally incurs
about the same cost as a try block. Excuse me? You say you thought
exception specifications were just specifications, you didn’t think they
generated code? Well, now you have something new to 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 a concern, 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’s overall
performance. Nevertheless, I know you’re curious about just how big a
hit you’ll take if you throw an exception, and the answer is it’s proba-
80B
Item 15
bly a big one. Compared to a normal function return, returning from a
function by throwing an exception may be as much as three orders of
magnitude slower. That’s quite a hit. But you’ll take it only if you throw
an exception, and that should be almost never. If, however, you’ve been
thinking of using exceptions to indicate relatively common conditions
like the completion of a data structure traversal or the 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 a program’s size will generally grow by about 5-10%, its speed will
decrease by a similar amount, and it may run orders of magnitude
slower if lots of exceptions are thrown? The answer is frightening: a little rumor and a handful of benchmarks (see Item 23). The fact is that
most people — including most compiler vendors — have little experience with exceptions, so though we know there are costs associated
with them, it is difficult to predict 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 very seriously. Whatever the
cost of exception handling, you don’t want to pay any more than you
have to. To minimize your exception-related costs, compile without
support for exceptions when that is feasible; limit your use of try
blocks and exception specifications to those locations where you honestly need them; and throw exceptions only under conditions that are
truly exceptional. If you still have performance problems, profile your
software (see Item 16) to determine if exception support is a contributing factor. If it is, consider switching to different compilers, ones that
provide more efficient implementations of C++’s exception-handling
features.
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 to drool?
Efficiency
In fact, efficiency is no laughing matter. Programs that are too big or
too slow fail to find acceptance, no matter how 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 a mere 16,
that chewing up 100 megabytes of disk space is better than swallowing
only 50. Furthermore, though some programs take longer and use
more memory because they perform more ambitious computations,
too many programs can blame their sorry pace and bloated footprint
on nothing more than bad design and slipshod programming.
Writing efficient programs in C++ starts with the recognition that C++
may well have nothing to do with any performance problems you’ve
been having. If you want to write an efficient C++ program, you must
first be able to write an efficient program. Too many developers overlook this simple truth. Yes, loops may be unrolled by hand and multiplications may be replaced by shift operations, but such micro-tuning
leads nowhere if the higher-level algorithms you employ are inherently
inefficient. Do you use quadratic algorithms when linear ones are
available? Do you compute the same value over and over? Do you
squander opportunities to reduce the average cost of expensive operations? If so, you can hardly be surprised if your programs are described like second-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
82B
Item 16
implementation medium for these ideas, because its strong support for
encapsulation makes it possible to replace inefficient class implementations with better algorithms and data structures that support the
same interface.
The second focus is on C++ itself. High-performance algorithms and
data structures are great, but sloppy implementation practices can reduce their effectiveness considerably. The most insidious mistake is
both simple to make and hard to recognize: creating and destroying too
many objects. Superfluous object constructions and destructions act
like a hemorrhage on your program’s performance, with precious
clock-ticks bleeding away each time an unnecessary object is created
and destroyed. This problem is so pervasive in C++ programs, I devote
four separate items to describing where these objects come from and
how you can eliminate them without compromising the correctness of
your code.
Programs don’t get big and slow only by creating too many objects.
Other potholes on the road to high performance include library selection and implementations of language features. In the items that follow, I address these issues, too.
After reading the material in this chapter, you’ll be familiar with several principles that can improve the performance of virtually any program you write, you’ll know exactly how to prevent unnecessary
objects from creeping into your software, and you’ll have a keener
awareness of how your compilers behave when generating executables.
It’s been said that forewarned is forearmed. If so, think of the information that follows as preparation for battle.
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: 80 percent of the runtime is
spent in approximately 20 percent of the code; 80 percent of the memory is used by some 20 percent of the code; 80 percent of the disk accesses are performed for about 20 percent of the code; 80 percent of
the maintenance effort is devoted to around 20 percent of the code.
The rule has been repeatedly verified through examinations of countless machines, operating systems, and applications. The 80-20 rule is
more than just a catchy phrase; it’s a guideline about system performance that has both wide applicability and a solid empirical basis.
The 80-20 Rule
When considering the 80-20 rule, it’s important not to get too hung up
on numbers. Some people favor the more stringent 90-10 rule, and
there’s experimental evidence to back that, too. Whatever the precise
The 80-20 Rule
83B
numbers, the fundamental point is this: the overall performance of
your software is almost always determined by a small part of its constituent code.
As a programmer striving to maximize your software’s performance,
the 80-20 rule both simplifies and complicates your life. On one hand,
the 80-20 rule implies that most of the time you can produce code
whose performance is, frankly, rather mediocre, because 80 percent of
the time its efficiency doesn’t affect the overall performance of the system you’re working on. That may not do much for your ego, but it
should reduce your stress level a little. On the other hand, the rule implies that if your software has a performance problem, you’ve got a
tough job ahead of you, because you not only have to locate the small
pockets of code that are causing the problem, you have to find ways to
increase their performance dramatically. Of these tasks, the more
troublesome is generally locating the bottlenecks. There are two fundamentally different ways to approach the matter: the way most 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 be traced to network delays, improperly tuned memory allocators, compilers that don’t optimize aggressively enough, or some
bonehead manager’s refusal to permit assembly language for crucial
inner loops. Such assessments are generally delivered with a condescending sneer, and usually both the sneerers and their prognostications are flat-out wrong.
Most programmers have lousy intuition about the performance characteristics of their programs, because program performance characteristics tend to be highly unintuitive. As a result, untold effort is
poured into improving 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’s all for naught if the program is I/Obound. Souped-up I/O libraries (see Item 23) may be substituted for
the ones shipped 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? The 80-20 rule means that improving random parts of the program is unlikely to help very much.
The fact that programs tend to have unintuitive performance characteristics means that trying to guess the causes of performance bottlenecks is unlikely to be much better than just improving random parts
of your program. What, then, will work?
84B
Item 16
What will work is to empirically identify the 20 percent of your program
that is causing you heartache, and the way to identify that horrid 20
percent is to use a program profiler. Not just any profiler will do, however. You want 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 you can focus on those places where a significant
improvement in local efficiency will also yield a significant improvement in overall efficiency.
Profilers that tell you how many times each statement is executed or
how many times each function is called are of limited utility. From a
performance point of view, you do not care how many times a statement is executed or a function is called. It is, after all, rather rare to
encounter a user of a program or a client of a library who complains
that too many statements are being executed or too many functions
are being called. If your software is fast enough, nobody cares how
many statements are executed, and if it’s too slow, nobody cares how
few. All they 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 what your software is doing. If,
for example, you think you’re creating about a hundred objects of a
particular type, it would 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’s behavior you can’t directly measure. If you
have no direct way of measuring dynamic memory usage, for example,
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 program while it’s processing unrepresentative input data, you’re in no position to complain if the profiler leads
you to fine-tune parts of your software — the parts making up some 80
percent of it — that have no bearing on its usual performance. Remember that a profiler can only tell you how a program behaved on a particular run (or set of runs), so if you profile a program using input data
that is unrepresentative, you’re going to get back a profile that is
equally unrepresentative. That, in turn, is likely to lead to you to optimize your software’s behavior for uncommon 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 data sets as possible. Moreover,
Lazy Evaluation
85B
you must ensure that each data set is representative of how the software is used by its clients (or at least its most important clients). It is
usually easy to acquire representative data sets, because many clients
are happy to let you use their data when profiling. After all, you’ll then
be tuning your software to meet their needs, and that can only be good
for both of you.
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 if you don’t need to do something,
why would you put code in your program to do it in the first place? And
if you do need to do something, how can you possibly avoid executing
the code that does it?
Lazy Evaluation
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 to confirm that
your room had, in fact, been cleaned. Then you’d sprint to your room
and get to work as fast as you possibly could. If you were lucky, your
parents would never check, and you’d avoid all the work cleaning your
room normally entails.
It turns out that the same delay tactics that work for a five year old
work for a C++ programmer. In Computer Science, however, we dignify
such procrastination with the name lazy evaluation. When you employ
lazy evaluation, you write your classes in such a way that they defer
computations until the results of those computations are required. If
the results are never required, the computations are never performed,
and neither your 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 evaluation is 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)
86B
Item 17
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 it would have to make a copy of s1’s value to give to
s2, and that would typically entail allocating heap memory via the new
operator (see Item 8) and calling strcpy to copy the data in s1 into the
memory allocated by s2. This is eager evaluation: making a copy of s1
and putting it into s2 just because the String copy constructor was
called. At this point, however, there has been no real need for s2 to
have a copy of the value, because s2 hasn’t been 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. All we have to do is a little bookkeeping so we know who’s sharing what, and in return we save the
cost of a call to new and the expense of copying anything. The fact that
s1 and s2 are sharing a data structure is transparent to clients, and it
certainly makes no difference in statements like the following, because
they only read values, they don’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 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 a copy of s2’s value and
makes that value private to s2 before modifying it. Inside convertToUpperCase, we can be 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 is never
modified, we never have to make a private copy of its value. It can continue to share a value as long as it exists. If we’re lucky, s2 will never
be modified, in which case we’ll never have to expend the effort to give
it its own value.
The details on making this kind of value sharing work (including all the
code) are provided in Item 29, but the idea is lazy evaluation: don’t
bother to make a copy of something until you really need one. Instead,
be lazy — use someone else’s copy as long as you can get away with it.
In some application areas, you can often get away with it forever.
Lazy Evaluation
87B
Distinguishing Reads from Writes
Pursuing the example of reference-counting strings a bit further, we
come upon a second way in which lazy evaluation can help us. Consider this code:
String s = "Homer’s Iliad";
// Assume s is a
// reference-counted string
...
cout << s[3];
s[3] = ’x’;
// call operator[] to read s[3]
// 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 be able to distinguish the read
call from the write, because reading a reference-counted string is
cheap, but writing to 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 things inside operator[] (depending on
whether it’s being called to perform a read or a write). How can we determine whether operator[] has been called in a read or a write context? The brutal truth is that we can’t. By using lazy evaluation and
proxy classes as described in Item 30, however, we can defer the decision on whether to take read 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 many constituent fields. Such objects must persist across program runs, so they’re stored in a database. Each object has a unique object identifier that can be used to
retrieve the object from the database:
class LargeObject {
public:
LargeObject(ObjectID id);
const string& field1() const;
int field2() const;
double field3() const;
const string& field4() const;
const string& field5() const;
...
// large persistent objects
// restore object from disk
// value of field 1
// value of field 2
// ...
};
Now consider the cost of restoring a LargeObject from disk:
88B
Item 17
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 database operation, especially if the data
must be retrieved from a remote database and pushed across a network. In some cases, 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 is needed 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
mutable
mutable
mutable
...
};
string *field1Value;
int *field2Value;
double *field3Value;
string *field4Value;
// see below for a
// discussion of "mutable"
Lazy Evaluation
89B
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;
}
Each field in the object is represented as a pointer to the necessary
data, and the LargeObject constructor initializes 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 on that data.
When implementing lazy fetching, you must confront the problem that
null pointers may need to be initialized to point 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 find a 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. 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. If not, you’ll need to find another way to convince your compilers to let you modify data members
inside const member functions. One workable strategy is the “fake
this” approach, whereby you create a pointer -to-non-const that
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;
...
private:
string *field1Value;
...
};
// unchanged
// not declared mutable
// so that older
// compilers will accept it
90B
Item 17
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 =
the appropriate data
from the database;
}
// this assignment is OK,
// because what fakeThis
// 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’t support 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 all those pointers to null,
then test each one before use. Fortunately, such drudgery can be automated through the use of smart pointers, which you can read about
in Item 28. If you use smart pointers inside LargeObject, you’ll also
find you no longer need to declare the pointers mutable. Alas, it’s only
a temporary respite, because you’ll wind up needing mutable once you
sit down to implement the smart pointer classes. Think of it as conservation of inconvenience.
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);
Matrix<int> m2(1000, 1000);
// a 1000 by 1000 matrix
// ditto
...
Matrix<int> m3 = m1 + m2;
// add m1 and m2
Lazy Evaluation
91B
The usual implementation of operator+ would use eager evaluation;
in this case it would compute and return the sum of m1 and m2. That’s
a fair amount of computation (1,000,000 additions), and of course
there’s the cost of allocating 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 structure inside m3 that indicates that m3’s value is the sum of m1 and m2. Such a data structure
might consist of nothing more 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 less memory, 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), and in its place we can start
remembering that m3 is the product of m4 and m1. Needless to say, we
don’t perform the multiplication. Why bother? We’re lazy, remember?
This example looks contrived, because no good programmer would
write a program that computed the sum of two matrices and failed to
use it, but it’s not as contrived as it seems. No good programmer would
deliberately compute a value that’s not needed, but during maintenance, it’s not uncommon for a programmer to modify the paths
through a program in such a way that a formerly useful computation
becomes unnecessary. The likelihood of that happening is reduced by
defining objects immediately prior to use, but it’s still a problem that
occurs from time to time.
Nevertheless, if that were the only time lazy evaluation paid off, it
would hardly be worth the trouble. A more common scenario is that we
need only part of a computation. For example, suppose we use m3 as
follows after initializing 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’s not be overly ambitious, either.
There’s no reason we have to compute any more than the fourth row of
m3; the remainder of m3 can remain uncomputed until it’s actually
needed. With luck, it never will be.
92B
Item 17
How likely are we to be lucky? Experience in the domain of matrix
computations suggests the odds are in our favor. In fact, lazy evaluation lies behind the wonder that is APL. APL was developed in the
1960s for interactive use by people who needed to perform matrixbased calculations. Running on computers that had less computational horsepower than the chips now found in high-end microwave
ovens, APL was seemingly able to add, multiply, and even divide large
matrices instantly! Its trick was lazy evaluation. The trick was usually
effective, because APL users typically added, multiplied, or divided matrices not because they needed the entire resulting matrix, but only because they needed a small part of it. APL employed lazy evaluation to
defer its computations until it knew exactly what part of a result matrix was needed, then it computed only that part. In practice, this allowed users to perform computationally intensive tasks interactively in
an environment where the underlying machine was hopelessly inadequate for an implementation employing eager evaluation. Machines are
faster today, but data sets are bigger and users less patient, so many
contemporary matrix libraries continue to 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 is dependent 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 old value of m1 and make m3 dependent on that, but we
have to do something to guarantee that m3 has the value it’s supposed
to have after m1 has been the target of an assignment. Other functions
that might modify a matrix must be 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 significant amounts of time and
space during program runs, and in many applications, that’s a payoff
that easily justifies the significant effort lazy evaluation requires.
Amortizing the Cost of Expected Computations
93B
Summary
These four examples show that lazy evaluation can be useful in a variety of domains: to avoid unnecessary copying of objects, to distinguish
reads from writes using operator[], to avoid unnecessary reads from
databases, and to avoid unnecessary numerical computations. Nevertheless, it’s not always a good idea. Just as procrastinating on your
clean-up chores won’t save you any work if your parents always check
up on you, lazy evaluation won’t save your program any work if all your
computations are necessary. Indeed, if all your computations are essential, lazy evaluation may slow you down and increase your use of
memory, because, in addition to having to do all the computations you
were hoping to avoid, you’ll also have to manipulate the fancy data
structures needed to make lazy evaluation possible in the first place.
Lazy evaluation is only useful when there’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 programming language, 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 eager evaluation, however, and C++ is mainstream. Yet C++ is particularly suitable as a vehicle for user-implemented lazy evaluation, because its support for
encapsulation makes it possible to add lazy evaluation to a class without clients 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 offer no hints about whether eager
or lazy evaluation is used by the classes. That means it’s possible to
implement a class 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 one based on lazy evaluation. The only change your
clients will see (after recompilation or relinking) is improved performance. That’s the kind of software enhancement clients love, one that
can make you downright proud to be lazy.
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 how laziness can improve the efficiency of your programs. In this item, I adopt a different stance. Here,
laziness has no place. I now encourage you to improve the performance of your software by having it do more than it’s asked to do. The
Amortizing the Cost of Expected Computations
94B
Item 18
philosophy of this item might be called over-eager evaluation: doing
things before you’re asked to do them.
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 the collection, there are three
ways in which these functions can be implemented. Using eager evaluation, we’d examine 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 the appropriate value whenever the functions’ return values were actually used. Using over-eager evaluation, we’d keep
track of the running minimum, maximum, and average values of the
collection, so when min, max, or avg was 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’s minimum, maximum, and average values over all the calls to those functions, and the amortized
cost per call would 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 can lower the average cost per request by designing your data structures to handle the requests
especially efficiently.
One of the simplest ways to do this is by caching values that have already been computed and are likely to be needed again. For example,
suppose you’re writing a program to provide information about employees, and one of the pieces of information you expect to be requested frequently is an employee’s cubicle number. Further suppose
that employee information is stored in a database, but, for most applications, an employee’s cubicle number is irrelevant, so the database is
not optimized to find it. To avoid having your specialized application
unduly stress the database with repeated lookups of employee cubicle
numbers, you could write a findCubicleNumber function that caches
the cubicle numbers it looks up. Subsequent requests for cubicle
Amortizing the Cost of Expected Computations
95B
numbers that have already been retrieved can then be satisfied by consulting the cache instead of querying the database.
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);
// "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
replace comparatively expensive database queries with comparatively
inexpensive lookups in an in-memory data structure. 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 cubicle number.
96B
Item 18
(One detail of the code requires explanation. The final statement returns (*it).second instead of the more conventional it->second.
Why? The answer has to do with the conventions followed by the STL.
In brief, the iterator it is an object, not a pointer, so there is no guarantee that “->” can be applied to it.† The STL does require that “.” and
“*” be valid for iterators, however, so (*it).second, though syntactically clumsy, is guaranteed to work.)
Caching is one way to amortize the cost of anticipated computations.
Prefetching is another. You can think of prefetching as the computational equivalent of a discount for buying in bulk. Disk controllers, for
example, read entire blocks or sectors of data when they read from
disk, even if a program asks for only a small amount of data. 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 disk caches, 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? No problem. Prefetching can yield dividends for even one as high-level as you. Imagine, for example, you’d
like to implement a template for dynamic arrays, i.e., arrays that start
with a size of one and automatically extend themselves so that all nonnegative indices are valid:
template<class T>
class DynArray { ... };
// template for dynamic
// 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;
// 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 to allocate only as much additional memory as needed, something like this:
† In July 1995, the ISO/ANSI committee standardizing C++ added a requirement that
most 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.
Amortizing the Cost of Expected Computations
97B
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 invoke operator new (see Item 8), and
calls to operator new (and operator delete) are usually expensive.
That’s because they typically result in calls to the underlying operating
system, and system calls are generally slower than are in-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 to accommodate index i, the locality
of reference principle suggests we’ll probably have to increase it in the
future to 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, we could
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 again at 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:
98B
Item 19
DynArray<double> a;
// only a[0] is valid
a[22] = 3.5;
//
//
//
//
a[32] = 0;
// a’s logical size is
// changed to allow a[32],
// but new isn’t called
new is called to expand
a’s storage through
index 44; a’s logical
size becomes 23
If a needs to be extended again, that extension, too, will be inexpensive, provided the new maximum index is no greater than 44.
There is a common theme running through this Item, and that’s that
greater speed can often be purchased at a cost of increased memory
usage. Keeping track of running minima, maxima, and averages requires extra space, but it saves time. Caching results necessitates
greater memory usage but reduces the time needed to regenerate the
results once they’ve been cached. Prefetching demands a place to put
the things that are prefetched, but it reduces the time needed to access
those things. The story is as old as Computer Science: you can often
trade space for time. (Not always, however. Using larger objects means
fewer fit on a virtual memory or cache page. In rare cases, 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-eager strategies like caching and
prefetching — is not contradictory to the advice on lazy evaluation I
put forth in Item 17. Lazy evaluation is a technique for improving the
efficiency of programs when you must support operations whose results are not always needed. Over-eager evaluation is a technique for
improving the efficiency of programs when you must support operations whose results are almost always needed or whose results are
often needed more than once. Both are more difficult to implement
than run-of-the-mill eager evaluation, but both can yield significant
performance improvements in programs whose behavioral characteristics justify the extra programming effort.
Item 19: Understand the origin of temporary objects.
When programmers speak amongst themselves, they often refer to
variables that are needed for only a short while as “temporaries.” For
example, in this swap routine,
Temporary Objects
Temporary Objects
99B
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 a non-heap object is created
but not named. Such unnamed objects usually arise in one of two situations: when implicit type conversions are applied to make function
calls succeed and when functions return objects. It’s important to understand how and why these temporary objects are created and destroyed, because the attendant costs 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 happens when the type of object passed to
a function is not the same as the type of the parameter to which it is
being bound. 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 function parameter is of type const
string&. This call can succeed only if the type mismatch can be eliminated, and your compilers will be happy to eliminate it by creating a
temporary object of type string. That temporary object is initialized
by calling the string constructor with buffer as its argument. The
str parameter of countChar is then bound to this temporary string
object. When the statement containing the call to countChar finishes
executing, the temporary object is automatically destroyed.
100B
Item 19
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 general ways to eliminate it. One is to redesign your code
so conversions like these can’t take place. That strategy is examined in
Item 5. An alternative tack is to modify your software so that the conversions are unnecessary. Item 21 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
In the character-counting example, a char array could be successfully
passed to countChar, but here, trying to call 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 modify the 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, and that programmer expected subtleBookPlug to be modified. Implicit type
conversion for references-to-non-const objects, then, would allow
temporary objects to be changed when programmers expected nontemporary objects to be modified. That’s why the language prohibits
the generation of temporaries for non-const reference parameters.
Reference-to-const parameters don’t suffer from this problem, because such parameters, by virtue of being 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. Given
a type Number, for example, operator+ for that type would be declared
like this:
const Number operator+(const Number& lhs,
const Number& rhs);
The Return Value Optimization
101B
The return value of this function is a temporary, because it has no
name: it’s just the function’s return value. You must pay to construct
and destruct this object each time you call operator+. (For an explanation of why the return value is const, see Item 6.)
As usual, you don’t want to incur this cost. For this particular function, you can avoid paying by switching to a similar 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 and destruction
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 in a
way that allows your compilers to optimize temporary objects out of existence. Of these optimizations, the most 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. More important 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 to that parameter.
Anytime you see a function returning an object, a temporary will be
created (and later destroyed). Learn to look for such constructs, and
your insight into the cost of “behind the scenes” compiler actions will
markedly improve.
Item 20: Facilitate the return value optimization.
A function that returns an object is frustrating to efficiency aficionados, because the by-value return, including the constructor and destructor calls it implies (see Item 19), cannot be eliminated. The
problem is simple: a function either has to return an object in order to
offer correct behavior or it doesn’t. If it does, there’s no way to get rid
of the object being returned. Period.
The Return Value Optimization
Consider the operator* function for rational numbers:
102B
Item 20
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 product of two arbitrary numbers. These are arbitrary numbers. How can operator* possibly avoid
creating a new object to hold their product? It can’t, so it must create
a new object and return it. C++ programmers have nevertheless expended Herculean efforts in a search for the legendary elimination of
the by-value return.
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 usually yes, 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:
The Return Value Optimization
103B
// another dangerous (and incorrect) way to avoid
// returning an object
const Rational& operator*(const Rational& lhs,
const Rational& rhs)
{
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 the local object result, but result is automatically destroyed when operator* is exited. Returning
a reference to an object 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’t fight 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 the wrong war to wage. From
an efficiency point of view, you shouldn’t care that a function returns
an object, you should only care about the cost of that object. What you
need to do is channel your efforts into finding a way to reduce the cost
of returned objects, not to eliminate the objects themselves (which we
now recognize is a futile quest). 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 cost of 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 you are. 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.
104B
Item 20
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 have gained
something. The rules for C++ allow compilers to optimize temporary
objects out of existence. As a result, 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 by operator*. They can construct the object defined by the return expression inside the memory
allotted for the object c. If your compilers do this, the total cost of temporary objects as a result of your calling operator* is zero: 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 be eliminated (see
also Item 22).† You can, however, eliminate the overhead of the call to
operator* by declaring that function inline:
// the most efficient way to write a function returning
// an object
inline const Rational operator*( const Rational& lhs,
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 know what 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 at the function’s
call site) — is both well-known and commonly implemented. It even
has a name: the return value optimization. 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.
† In July 1996, the ISO/ANSI standardization committee declared that both named and
unnamed objects may be optimized away via the return value optimization.
Overloading to Avoid Implicit Type Conversions
105B
Item 21: Overload to avoid implicit type conversions.
Here’s some code that looks nothing if not eminently reasonable:
Overloading to Avoid Implicit Type Conversions
class UPInt {
public:
UPInt();
UPInt(int value);
...
// class for unlimited
// precision integers
};
// For an explanation of why the return value is const,
// see Item 6
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 10 into UPInts (see Item 19).
It is convenient to have compilers perform these kinds of conversions,
but the temporary objects created to make the conversions work are a
cost we may not wish to bear. Just as most people want government
benefits without having to pay for them, most C++ programmers want
implicit type conversions without incurring any cost for temporaries.
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 to operator+ with a combination of UPInt and int arguments. Implicit type conversion happens to
be a means to that end, 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 int objects, all we have to do is say
so. We do it by declaring several functions, each with a different set of
parameter types:
const UPInt operator+(const UPInt& lhs,
const UPInt& rhs);
// add UPInt
// and UPInt
106B
Item 21
const UPInt operator+(const UPInt& lhs,
int rhs);
// add UPInt
// and int
const UPInt operator+(int lhs,
const UPInt& rhs);
// add int and
// UPInt
UPInt upi1, upi2;
...
UPInt upi3 = upi1 + upi2;
// fine, no temporary for
// upi1 or upi2
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 of the 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 possible combinations for operator+. Given
the three overloadings above, the only one missing is operator+ taking two int 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 take at least one argument of a
user-defined type. int isn’t a user-defined type, so we can’t overload
an operator taking only arguments of that type. (If this rule didn’t exist, programmers would be able to change the meaning of predefined
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 want to allow a string object
everywhere a char* is acceptable, and vice versa. Similarly, if you’re
using a numerical class like complex (see Item 35), you’ll want types
like int and double to be valid anywhere a numerical object is. As a
result, any function taking arguments of type string, char*, complex, etc., is a reasonable candidate for overloading 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 of overloaded functions unless you
op= Compared to Stand-Alone op
107B
have good reason to believe that it will make a noticeable improvement
in the overall efficiency of the programs that use them.
Item 22: Consider using op= instead of stand-alone op.
Most programmers expect that if they can say things like these,
op= Compared to Stand-Alone op
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 no relationship between operator+, operator=, and operator+=, so if you want all three operators
to exist and to have 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 6 for an explanation of why the return value is
// const and page 109B 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 operators need to be maintained. Furthermore, assuming the
108B
Item 22
assignment versions of the operators are in the class’s public interface,
there is never a need for the stand-alone operators to be friends of the
class.
If you don’t mind putting all stand-alone operators at global scope, you
can use templates to eliminate the need to write 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
}
...
With these templates, as long as an assignment version of an operator
is defined for some type T, the corresponding 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, after all, 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 versions must typically return a new
object, and that costs us the construction and destruction of a temporary (see Items 19 and 20). Assignment versions of operators write to
their left-hand argument, so there is no 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, you allow clients of your classes to
make the difficult trade-off between efficiency and convenience. That
is, your clients 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
result
result
result
= a;
+= b;
+= c;
+= d;
//
//
//
//
no
no
no
no
temporary
temporary
temporary
temporary
needed
needed
needed
needed
op= Compared to Stand-Alone op
109B
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 language programmers. By offering both options, you let clients develop
and debug code using the easier-to-read stand-alone operators while
still reserving the right to replace them with the more efficient assignment versions of the operators. Furthermore, by implementing the
stand-alones in terms of the assignment versions, you ensure that
when clients switch from one to the other, the semantics of the operations remain constant.
The final efficiency observation concerns implementing the standalone operators. Look again at the implementation 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 as that of lhs. This temporary is then used to invoke operator+= with rhs, and the result of
that operation is returned from operator+.† 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 template contains 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 on page 104B). The first
implementation has always been eligible for the return value optimization, so the odds may be better that the compilers you use will generate optimized code for it.
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 first implementation above may thus cost
you one temporary object within the function, just as you’d pay for
using the named object result. However, the fact remains that unnamed objects have historically been easier to eliminate than named
objects, so when faced with a choice between a named object and a
† At least that’s what’s supposed to happen. Alas, some compilers treat T(lhs) as a cast
to remove lhs’s constness, then add rhs to lhs and return a reference to the modified
lhs! Test your compilers before relying on the behavior described above.
110B
Item 23
temporary object, you may be better off using the temporary. It should
never cost you more than its named colleague, and, especially with
older compilers, it may cost you less.
All this talk of named objects, unnamed objects, and compiler optimizations is interesting, but let us not forget the big picture. The big picture is that assignment versions of operators (such as operator+=)
tend to be more efficient than stand-alone versions of those operators
(e.g. operator+). As a library designer, you should offer both, and as
an application developer, you should consider using assignment versions of operators instead of stand-alone versions whenever performance is at a premium.
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 rarely intuitive. Bug-free libraries
are limited in scope. In the real world, you can’t have everything; something always has to give.
Considering Alternative Libraries
Different designers assign different priorities to these criteria. They
thus sacrifice different things in their designs. As a result, it is not uncommon for two libraries offering similar functionality to have quite
different performance 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. It’s type-safe, for example,
and it’s extensible. In terms of efficiency, however, the iostream library
generally suffers in comparison with stdio, because stdio usually results in executables that are both smaller and faster than those arising
from iostreams.
Consider first the speed issue. One way to get a feel for the difference
in performance between iostreams and stdio is to run benchmark applications using both libraries. Now, it’s important to bear in mind that
benchmarks lie. Not only is it difficult to come up with a set of inputs
that correspond to “typical” usage of a program or library, 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 to a problem, so though it would be foolish to rely on them completely, it would
also be foolish to ignore them.
Considering Alternative Libraries
111B
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 a fixed format. The choice between the iostream
and stdio libraries is made during compilation and is determined by
the preprocessor symbol STDIO. If this symbol is defined, the stdio library is used, otherwise the iostream library 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)
<< setprecision(5)
<< setiosflags(ios::showpoint)
<< setiosflags(ios::fixed)
<< d;
#endif
//
//
//
//
set field width
set decimal places
keep trailing 0s
use these settings
if (n % 5 == 0) {
#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:
112B
Item 23
0.00000
1.79176
2.39790
2.77259
3.04452
0.69315
1.94591
2.48491
2.83321
3.09104
1.09861
2.07944
2.56495
2.89037
3.13549
1.38629
2.19722
2.63906
2.94444
3.17805
1.60944
2.30259
2.70805
2.99573
3.21888
Such output demonstrates, if nothing else, that it’s possible to produce
fixed-format I/O using iostreams. Of course,
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 every case the stdio version has
been faster. Sometimes it’s been only a little faster (about 20%), sometimes it’s been substantially faster (nearly 200%), but I’ve never come
across an iostream implementation that was as fast as the corresponding stdio implementation. In addition, the size of this trivial program’s
executable using stdio tends to be smaller (sometimes much smaller)
than the corresponding program using iostreams. (For programs of a
realistic size, this difference is rarely significant.)
Bear in mind that any efficiency advantages of stdio are highly implementation-dependent, so future implementations of systems I’ve
tested or existing implementations of systems I haven’t tested may
show a negligible performance difference between iostreams and stdio.
In fact, one can reasonably hope to discover an iostream implementation that’s faster than stdio, because iostreams determine the types of
their operands during compilation, 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 performance trade-offs, so once you’ve identified the bottlenecks in your
software (via profiling — see Item 16), you should see if it’s possible to
remove those bottlenecks by replacing one library with another. If your
program has an I/O bottleneck, for example, you might consider replacing iostreams with stdio, but if it spends a significant portion of its
time on dynamic memory allocation and deallocation, you might see if
The Costs of Various Language Features
113B
there are alternative implementations of operator new and operator
delete available (see Item 8). Because different libraries embody different design decisions regarding efficiency, extensibility, portability,
type safety, and other issues, you can sometimes significantly improve
the efficiency of your software by switching to libraries whose designers gave more weight to performance considerations than to other factors.
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, of course, compiler-dependent, and different compilers implement language features in different
ways. For the most part, you need not concern yourself with such matters. However, the implementation of some features can have a noticeable 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.
The foremost example of such a feature is virtual functions.
The Costs of Various Language Features
When a virtual function is called, the code executed must correspond
to the dynamic type of the object on which the function is invoked; the
type of the pointer or reference to the object is immaterial. How can
compilers provide this behavior efficiently? Most implementations use
virtual tables and virtual table pointers. Virtual tables 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 has its own vtbl, and the entries in a class’s vtbl are pointers
to the implementations of the virtual functions for that class. For example, given a class definition like this,
114B
Item 24
class C1 {
public:
C1();
virtual
virtual
virtual
virtual
~C1();
void f1();
int f2(char c) const;
void f3(const string& s);
void f4() const;
...
};
C1’s virtual table array will look something like this:
C1’s
vtbl
implementation of C1::~C1
implementation of C1::f1
implementation of C1::f2
implementation of C1::f3
Note that the nonvirtual function f4 is not in the table, nor is C1’s constructor. Nonvirtual functions — including constructors, which are by
definition nonvirtual — are implemented just like ordinary C functions, so there are no special 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 its own,
class C2:
public:
C2();
virtual
virtual
virtual
...
};
public C1 {
~C2();
void f1();
void f5(char *str);
//
//
//
//
nonvirtual function
redefined function
redefined function
new virtual function
its virtual table entries point to the functions that are appropriate for
objects of its type. These entries include pointers to the C1 virtual
functions that C2 chose not to redefine:
C2’s
vtbl
implementation of C2::~C2
implementation of C2::f1
implementation of C1::f2
implementation of C1::f3
implementation of C2::f5
This discussion brings out the first cost of virtual functions: you have
to set aside space for a virtual table for each class that contains virtual
The Costs of Various Language Features
115B
functions. The size of a class’s vtbl is proportional to the number of virtual functions declared for that class (including those it inherits from
its base classes). There should be only one virtual table per class, so
the total amount of space required for virtual tables is not usually significant, but if you have a large number of classes or a large number of
virtual functions in each class, you may find that the vtbls 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 object file is generated independently of the others. Which object file should contain the vtbl for any given class? You might think to
put it in the object file containing main, but libraries have no main,
and at any rate the source file containing main may make no mention
of many of the classes requiring vtbls. How could compilers then know
which vtbls they were supposed to create?
A different strategy must be adopted, and compiler vendors tend to fall
into two camps. For vendors who provide an integrated environment
containing both compiler and linker, a brute-force strategy is to generate a copy of the vtbl in each object file that might need it. The linker
then strips out duplicate copies, leaving only a single 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 a class. 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 above would 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 virtual functions inline. If all virtual
functions in a class are declared inline, the heuristic fails, and most
heuristic-based implementations then generate a copy of the class’s
vtbl in every object file that uses it. In large systems, this can lead to
programs containing hundreds or thousands of copies of a class’s vtbl!
Most compilers following this heuristic give you some way to control
vtbl generation manually, but a better solution to this problem is to
avoid declaring virtual functions inline. As we’ll see below, there are
116B
Item 24
good reasons why present compilers 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 is the 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 virtual table for that class. This
hidden data member — the vptr — is added by compilers at a location
in the object known only to the compilers. Conceptually, we can think
of the layout of an object that has virtual functions as looking like this:
Data members
for
the object
Object’s vptr
This picture shows the vptr at the end of the object, but don’t be fooled:
different compilers put them in different places. In the presence of inheritance, an object’s vptr is often surrounded by data members. Multiple inheritance complicates this picture, but we’ll deal with that a bit
later. At this point, simply note the second cost of virtual functions:
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 member data, for example, the addition of a vptr can double their size (assuming four bytes are devoted to
the vptr). On systems with limited memory, this means the number of
objects you can create is reduced. Even on systems with unconstrained memory, you may find that the performance of your software
decreases, because larger objects mean fewer fit on each cache or virtual memory page, and that means your paging activity will probably
increase.
Suppose we have a program with several objects of types C1 and C2.
Given the relationships among objects, vptrs, and vtbls that we have
The Costs of Various Language Features
117B
just seen, we can envision the objects in our program like this:
C1 Object
Data
Members
C1’s
C1 Object
Data
Members
vptr
C1 Object
Data
Members
vtbl
Implementations
of C1’s virtual
functions
vptr
vptr
C2 Object
Data
Members
vptr
C2 Object
Data
Members
C2’s
vtbl
vptr
C2 Object
Data
Members
Implementations
of C2’s virtual
functions
vptr
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 to know which f1 function —
C1::f1 or C2::f1 — should be invoked, because pC1 might point to a C1
object or to a C2 object. Your compilers must nevertheless generate
code for the call to f1 inside makeACall, and they must ensure that
the correct function is called, no matter what pC1 points to. They do
this by generating code to do the following:
1. Follow the object’s vptr to its vtbl. This is a simple operation, because the compilers know where to look inside 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, is simple, because compilers assign each virtual function a unique index within the
table. The cost of this step is just an offset into the vtbl array.
3. Invoke the function pointed to by the pointer located in step 2.
118B
Item 24
If we imagine that each object has a hidden member called vptr and
that the vtbl index of function f1 is i, the code 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 more instructions. The cost of calling a
virtual function is thus basically the same as that of calling a function
through a function 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 practical purposes, virtual functions aren’t
inlined. That’s because “inline” means “during compilation, replace the
call site with the body of the called function,” but “virtual” means “wait
until runtime to see which function is called.” If your compilers don’t
know which function will be called at a particular call site, you can understand why they won’t inline that function call. This is the third cost
of virtual functions: you effectively give up inlining. (Virtual functions
can be inlined when invoked through objects, but most virtual function
calls are made through pointers or references to objects, and such calls
are not inlined. Because such calls are the norm, virtual functions are
effectively not inlined.)
Everything we’ve seen so far applies to both single and multiple inheritance, but when multiple inheritance enters the picture, things get
more complex. There is no point in dwelling on details, but with multiple inheritance, offset calculations to find vptrs within objects become
more complicated; there are multiple vptrs within a single object (one
per base class); and special vtbls must be generated for base classes in
addition to the stand-alone vtbls we have discussed. As a result, both
the per-class and the per-object space overhead 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 derived class has more than one inheritance path to a base class, the data members of that base class are
replicated within each derived class object, one copy for each path between the derived class and the base class. Such replication is almost
never what programmers want, and making base classes virtual eliminates the replication. Virtual base classes may incur a cost of their
The Costs of Various Language Features
119B
own, however, because implementations of virtual base classes often
use pointers to virtual base class parts as the means for avoiding the
replication, and one or more of those pointers may be stored inside
your objects.
For example, consider this, which I generally call “the dreaded multiple
inheritance diamond:”
A
class
class
class
class
A { ... };
B: virtual public A { ... };
C: virtual public A { ... };
D: public B, public C { ... };
B
C
D
Here A is a virtual base class because B and C virtually inherit from it.
With some compilers (especially older compilers), the layout for an object of type D is likely to look like this:
B Data Members
Pointer to virtual base class
C Data Members
Pointer to virtual base class
D Data Members
A Data Members
It seems a little strange to place the base class data members at the
end of the object, but that’s often how it’s done. Of course, implementations are free to organize memory any way they like, so you should
never rely on this picture for anything more than a conceptual overview of how virtual base classes may lead to the addition of hidden
pointers to your objects. Some implementations add fewer pointers,
and some find ways to add none at all. (Such implementations make
the vptr and vtbl serve double duty).
If we combine this picture with the earlier one showing how virtual
table pointers are added to objects, we realize that if the base class A
120B
Item 24
in the hierarchy on page 119 has any virtual functions, the memory
layout for an object of type D could look like this:
B Data Members
vptr
Pointer to virtual base class
C Data Members
vptr
Pointer to virtual base class
D Data Members
A Data Members
vptr
Here I’ve shaded the parts of the object that are added by compilers.
The picture may be misleading, because the ratio of shaded to unshaded areas is determined by the amount of data in your classes. For
small classes, the relative overhead is large. For classes with more
data, the relative overhead is less significant, though it is 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 a vptr), 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 how multiple inheritance and virtual base classes can also increase the size of objects. Let us therefore
turn to our final 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 the information we’re allowed
to query. That information is stored in an object of type type_info,
and you can access the 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 to that information for any object.
Actually, that’s not quite true. The language specification states that
we’re guaranteed accurate information on an object’s dynamic type
The Costs of Various Language Features
121B
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 per class, and we need a way to get to the appropriate
information from any object containing a virtual function. This parallel
between RTTI and virtual function tables is no accident: RTTI was designed to be implementable in terms 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 corresponding to that vtbl. The vtbl for
class C1 on page 114 would then look like this:
C1’s type_info object
implementation of C1::~C1
implementation of C1::f1
implementation of C1::f2
implementation of C1::f3
C1’s
vtbl
With this implementation, the space cost of RTTI is an additional entry
in each class vtbl plus the cost of the storage for the type_info object
for each class. Just as the memory for virtual tables is unlikely to be
noticeable for 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:
Increases
Size of Objects
Increases
Per-Class Data
Reduces
Inlining
Virtual Functions
Yes
Yes
Yes
Multiple Inheritance
Yes
Yes
No
Virtual Base Classes
Often
Sometimes
No
No
Yes
No
Feature
RTTI
Some people look at this table and are aghast. “I’m sticking with C!”,
they declare. Fair enough. But remember that each of these features
offers functionality you’d otherwise have to code by hand. In most
cases, your manual approximation would probably be less efficient
and less robust than the compiler -generated code. Using nested
switch statements or cascading if-then-elses to emulate virtual
function calls, for example, yields more code than virtual function calls
do, and the code runs more slowly, too. Furthermore, you must manually track object types yourself, which means your objects carry
around type tags of their own; you thus often fail to gain even the benefit of smaller objects.
122B
Item 24
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. For example,
hidden vptrs and pointers to virtual base classes can make it difficult
to store C++ objects in databases or to move them across process
boundaries, so you may wish to emulate these features in a way that
makes it easier to accomplish these other tasks. From the point of view
of efficiency, however, you are unlikely to do better than the compilergenerated implementations by coding these features yourself.
Techniques
Most of this book is concerned with programming guidelines. Such
guidelines are important, but no programmer lives by guidelines alone.
According to the old TV show Felix the Cat, “Whenever he gets in a fix,
he reaches into his bag of tricks.” Well, if a cartoon character can have
a bag of tricks, so too can C++ programmers. Think of this chapter as
a starter set for your bag of tricks.
Techniques
Some problems crop up repeatedly when designing C++ software. How
can you make constructors and non-member functions act like virtual
functions? How can you limit the number of instances of a class? How
can you 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
member functions are called? How can you have different objects
share data structures while giving clients the illusion that each has its
own copy? How can you distinguish between read and write usage of
operator[]? How can you create 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 problems commonly encountered by C++
programmers. I call such solutions techniques, but they’re also known
as idioms and, when documented in a stylized fashion, patterns. Regardless of what you call them, the information that follows will serve
you well as you engage in the day-to-day skirmishes of practical software development. It should also convince you that no matter what
you want to do, there is almost certainly a way to do it in C++.
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 to achieve type-specific behavior
Virtualizing Constructors and Non-Member Functions
124B
Item 25
when you have a pointer or reference to an object but you don’t know
what the real type of the object is. You call a constructor only when you
don’t yet have an object but you know exactly what 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 nonsensical ideas are never useful,
how do you explain the success of modern physics?) For example, suppose you write applications for working with newsletters, where a
newsletter consists of components that are either textual or graphical.
You might organize things this way:
class NLComponent {
public:
// abstract base class for
// 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 {
public:
...
// a newsletter object
// consists of a list of
// NLComponent objects
private:
list<NLComponent*> components;
};
The classes relate in this way:
NewsLetter
object
NLComponent
pointers
list
object
public
inheritance
TextBlock
Graphic
The list class used inside NewsLetter is part of the Standard Template Library, which is part of the standard C++ library (see Item 35).
Virtualizing Constructors and Non-Member Functions
125B
Objects of type list behave like doubly linked lists, though they need
not be implemented in that way.
NewsLetter objects, when not being worked on, would likely be stored
on disk. To support the creation of a Newsletter from its on-disk representation, it would be convenient to give NewsLetter a constructor
that takes an istream. The constructor would read information from
the stream as it created the necessary in-core data structures:
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;
}
}
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 the data it reads. Because it
126B
Item 25
creates new objects, it acts much like a constructor, but because it can
create different types of objects, we call it a virtual constructor. A virtual
constructor is a function that creates different types of objects depending on the input it is given. Virtual constructors are useful in many
contexts, only one of which is 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 copy constructor returns a pointer to
a new copy of the object invoking the function. Because of this behavior, virtual copy constructors are typically given names like copySelf,
cloneSelf, or, as shown below, just plain clone. Few functions 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
{ return new TextBlock(*this); }
...
// virtual copy
// constructor
};
class Graphic: public NLComponent {
public:
virtual Graphic * clone() const
{ return new Graphic(*this); }
...
// virtual copy
// constructor
};
As you can see, a class’s virtual copy constructor just calls its real copy
constructor. The meaning of “copy” is hence the same for both functions. If the real copy constructor performs a shallow copy, so does the
virtual copy constructor. If the real copy constructor performs a deep
copy, so does the virtual copy constructor. If the real copy constructor
does something fancy like reference counting or copy-on-write (see
Item 29), so does the 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 types that was adopted relatively recently. No longer must a derived class’s redefinition of a base
class’s virtual function declare the same return type. Instead, if the
Virtualizing Constructors and Non-Member Functions
127B
function’s return type is a pointer (or a reference) to a base class, 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 virtual
copy constructors. That’s why TextBlock’s clone can return a TextBlock* and Graphic’s clone can return a Graphic*, 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) copy constructor 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 is simple: just iterate over the list of
components for the NewsLetter object being copied, and for each
component in the list, call its virtual copy constructor. We need a virtual copy constructor here, because the list contains pointers to NLComponent objects, but we know each pointer really points to a
TextBlock or a Graphic. We want to copy whatever the pointer really
points to, and the virtual copy constructor does that for us.
128B
Item 25
Making Non-Member Functions Act Virtual
Just as constructors can’t really be virtual, neither can non-member
functions. However, just as it makes sense to conceive of functions
that construct new objects of different types, it makes sense to conceive of non-member functions whose behavior depends on the dynamic types of their parameters. For example, suppose you’d like to
implement output operators for the TextBlock and Graphic classes.
The obvious approach to this problem is to make the output operator
virtual. However, the output operator is operator<<, and that function takes an ostream& as its left-hand argument; that effectively rules
out the possibility of making it a member function 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;
...
};
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 the convention for output operators. To
get back to the normal syntax, we must move operator<< out of the
TextBlock and Graphic classes, but if we do that, we can no longer
declare it virtual.)
Virtualizing Constructors and Non-Member Functions
129B
An alternate approach is to declare a virtual function for printing (e.g.,
print) and define it for the TextBlock and Graphic classes. But if we
do that, the syntax for printing TextBlock and Graphic objects is inconsistent with that 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 nonmember function called operator<< that exhibits the behavior of a
virtual function like print. This description of what we want is in fact
very close to a description 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)
{
return c.print(s);
}
Virtual-acting non-member functions, then, are easy. You write virtual
functions to do the work, then write a non-virtual function that does
nothing but call the virtual function. To avoid incurring the cost of a
function call for this syntactic sleight-of-hand, of course, you inline the
non-virtual function.
Now that you know how to make non-member functions act virtually
on one of their arguments, you may wonder if it’s possible to make
them act virtually on more than one of their arguments. It is, but it’s
not easy. How hard is it? Turn to Item 31; it’s devoted to that question.
130B
Item 26
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 only one printer in your system,
so you’d like to somehow limit the number of printer objects to one. Or
you’ve got only 16 file descriptors you can hand out, so you’ve got to
make sure there are never more than that many file descriptor objects
in existence. How can you do such things? How can you limit the number of objects?
Limiting Object Instantiations
If this were a proof by mathematical induction, we might start with
n = 1, then build from there. Fortunately, this is neither a proof nor an
induction. Moreover, it turns out to be instructive to begin with n = 0,
so we’ll start there 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 the case, the easiest way to prevent
objects of a particular class from being created is to declare the constructors of that 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, for example, we want to create a class
for printers, but we also want to abide by the constraint that there is
only one printer available to us, we can encapsulate the printer object
inside a function so that everybody has access to the printer, but only
a single printer object is created:
class PrintJob;
// forward declaration
class Printer {
public:
void submitJob(const PrintJob& job);
void reset();
void performSelfTest();
...
friend Printer& thePrinter();
Limiting Object Instantiations
131B
private:
Printer();
Printer(const Printer& rhs);
...
};
Printer& thePrinter()
{
static Printer p;
return p;
}
// the single printer object
There are three separate components to this design. First, the constructors of the Printer class are private. That suppresses object creation. Second, the global function thePrinter is declared a friend of
the class. That lets thePrinter escape the restriction imposed by the
private constructors. Finally, thePrinter contains a static Printer
object. 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 a reference 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,” you may say, “as a global function it looks more like a global variable, but global variables are
gauche, and I’d prefer to localize all printer-related functionality inside
the Printer class.” Well, far be it from me to argue with someone 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 as tacky in its own right. Using a static member function,
Printer looks like this:
132B
Item 26
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. Namespaces are a recent addition
to C++. Anything that can be declared at global scope can also be declared in a namespace. This includes classes, structs, functions, variables, objects, typedefs, etc. The fact that something is in a namespace
doesn’t affect its behavior, but it does prevent name conflicts between
entities in different namespaces. By putting the Printer class and the
thePrinter function into a namespace, we don’t have to worry about
whether anybody else happened to choose the names Printer or thePrinter for themselves; our namespace 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 {
class Printer {
public:
// this class is in the
// PrintingStuff namespace
void submitJob(const PrintJob& job);
void reset();
void performSelfTest();
...
friend Printer& thePrinter();
Limiting Object Instantiations
133B
private:
Printer();
Printer(const Printer& rhs);
...
};
Printer& thePrinter()
{
static Printer p;
return p;
}
}
// so is this function
// this is the end of the
// namespace
Given this namespace, clients can refer to thePrinter using a fullyqualified name (i.e., one that includes the name 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; //
//
//
//
thePrinter().reset();
thePrinter().submitJob(buffer);
import the name
"thePrinter" from the
namespace "PrintingStuff"
into the current scope
// now thePrinter can be
// 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 the single Printer object be
static in a function and not in a class. An object that’s static in a class
is, for all intents and purposes, always constructed (and destructed),
even if it’s never used. In contrast, an object that’s static in a function
is created the first time through the function, so if the function is never
called, the object is never created. (You do, however, pay for a check
each time the function is called to see whether the object needs to be
created.) One of the philosophical pillars on which C++ was built is the
idea that you shouldn’t pay for things you don’t use, and defining an
object like our printer as a static object in a function is one way of adhering to this philosophy. 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 its time of initialization. We
know exactly when a function static is initialized: the first time
through the function at the point where the static is defined. The situ-
134B
Item 26
ation with a class static (or, for that matter, a global static, should you
be so gauche as to use one) is less well defined. C++ offers certain
guarantees regarding the order of initialization of statics within a particular translation unit (i.e., a body of source code that yields a single
object file), but it says nothing about the initialization order of static
objects in different translation units. In practice, this turns out to be a
source of countless headaches. Function statics, when they can be
made to suffice, 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 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 — it consists entirely of the statement “return p;”. If ever there were a good candidate for inlining, this
function would certainly 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 copy of that object, right? Now
consider what inline means. Conceptually, it means compilers
should replace each call 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 must remember: functions with internal linkage may be duplicated within a program (i.e., the object code
for the program may contain more than one copy of each function with
internal linkage), and this duplication includes static objects contained
within the functions. The result? If you create an inline non-member
function containing 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.†
But maybe you think this business of creating a function to return a
reference to a hidden object is the wrong way to go about limiting the
number of objects in the first place. Perhaps you think it’s better to
simply count the number of objects in existence and throw an excep† In July 1996, the ISO/ANSI standardization committee changed the default linkage of
inline functions to external, so the problem I describe here has been eliminated, at
least on paper. Your compilers may not yet be in accord with the standard, however,
so your best bet is still to shy away from inline functions with static data.
Limiting Object Instantiations
135B
tion in a constructor if too many objects are requested. In other 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
The idea is to use numObjects to keep track of how many Printer objects are in existence. This value will be incremented in the class constructor and decremented in its destructor. If an attempt is made to
construct too many 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;
}
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 the maximum number of objects is some number other than
one.
136B
Item 26
Contexts for Object Construction
There is also a problem with this strategy. Suppose we have a special
kind of printer, say, a color printer. The class for such printers would
have much in common with our generic printer class, so of course we’d
inherit from 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 the Printer part of cp. At runtime, a TooManyObjects exception will be thrown during the construction of the base class part of cp. For many programmers, this is
neither what they want nor what they expect. (Designs that avoid having concrete classes inherit from other concrete classes do not suffer
from this problem. For details on this design philosophy, see Item 33.)
A similar problem occurs when Printer objects are contained inside
other objects:
class CPFMachine {
private:
Printer p;
FaxMachine f;
CopyMachine c;
// for machines that can
// copy, print, and fax
// for printing capabilities
// for faxing capabilities
// 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 of more derived objects, and
embedded inside larger objects. The presence of these different contexts significantly muddies the waters regarding what it means to keep
track of the “number of objects in existence,” because what you 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 number of those kinds of instantiations. That restriction is easy to satisfy if you adopt the strategy exem-
Limiting Object Instantiations
137B
plified by our original 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.
The fact that you can’t derive from classes with private constructors
leads to a general scheme for preventing derivation, one that doesn’t
necessarily have to be coupled with limiting object instantiations. Suppose, for example, you have a class, FSA, for representing finite state
automata. (Such state machines are useful in many contexts, among
them user interface design.) Further suppose you’d like to allow any
number of FSA objects to be created, but you’d also like to ensure that
no class ever inherits from FSA. (One reason for doing this might be to
justify the presence of a nonvirtual destructor in FSA. As Item 24 explains, classes without virtual functions yield smaller objects than do
equivalent 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 makeFSA pseudo-constructor returns a pointer to a
unique object. That’s what allows an unlimited number of FSA objects
to be created.
This is nice, but the fact that each pseudo-constructor calls new implies that callers will have to remember to call delete. Otherwise a resource leak will be introduced. Callers who wish to have delete called
automatically when 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:
138B
Item 26
// 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 the number of objects of a particular class is complicated by the fact that object constructors are called
in three different contexts, and we know that we can eliminate the confusion surrounding object counts by making constructors private. It is
worthwhile to make one final observation. Our use of the thePrinter
function to encapsulate access to a single object limits the number of
Printer objects to one, but it also limits us to a single Printer 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;
use p2;
destroy p2;
...
This design never instantiates more than a single Printer object at a
time, but it does use different Printer objects in different parts of the
program. It somehow seems unreasonable that this isn’t allowed. After
all, at no point do we 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-constructors we just saw:
class Printer {
public:
class TooManyObjects{};
// pseudo-constructor
static Printer * makePrinter();
~Printer();
Limiting Object Instantiations
139B
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
// 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 this before doing anything with it, of course.
Clients use this Printer class just as they would any other class, except they must call the pseudo-constructor function instead of the real
constructor:
Printer p1;
Printer *p2 =
Printer::makePrinter();
// error! default ctor is
// private
// fine, indirectly calls
// default ctor
Printer p3 = *p2;
// error! copy ctor is
// private
p2->performSelfTest();
p2->reset();
// all other functions are
// called as usual
...
delete p2;
// avoid resource leak; this
// would be unnecessary if
// p2 were an auto_ptr
140B
Item 26
This technique is easily generalized to any number of objects. All we
have to do is replace the hard-wired constant 1 with a class-specific
value, then lift the restriction against copying objects. For example, the
following 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; }
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 class definition above. In particular, be
Limiting Object Instantiations
141B
prepared for them to complain about the specification of 10 as an initial value for 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 yet allow it. If your compilers are as-yet-unupdated,
pacify them by declaring maxObjects to be an enumerator inside 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 is easier for other programmers to
understand. When your compilers support the specification of initial
values for const 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 it that continues to nag. If we
had a lot of classes like Printer whose instantiations needed to be
limited, we’d have to write this same code over and over, once per
class. That would be mind-numbingly dull. Given a fancy-pants language like C++, it somehow seems we should be able to automate the
process. Isn’t there a way to encapsulate 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 from that, but it turns out we
can do even better. We can actually come up with a way to encapsulate
the whole counting kit and kaboodle, by which I mean not only the
functions to manipulate the instance count, but also the instance
count itself. (We’ll see the need for a similar trick when we examine reference counting in Item 29.)
142B
Item 26
The counter in the Printer class is the static variable numObjects, so
we need to move that variable into an instance-counting class. However, we also need to make sure that each class for which we’re counting instances has a separate counter. Use of a counting class template
lets us automatically generate the appropriate number of 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 size_t objectCount() { return numObjects; }
protected:
Counted();
Counted(const Counted& rhs);
~Counted() { --numObjects; }
private:
static size_t 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 protected constructors and destructor. Note
the use of the private member function init to avoid duplicating the
statements in the two Counted constructors.
We can now modify the Printer class to use the Counted template:
Limiting Object Instantiations
143B
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;
using Counted<Printer>::TooManyObjects;
// see below
// 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 why private inheritance is used here. The alternative would
b e t o u s e p u b l i c i n h e r i t a n c e b e t w e e n Printer a n d
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.) As Item 24 makes clear, the presence of a virtual function in
Counted would almost certainly affect the size and layout of objects of
classes inheriting from Counted. We don’t want to absorb that overhead, 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 want to find out how many
Printer objects exist. The Counted template offers the objectCount
function to provide this information, but that function becomes private
in Printer due to our use of private inheritance. To restore the public
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
};
144B
Item 26
This is perfectly legitimate, but if your compilers don’t yet support
namespaces, they won’t allow it. If they don’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 class TooManyObjects is handled in
the same fashion as objectCount, because clients of Printer must
have access to TooManyObjects 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 if somebody else were
doing the counting for it, because somebody else (Counted<Printer>)
is. A Printer constructor 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 if the limit is about to be exceeded, no incrementing the number of objects in existence once the
constructor is done. All that is now handled by the Counted<Printer>
constructors, and because Counted<Printer> is a base class of
Printer, we know that a Counted<Printer> constructor will always
be called before a Printer constructor. If too many objects are created, a Counted<Printer> constructor throws an exception, and the
Printer constructor 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 statics inside Counted. It’s easy
enough to take care of numObjects — we just put this in Counted’s
implementation file:
template<class BeingCounted>
// defines numObjects
size_t 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 to allow up to 10 printers, we
should initialize Counted<Printer>::maxObjects to 10. If, on the
Requiring or Prohibiting Heap-Based Objects
145B
other hand, we want to 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 require that clients of the class provide the appropriate initialization. The author of Printer must add
this to an implementation file:
const size_t Counted<Printer>::maxObjects = 10;
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 an error during linking, because
maxObjects will be undefined. Provided we’ve adequately documented
this requirement for clients of Counted, they can then say “Duh” to
themselves and go back and add the requisite initialization.
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 “delete this.” Such an arrangement clearly requires that objects of that type be allocated on the heap.
Other times you’ll want to bask in the certainty that there can be no
memory leaks for a particular class, because none of the objects could
have been allocated on the heap. This might be the case if you are
working on an embedded system, where memory leaks are especially
troublesome and heap space is at a premium. Is it possible to produce
code that requires or prohibits heap-based objects? Often it is, but it
also turns out that the notion of being “on the heap” is more nebulous
than you might think.
Requiring or Prohibiting Heap-Based Objects
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 to find a way to prevent clients
from creating objects other than by calling new. This is easy to do. Nonheap objects are automatically constructed at their point of definition
and automatically destructed at the end of their lifetime, 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. This is overkill. There’s no
reason why they both need to be private. Better to make the destructor
private and the constructors public. Then, in a process that should be
familiar from Item 26, you can introduce a privileged pseudo-destruc-
146B
Item 27
tor function that has access to the real destructor. Clients then call the
pseudo-destructor to destroy the objects they’ve created.
If, for example, we want to ensure that objects representing unlimited
precision numbers are created only on the heap, 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 constructors, and the class’s
author must remember to declare each of them private. This includes
the copy constructor, and it may include a default constructor, too, if
these functions would otherwise be generated by compilers; compilergenerated functions are always public. As a result, it’s easier to declare
only 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 a story that is told in Item 26,
it also prevents both inheritance and containment:
class UPNumber { ... };
class NonNegativeUPNumber:
public UPNumber { ... };
// declares dtor or ctors
// private
// error! dtor or ctors
// won’t compile
Requiring or Prohibiting Heap-Based Objects
class Asset {
private:
UPNumber value;
...
147B
// error! dtor or ctors
// won’t compile
};
Neither of these difficulties is insurmountable. The inheritance problem can be solved by making UPNumber’s destructor protected (while
keeping its constructors public), and classes that need to contain objects of type UPNumber can be modified to contain pointers to UPNumber
objects instead:
class UPNumber { ... };
class NonNegativeUPNumber:
public UPNumber { ... };
// declares dtor protected
// 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))
{ ... }
Asset::~Asset()
{ value->destroy(); }
// fine
// 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 definition sketched 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 answer depends on the details of the
class’s design and implementation, but let us suppose it is not okay,
that all UPNumber objects — even base class parts of more derived objects — must be on the heap. How can we enforce this restriction?
There is no easy way. It is not possible for a UPNumber constructor to
determine whether it’s being invoked as the base class part of a heap-
148B
Item 27
based object. That is, there is no way for the UPNumber constructor to
detect that the 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 new operator, operator new and
the constructor that the new operator calls (see Item 8). Perhaps you
think you can outsmart 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 on the heap, operator new is
called to allocate the raw memory, then a constructor is called to initialize an object in that memory. In particular, operator new sets
onTheHeap to true, and each constructor checks onTheHeap to see if
the raw memory of the object being constructed was allocated by op-
Requiring or Prohibiting Heap-Based Objects
149B
erator new. If not, an exception of type HeapConstraintViolation is
thrown. Otherwise, construction proceeds as usual, and when construction is finished, 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 more troublesome is the fact that numberArray has 100 elements,
so there will be 100 constructor calls. But there is 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 of the second one. This code has
a resource leak, but let us ignore that in favor of an examination of
what happens during 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 UPNumber constructors (see Item 8). Programmers typically expect these function calls to be executed in this order,
1. Call operator new for first object (the leftmost one above)
2. Call constructor for first object
3. Call operator new for second object (the one used as an argument to the first UPNumber’s constructor)
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 calls in this order instead:
1. Call operator new for first object
2. Call operator new for second object
3. Call constructor for second object
4. Call constructor for first object
150B
Item 27
There is nothing wrong with compilers that generate this kind of code,
but the set-a-bit-in- operator -new trick fails with such compilers.
That’s because the bit set in steps 1 and 2 is cleared in step 3, thus
making the object constructed in step 3 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 to determine 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, you might decide to take advantage of the fact that on many systems, a program’s address space is organized as a linear sequence of addresses, with the program’s stack
growing down from the top of the address space and the heap rising up
from the bottom:
Stack
(Grows Down)
High
Addresses
Program’s
Address
Space
(Incomplete
Picture)
Heap
(Grows Up)
Low
Addresses
On systems that organize a program’s memory in this way (many do,
but many do not), you might think you could use 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;
}
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
Requiring or Prohibiting Heap-Based Objects
151B
onHeap is called, its stack frame (i.e., its activation record) will be
placed at the top of the program’s stack, and because the stack grows
down (toward lower addresses) in this architecture, the address of onTheStack must be less than the address of any other stack-based variable or object. If the parameter address 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 three places where objects may
be allocated, not two. Yes, the stack and the heap hold objects, but let
us not forget about static objects. Static objects are those that are initialized only once during a program run. Static objects comprise not
only those objects explicitly declared static, but also objects at global
and namespace scope. 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 one another, they go below the
heap. The earlier picture of memory organization, while telling the
truth and nothing but the truth for many systems, failed to tell the
whole truth for those systems. With static objects added to the picture,
it looks like this:
Stack
(Grows Down)
High
Addresses
Program’s
Address
Space
(Complete
Picture)
Heap
(Grows Up)
Static
Objects
Low
Addresses
Suddenly it becomes clear why onHeap won’t work, not even on systems where it’s purported to: it fails to distinguish between heap objects and static objects:
void allocateSomeObjects()
{
char *pc = new char;
// heap object: onHeap(pc)
// will return true
152B
Item 27
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 may be willing to strike a deal with
the portability Devil, but are you so desperate that you’ll strike a deal
that fails to guarantee you the right answers? Surely not, so I know
you’ll reject this seductive but unreliable 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 a semi-portable way that
works most of the time. † If you absolutely, positively have to tell
whether an address is on the heap, you’re going to have to turn to unportable, implementation-dependent system calls, and that’s that.
That being the case, you’re better off trying to redesign your software
so you don’t need to determine whether an object is on the heap 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 if it’s safe to invoke delete
on it. Often such deletion will take the form of the infamous “delete
this.” Knowing whether it’s safe to delete a pointer, however, is not the
same as simply knowing whether that pointer points to something on
the heap, because not all pointers to things on the heap can be safely
deleted. Consider again an Asset 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 a pointer 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 a pointer points to something on
the heap, because all we need to answer the former question is a collection of addresses that have been returned by operator new. Since
we can write operator new ourselves, it’s easy to construct such a collection. Here’s how we might approach the problem:
† I have since become convinced that signature-based techniques are all but foolproof.
For details, consult http://www.aristeia.com/BookErrata/M27Comments.html.
Requiring or Prohibiting Heap-Based Objects
void *operator new(size_t size)
{
void *p = getMemory(size);
//
//
//
//
153B
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, operator delete removes entries, and
isSafeToDelete does a lookup in the collection to see if a particular
address is there. If the operator new and operator delete functions
are at global scope, this should work for all types, even the built-ins.
In practice, three things are likely to dampen our enthusiasm for this
design. The first is our extreme reluctance to define anything at global
scope, especially functions with predefined meanings like operator
new and operator delete. Knowing as we do that there is but one
global scope and but a single version of operator new and operator
delete with the “normal” signatures (i.e., sets of parameter types)
within that scope, the last thing we want to do is seize those function
signatures for ourselves. Doing so would render our software incompatible 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 overhead necessary 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 implement isSafeToDelete so that it always
works. The difficulty has to do with the fact that objects with multiple
154B
Item 27
or virtual base classes have multiple addresses, so there’s no guarantee that the address passed to isSafeToDelete is the same as the one
returned from operator new, even if the object in question was allocated on the heap. For details, see Items 24 and 31.
What we’d like is the functionality provided by these functions without
the concomitant pollution of the global namespace, the mandatory
overhead, and the correctness problems. Fortunately, C++ gives us exactly what we need 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. A mixin (“mix in”) class is
one that provides a single well-defined capability and is designed to be
compatible with any other capabilities an inheriting class might provide. Such classes are nearly always abstract. We can therefore come
up with an abstract mixin base class that offers derived classes the
ability to determine whether a pointer was allocated from operator
new. Here’s such a class:
class HeapTracked {
public:
class MissingAddress{};
// mixin class; keeps track of
// ptrs returned from op. new
// 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 35) to keep track 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’s address is in the list.
Implementation of the HeapTracked class is simple, because the
global operator new and operator delete functions are called to
perform the real memory allocation and deallocation, and the list
class has functions to make insertion, removal, and lookup singlestatement operations. Here’s the full implementation of HeapTracked:
// mandatory definition of static class member
list<RawAddress> HeapTracked::addresses;
Requiring or Prohibiting Heap-Based Objects
155B
// HeapTracked’s destructor is pure virtual to make the
// class abstract. The destructor must still be
// defined, however, so we provide this empty definition.
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)
{
// gracefully hande null pointers
if (ptr == 0) return;
// 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 the other components of the Standard Template Library. Item 35 explains everything, but the comments
in the code above 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);
156B
Item 27
I mentioned earlier that writing the global function isSafeToDelete is
complicated by the fact that objects with multiple or virtual base
classes have several addresses. That problem plagues us in isOnHeap,
too, but because isOnHeap 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 volatile void* or, for those who can’t get
enough modifiers in their usual diet, const volatile void*) yields a
pointer to the beginning of the memory for the object pointed to by the
pointer. But dynamic_cast is applicable only to pointers to objects
that have at least one virtual function. Our ill-fated isSafeToDelete
function had to work with any type of pointer, so dynamic_cast
wouldn’t help it. isOnHeap is more selective (it tests only pointers to
HeapTracked objects), so dynamic_casting this to const void*
gives us a pointer to the beginning of the memory for the current object. That’s the pointer that HeapTracked::operator new must have
returned if the memory for the current object was allocated by HeapTracked::operator new in the first place. Provided your compilers
support the dynamic_cast operator, this technique is completely portable.
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 to determine whether a pointer to an Asset object points to a
heap-based object, we’d modify Asset’s class definition 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 types like int and char can’t in-
Requiring or Prohibiting Heap-Based Objects
157B
herit from anything. Still, the most common reason for wanting to use
a class like HeapTracked is to determine whether it’s okay to “delete
this,” and you’ll never want to do that with a built-in type 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 spectrum is 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 class objects, 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 always created by calls to new and you
can make it impossible for clients to call new. Now, you can’t affect the
availability of the new operator (that’s built into the language), but you
can take advantage of the fact that the new operator always calls operator new (see Item 8), and that function is one you can declare yourself. In particular, it is one you can declare private. If, for example,
you want to keep clients from creating UPNumber objects 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 operator delete be public, so unless
there’s a compelling reason to split up the pair, it’s best to declare
them in the same part of a class. If you’d like to prohibit heap-based
arrays of UPNumber objects, too, you could declare operator new[]
and operator delete[] (see Item 8) private as well.
Interestingly, declaring operator new private often also prevents UPNumber objects from being instantiated as base class parts of heap-
158B
Item 27
based derived class objects. That’s because operator new and operator delete are inherited, so if these functions aren’t declared public
in a derived class, that class inherits the private versions declared in
its base(s):
class UPNumber { ... };
// as above
class NonNegativeUPNumber:
public UPNumber {
...
};
// assume this class
// declares no operator new
NonNegativeUPNumber n1;
// okay
static NonNegativeUPNumber n2; // also okay
NonNegativeUPNumber *p =
new NonNegativeUPNumber;
// error! attempt to call
// private operator new
If the derived class declares an operator new of its own, that function
will be called when allocating derived class objects on the heap, and a
different way will have to be found to prevent UPNumber base class
parts from winding up there. Similarly, the fact that UPNumber’s operator new is private has no effect on attempts to allocate 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 the UPNumber constructors if a UPNumber object was being constructed in memory that wasn’t on the
heap. This time, of course, we want to throw an exception if the object
in question is on the heap. Just as there is no portable way to determine if an address is on the heap, however, there is no portable way to
determine that it is not on the heap, so we’re out of luck. This should
be no surprise. After all, if we could tell when an address is on the
heap, we could surely tell when an address is not on the heap. But we
can’t, so we can’t. Oh well.
Smart Pointers
159B
Item 28: Smart pointers.
Smart pointers are objects that are designed to look, act, and feel like
built-in pointers, but to offer greater functionality. 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).
Smart Pointers
When you use smart pointers in place of C++’s built-in pointers (i.e.,
dumb pointers), you gain control over the following aspects of pointer
behavior:
■
Construction and destruction. You determine what happens
when a smart pointer is created and destroyed. It is common to
give smart pointers a default value of 0 to avoid the headaches associated with uninitialized pointers. Some smart pointers are
made responsible for deleting the object they point to when the
last smart pointer pointing to the object is destroyed. This can go a
long way toward eliminating resource leaks.
■
Copying and assignment. You control what happens when a
smart pointer is copied or is involved in an assignment. For some
smart pointer types, the desired behavior is to automatically copy
or make an assignment to what is pointed to, i.e., to perform a
deep copy. For others, only the pointer itself should be copied or
assigned. For still others, these operations should not be allowed
at all. Regardless of what behavior 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? You get to decide. You could,
for example, use smart pointers to help implement the lazy fetching strategy outlined in Item 17.
Smart pointers are generated from templates because, like built-in
pointers, they must be strongly typed; the template parameter specifies the type of object pointed to. Most smart pointer templates look
something like this:
160B
Item 28
template<class T>
class SmartPtr {
public:
SmartPtr(T* realPtr = 0);
// template for smart
// pointer objects
//
//
//
//
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 where copying and assignment are not
allowed, they would typically be declared private. The two dereferencing operators are declared const, because dereferencing a pointer
doesn’t modify it (though it may lead to modification of what the
pointer points to). Finally, each smart pointer-to-T object is implemented by containing a dumb pointer-to-T within it. It is this dumb
pointer that does the actual pointing.
Before going into the details of smart pointer implementation, it’s
worth seeing how clients might use smart pointers. Consider a distributed system in which some objects are local and some are remote. Access to local objects is generally simpler and faster than access to
remote objects, because remote access may require remote procedure
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 is more convenient to have all
objects appear to be located in the same place. Smart pointers allow a
library to offer this illusion:
template<class T>
class DBPtr {
public:
DBPtr(T *realPtr = 0);
// template for smart ptrs
// to objects in a
// distributed DB
// create a smart ptr to a
// DB object given a local
// dumb pointer to it
Smart Pointers
DBPtr(DataBaseID id);
161B
// create a smart ptr to a
// DB object given its
// unique DB identifier
...
};
// other smart ptr
// functions as above
class Tuple {
public:
...
void displayEditDialog();
// class for database
// tuples
bool isValid() const;
};
// return whether *this
// passes validity check
// present a graphical
// dialog box allowing a
// user to edit the tuple
// 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 programmer writing editTuple need not be
concerned with such matters; the smart pointer class hides that aspect of the system. As far as the programmer is concerned, all tuples
are accessed through objects that, except for how they’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 call to displayEditDialog with calls to begin and end the log entry. In the approach
shown here, the LogEntry’s constructor begins the log entry and its
destructor ends the log entry. As Item 9 explains, using an object to
begin and end logging is more robust in the face of exceptions than explicitly calling functions, so you should accustom yourself to using
162B
Item 28
classes like LogEntry. Besides, it’s easier to create a single LogEntry
object than to add separate 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’s testimony to the effectiveness of
encapsulation. Clients of smart pointers are supposed to be able to
treat them as dumb 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 the smart pointer’s constructor arguments), then make the smart pointer’s internal dumb pointer point
there. If no object 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 complicated somewhat by the issue of ownership. If a smart pointer owns the object it points to, it is responsible for
deleting that object when it (the smart pointer) is destroyed. This assumes the object pointed to by the smart pointer is dynamically allocated. Such an assumption is common when working with smart
pointers. (For ideas on how to make 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 smart pointer that points to a
heap-based object until it (the auto_ptr) is destroyed. When that happens, the auto_ptr’s destructor 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 copied or assigned?
auto_ptr<TreeNode> ptn1(new TreeNode);
auto_ptr<TreeNode> ptn2 = ptn1;
// call to copy ctor;
// what should happen?
Smart Pointers
163B
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. This would 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 (and are
frequently disastrous).
An alternative would be to create a new copy of what was pointed to by
calling new. That would guarantee we didn’t have too many auto_ptrs
pointing to a single object, but it might engender an unacceptable performance hit for the creation (and later destruction) of the new object.
Furthermore, we wouldn’t necessarily know what type of object to create, because an auto_ptr<T> object need not point to an object of type
T; it might point to an object of a type derived from T. Virtual constructors (see Item 25) can help solve this problem, but it seems inappropriate to require their use in a general-purpose class like auto_ptr.
The problems would vanish if auto_ptr prohibited copying and assignment, but a more flexible solution was adopted 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>&
operator=(auto_ptr<T>& rhs);
// assignment
// 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
164B
Item 28
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;
rhs.pointee = 0;
// transfer ownership of
// *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 the object the auto_ptr points to.
Because object ownership is transferred when auto_ptr’s copy constructor is called, passing auto_ptrs by value is 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
...
}
When printTreeNode’s parameter p is initialized (by calling
auto_ptr’s copy constructor), ownership of the object pointed to by
ptn is transferred to p. When printTreeNode finishes executing, p
goes out of scope and its destructor deletes what it points to (which is
what ptn used to point to). ptn, however, no longer points to anything
(its underlying dumb pointer is null), so just about any attempt to use
it after the call to printTreeNode will yield undefined behavior. Passing auto_ptrs by value, then, is something to be done only if you’re
sure you want to transfer ownership of an object to a (transient) function parameter. Only rarely will you want to do this.
Smart Pointers
165B
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-toconst 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 to this version of printTreeNode, it retains ownership of the object it points to, and ptn can safely
be used after the call to printTreeNode. Thus, passing auto_ptrs by
reference-to-const avoids the hazards arising from pass-by-value.
The notion of transferring ownership from one smart pointer to another during copying and assignment is interesting, but you may have
been at least as interested in the unconventional declarations of the
copy constructor and assignment operator. These functions normally
take const parameters, but above they do not. In fact, the code above
changes these parameters during the copy or the assignment. In other
words, auto_ptr objects 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 language required that copy constructors and assignment operators take const parameters, you’d probably
have to cast away the parameters’ constness 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, that object 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 complete implementation. You’ll find one on
pages 291-294B, where you’ll also see that the auto_ptr template in
the standard C++ library has copy constructors and assignment operators that are more flexible than those described here. In the standard
auto_ptr template, those functions are member function templates,
not just member functions. (Member function templates are described
later in this Item.)
A smart pointer’s destructor often looks like this:
166B
Item 28
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 times the test is a bit more complicated. A smart pointer that employs reference counting (see Item 29)
must adjust a reference count before determining whether it has the
right to delete what it points to. Of course, some smart pointers are like
dumb pointers: they have no effect on the object they point to when
they themselves are destroyed.
Implementing the Dereferencing Operators
Let us now turn our attention to the very heart of smart pointers, the
operator* and operator-> functions. The former returns the object
pointed to. Conceptually, this is simple:
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. For example, if lazy fetching is being
used (see Item 17), the function may have to conjure up a new object
for pointee 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 will let 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 the actual derived class object, your function will return an object of the
wrong type! (This is the slicing problem — see 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 smart pointer will not properly
support virtual functions, and how smart is a pointer like that? Besides, returning a reference is more efficient anyway, because there is
no need to construct a temporary object (see Item 19). This is one of
Smart Pointers
167B
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 a null smart pointer, i.e., one
whose embedded dumb pointer is null. Relax. You can do anything you
want. The result 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 through memory setting every byte to your birth date
modulo 256? That’s okay, too. It’s not nice, but as far as the language
is concerned, you are completely unfettered.
The story with operator-> is similar to that for operator*, but before
examining operator->, let us remind ourselves of the unusual meaning of a call to this function. Consider again the editTuple function
that uses a smart 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 pointer object. Most of the time, you’ll want to return an ordinary dumb pointer. In those cases, you implement operator-> as follows:
template<class T>
T* SmartPtr<T>::operator->() const
{
perform "smart pointer" processing;
return pointee;
}
This will work fine. Because this function returns a pointer, virtual
function calls via operator-> will behave the way they’re supposed to.
168B
Item 28
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 pointers further, however, you must know more about dumb
pointer behavior and how smart pointers can and cannot emulate 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 smart pointers. 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 the problem that smart
pointers don’t act like dumb pointers when testing for nullness. A different approach is to provide an implicit conversion operator that allows the tests above to compile. The conversion traditionally employed
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 code like this:
ifstream inputFile("datafile.dat");
Smart Pointers
if (inputFile) ...
169B
// test to see if inputFile
// was successfully
// opened
Like all type conversion functions, this one has the drawback of letting
function calls succeed that most programmers would expect to fail (see
Item 5). In particular, it allows comparisons of smart pointers of completely different types:
SmartPtr<Apple> pa;
SmartPtr<Orange> po;
...
if (pa == po) ...
// this compiles!
Even if there is no operator== taking a SmartPtr<Apple> and a
SmartPtr<Orange>, this compiles, because both smart pointers can
be implicitly converted into void* pointers, and there is a built-in
comparison function for built-in pointers. This kind of behavior makes
implicit conversion functions dangerous. (Again, see Item 5, and keep
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-type comparisons.
There is a middle ground that allows you to offer a reasonable syntactic form for testing for nullness while minimizing 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 invoked is 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,
170B
Item 28
SmartPtr<TreeNode> ptn;
...
if (!ptn) {
...
}
else {
...
}
// fine
// ptn is null
// 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 implementations provide an operator! in
addition to the implicit conversion to void*, but these two functions
typically test for slightly different stream states. (In the C++ library
standard (see Item 35), the implicit conversion to void* has been replaced by an implicit conversion to bool, and operator bool always
returns the negation of operator!.)
Converting Smart Pointers to Dumb Pointers
Sometimes you’d like to add smart pointers to an application or library
that already uses dumb pointers. For example, your distributed database system may not originally have been distributed, so you may have
some old library 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!
Smart Pointers
171B
The call will fail to compile, because there is no way to convert a DB-
Ptr<Tuple> to a Tuple*. You can make it work 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 to a 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 Item 5?) They make it easy for
clients to program directly with dumb pointers, thus bypassing the
smarts your pointer-like objects are designed to provide:
void processTuple(DBPtr<Tuple>& pt)
{
Tuple *rawTuplePtr = pt;
// converts DBPtr<Tuple> to
// Tuple*
use rawTuplePtr to modify the tuple;
}
Usually, the “smart” behavior provided by a smart pointer is an essential component of your design, so allowing clients to use dumb pointers
typically leads to disaster. For example, if DBPtr implements the reference-counting strategy of Item 29, allowing clients to manipulate
dumb pointers directly will almost certainly lead to bookkeeping errors
that corrupt the reference-counting data structures.
172B
Item 28
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
conversion from a smart pointer to a dumb pointer is a user-defined
conversion, and compilers are forbidden from applying more than one
such conversion at a time. For example, suppose you have a class representing all the clients who have 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* to TupleAccessors (see Item
5). Now consider a function for merging the information in two TupleAccessors objects:
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
of conversions are prohibited by the language.
Smart pointer classes that provide an implicit conversion to a dumb
pointer open the door to a particularly nasty bug. Consider this code:
DBPtr<Tuple> pt = new Tuple;
...
delete pt;
Smart Pointers
173B
This should not compile. After all, pt is not a pointer, it’s an object,
and you can’t delete an object. Only pointers can be deleted, right?
Right. But remember from Item 5 that compilers use implicit type conversions to make function calls succeed whenever they can, and recall
from Item 8 that use of the delete operator leads to calls to a destructor and to operator delete, both of which are functions. Compilers
want these function calls to succeed, so in the delete statement
above, they implicitly convert pt to a Tuple*, then they delete that.
This will almost certainly break your program.
If pt owns the object it points to, that object is now deleted twice, once
at the point where delete is called, a second time when pt’s destructor is invoked. If pt doesn’t own the object, somebody else does. That
somebody may be the person who deleted pt, in which case all is well.
If, however, the owner of the object pointed to by pt is not the person
who deleted pt, we can expect the rightful owner to delete that object
again later. The first and last of these scenarios leads to an object
being deleted twice, and deleting an object more than once yields undefined behavior.
This bug is especially pernicious because the whole idea behind smart
pointers is to make them look and feel as much like dumb pointers as
possible. The closer you get to this ideal, the more likely your clients
are to forget they are using smart pointers. If they do, who can blame
them if they continue to think that in order to avoid resource 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 a compelling reason to do so.
Smart Pointers and Inheritance-Based Type Conversions
Suppose we have a public inheritance hierarchy modeling consumer
products for storing music:
MusicProduct
Cassette
CD
class MusicProduct {
public:
MusicProduct(const string& title);
virtual void play() const = 0;
virtual void displayTitle() const = 0;
...
};
174B
Item 28
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;
...
};
Further suppose we have a function that, given a MusicProduct object, displays the title of the product and then plays 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 smart counterparts:
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);
displayAndPlay(nightmareMusic, 0);
// error!
// 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 a SmartPtr<MusicProduct>. As far as
compilers are concerned, these are three separate classes — they have
no relationship to one another. Why should compilers think otherwise?
After all, it’s not like SmartPtr<CD> or SmartPtr<Cassette> inherits
from SmartPtr<MusicProduct>. With no inheritance relationship be-
Smart Pointers
175B
tween these classes, 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 each smart pointer class an implicit
type conversion operator (see Item 5) for each smart pointer class to
which it should be implicitly convertible. For example, in the music hierarchy, you’d add an operator SmartPtr<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); }
...
private:
CD *pointee;
};
The drawbacks to this approach are twofold. First, you must manually
specialize the SmartPtr class instantiations so you can add the necessary implicit type conversion operators, but that pretty much defeats
the purpose of templates. Second, you may have to add many such
conversion operators, because your pointed-to object may be deep in
an inheritance hierarchy, and you must provide a conversion operator
for each base class from which that object directly or indirectly inherits. (If you think you can get around this by providing only an implicit
type conversion operator for each direct base class, think again. Because compilers are prohibited from employing more than one userdefined type conversion function at a time, they can’t convert a smart
pointer-to-T to 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 conversion functions for you. Thanks to a
recent language extension, you can. The extension in question is the
ability to declare (nonvirtual) member function templates (usually just
176B
Item 28
called member templates), and you use it to generate smart pointer
conversion functions like this:
template<class T>
class SmartPtr {
public:
SmartPtr(T* realPtr = 0);
// template class for smart
// pointers-to-T objects
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 specific example in a moment, so don’t despair if the remainder of this paragraph reads like so much gobbledygook. After you’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 checks the class definition for SmartPtr<T> to see if the
requisite conversion operator is declared, but it is not. (It can’t be: no
conversion operators are declared in the template above.) The compiler
then checks to see if there’s a member function template it can instantiate that would let it perform the conversion it’s looking for. It finds
such a template (the one taking the formal type parameter newType),
so it instantiates the template with newType bound to the base class of
T that’s the target of the conversion. At that point, the only question is
whether the code 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 is certainly legal to convert it into a pointer to its
(public or protected) base classes. Hence, the code for the type conversion operator will compile, and the implicit conversion from smart
pointer-to-T to smart pointer-to-base-of-T will succeed.
An example will help. Let us return to the music hierarchy of CDs, cassettes, and music products. We saw earlier that the following code
wouldn’t compile, because there was no way for compilers to convert
the smart pointers to CDs or cassettes into smart pointers to music
products:
Smart Pointers
177B
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);
displayAndPlay(nightmareMusic, 0);
// used to be an error
// used to be an error
With the revised smart pointer class containing the member function
template for implicit type conversion operators, 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 a SmartPtr<MusicProduct> object. Compilers detect the type mismatch and seek a way to convert funMusic
into a SmartPtr<MusicProduct> object. They look for a single-argument constructor (see Item 5) in the SmartPtr<MusicProduct> class
that takes a SmartPtr<Cassette>, but they find none. They look for
an implicit type conversion operator in the SmartPtr<Cassette> class
that yields a SmartPtr<MusicProduct> class, but that search also
fails. They then look for a member function template they can instantiate to yield one of these functions. They discover that the template inside SmartPtr<Cassette>, when instantiated with newType bound to
MusicProduct, 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 the SmartPtr<MusicProduct> constructor
with pointee as its argument, so the real question is whether one can
construct a SmartPtr<MusicProduct> object with a Cassette*
pointer. The SmartPtr<MusicProduct> constructor expects a MusicProduct* pointer, but now we’re on the familiar ground of conversions
between dumb pointer types, and it’s clear that Cassette* can be
passed in where a MusicProduct* is expected. The construction of the
SmartPtr<MusicProduct> is therefore successful, and the conversion
of the SmartPtr<Cassette> to SmartPtr<MusicProduct> is equally
successful. Voilà! Implicit conversion of smart pointer types. What
could be simpler?
Furthermore, what could be more powerful? Don’t be misled by this
example into assuming that this works only for pointer conversions up
an inheritance hierarchy. The method shown succeeds for any legal
178B
Item 28
implicit conversion between pointer types. If you’ve got a dumb pointer
type T1* and another dumb pointer type T2*, you can implicitly convert a smart pointer-to-T1 to a smart pointer-to-T2 if and only if you
can implicitly convert a T1* to a T2*.
This technique gives you exactly the behavior you want — almost. Suppose we augment our MusicProduct hierarchy with a new class,
CasSingle, for representing cassette singles. The revised hierarchy
looks like this:
MusicProduct
Cassette
CD
CasSingle
Now consider this code:
template<class T>
class SmartPtr { ... };
// as above, including member tem// 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 and the other taking a
SmartPtr<Cassette> object. When we invoke displayAndPlay with
a SmartPtr<CasSingle>, we expect the SmartPtr<Cassette> function to be chosen, because CasSingle inherits directly from Cassette
and only indirectly from MusicProduct. Certainly that’s how it would
work with dumb pointers. Alas, our smart pointers aren’t that smart.
They employ member functions as conversion operators, and as far as
C++ compilers are concerned, all calls to conversion functions are
equally good. As a result, the call to displayAndPlay is ambiguous,
because the conversion from SmartPtr<CasSingle> to Smart-
Smart Pointers
179B
Ptr<Cassette> is no better than the conversion to SmartPtr<MusicProduct> .
Implementing smart pointer conversions through member templates
has two additional drawbacks. First, support for 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 from
transparent, relying as they do on a detailed understanding of argument-matching rules for function calls, implicit type conversion functions, implicit instantiation of template functions, and the existence of
member function templates. Pity the poor programmer who has never
seen this trick before and is then asked to maintain or enhance code
that relies on it. The technique is clever, that’s for sure, but too much
cleverness can be a dangerous thing.
Let’s stop beating around the bush. What we really want to know is
how we can make smart pointer classes behave just like dumb pointers for purposes of inheritance-based type conversions. The answer is
simple: we can’t. As Daniel Edelson has noted, smart pointers are
smart, but they’re not pointers. The best we can do is to use member
templates to generate conversion functions, then use casts (see Item 2)
in those cases where ambiguity results. This isn’t a perfect state of affairs, but it’s pretty good, and having to cast away ambiguity in a few
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:
CD goodCD("Flood");
const CD *p;
// p is a non-const pointer
// to a const CD object
CD * const p = &goodCD;
//
//
//
//
const CD * const p = &goodCD;
// p is a const pointer to
// a const CD object
p is a const pointer to
a non-const CD object;
because p is const, it
must be initialized
Naturally, we’d like to have the same flexibility with smart pointers.
Unfortunately, there’s only one place to put the const, and there it applies to the pointer, not to the object pointed to:
const SmartPtr<CD> p =
&goodCD;
// p is a const smart ptr
// to a non-const CD object
180B
Item 28
This seems simple enough to remedy — just create a smart pointer to
a const CD:
SmartPtr<const CD> p =
&goodCD;
// p is a non-const smart ptr
// 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 pointers and we can initialize
pointers to const objects with pointers to non-consts; the rules for
assignments are analogous. 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, they are unrelated, so they have
no reason to believe they are assignment-compatible. In what must be
an old story by now, the only way these two types will be considered
assignment-compatible is if you’ve provided a function to convert objects of type SmartPtr<CD> to objects of type SmartPtr<const CD>. If
you’ve got a compiler that supports member templates, you can use
the technique shown above for automatically generating the implicit
type conversion operators you need. (I remarked earlier that the technique worked anytime the corresponding conversion 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 go from const to non-const.
Furthermore, anything you can do with a const pointer you can do
with a non-const pointer, but with non-const pointers you can do
Smart Pointers
181B
other things, too (for example, assignment). Similarly, anything you
can do with a pointer-to-const is legal for a pointer-to-non-const, but
you can do some things (such as assignment) with pointers-to-nonconsts that you can’t do with pointers-to-consts.
These rules sound like the rules for public inheritance. You can convert from a derived class object to a base class object, but not vice
versa, and you can do anything to a derived class object you can do to
a base class object, but you can typically do additional things to a derived class object, as well. We can take advantage of this similarity
when implementing smart pointers by having each smart pointer-to-T
class publicly inherit from a corresponding smart pointer-to-const-T
class:
Smart
pointer-to-const-T
Smart
pointer-to-non-const-T
template<class T>
class SmartPtrToConst {
...
protected:
union {
const T* constPointee;
T* pointee;
};
};
template<class T>
class SmartPtr:
public SmartPtrToConst<T> {
...
};
// smart pointers to const
// objects
// the usual smart pointer
// member functions
// for SmartPtrToConst access
// for SmartPtr access
// smart pointers to
// non-const objects
// no data members
With this design, the smart pointer-to-non-const-T object needs to
contain a dumb pointer-to-non-const-T, and the smart pointer-toconst-T needs to contain a dumb pointer-to-const-T. The naive way
to handle this would be to put a dumb pointer-to-const-T in the base
182B
Item 28
class and a dumb pointer-to-non-const-T in the derived class. That
would be wasteful, however, because SmartPtr objects would contain
two dumb pointers: the one they inherited from 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 in C++ as it is in C. The union
is protected, so both classes have access to it, and it contains both of
the necessary dumb pointer types. SmartPtrToConst<T> objects use
the constPointee pointer, SmartPtr<T> objects use the pointee
pointer. We therefore get the advantages of two different pointers without having to allocate space for more than one. Such is the beauty of a
union. Of course, the member functions of the two classes must constrain themselves to using only the appropriate pointer, and you’ll get
no 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 they worth 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 smart pointers. Furthermore, as that example demonstrates, some uses of smart pointers are sufficiently limited
in scope that things like testing for nullness, conversion to dumb
pointers, inheritance-based conversions, and support for pointers-toconsts 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 dumb pointers.
Try as you may, you will never succeed in designing a general-purpose
smart pointer that can seamlessly replace its dumb pointer counterpart.
Smart pointers nevertheless make it possible to achieve effects in your
code that would otherwise be difficult to implement. Smart pointers
should be used judiciously, but every C++ programmer will find them
useful at one time or another.
Reference Counting
183B
Item 29: Reference counting.
Reference counting is a technique that allows multiple objects with the
same value to share a single representation of that value. There are
two common motivations for the technique. The first is to simplify the
bookkeeping surrounding heap objects. Once an object is allocated by
calling new, it’s crucial to keep track of who owns that object, because
the owner — and only the owner — is responsible for calling delete on
it. But ownership can be transferred from object to object as a program
runs (by passing pointers as parameters, for example), so keeping
track of an object’s ownership is hard work. Classes like auto_ptr (see
Item 9) can help with this task, but experience has shown that most
programs still fail to get it right. Reference counting eliminates the
burden of tracking object ownership, because when an object employs
reference counting, it owns itself. When nobody is using it any longer,
it destroys itself automatically. Thus, reference counting constitutes a
simple form of garbage collection.
Reference Counting
The second motivation for reference counting is simple common sense.
If many objects have the same value, it’s silly to store that value more
than once. Instead, it’s better to let all the objects with that value share
its representation. Doing so not only saves memory, it also leads to
faster-running programs, because there’s no need 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 us master basics. A good way to begin is by seeing how we
might come to have many objects with the same value in the first
place. Here’s one way:
class String {
public:
// the standard string type may
// 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 is represented depends on how the
184B
Item 29
String class is implemented, but a common implementation would
have each String object carry its own copy of the value. For example,
String’s assignment operator might be implemented like this:
String& String::operator=(const String& rhs)
{
if (this == &rhs) return *this;
delete [] data;
data = new char[strlen(rhs.data) + 1];
strcpy(data, rhs.data);
return *this;
}
Given this implementation, we can envision the five objects and their
values as follows:
a
Hello
b
Hello
c
Hello
d
Hello
e
Hello
The redundancy in this approach is clear. In an ideal world, we’d like
to change the picture to look like this:
a
b
c
Hello
d
e
Here only one copy of the value “Hello” is stored, and all the String
objects with that value share its representation.
In practice, it isn’t possible to achieve this ideal, because we need to
keep track of how many objects are sharing a value. If object a above
is assigned a different value from “Hello”, we can’t destroy the value
“Hello”, because four other objects still need it. On the other hand, if
only a single object had the value “Hello” and that object went out of
scope, no object would have that value and we’d have to destroy the
value to avoid a resource leak.
Reference Counting
185B
The need to store information on the number of objects currently sharing — referring to — a value means our ideal picture must be modified
somewhat to take into account the existence of a reference count:
a
b
c
5
Hello
d
e
(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 walk through 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 place cannot be in a
String object, because we need one reference count per string value,
not one reference count per string object. That implies a coupling between values and reference counts, so we’ll create a class to store reference counts and the values they track. We’ll call this class
StringValue, and because its only raison d’être is to help implement
the String class, we’ll nest it inside String’s private section. Furthermore, it will be convenient to give all the member functions of String
full access to the StringValue data structure, so we’ll declare
StringValue to be a struct. This is a trick worth knowing: nesting a
struct in the private part of a class is a convenient 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:
186B
Item 29
class String {
public:
...
private:
struct StringValue { ... };
StringValue *value;
};
// the usual String member
// functions go here
// holds a reference count
// and a string value
// value of this String
We could give this class a different name (RCString, perhaps) to emphasize that it’s implemented using reference 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 of the String interface supports exactly the same operations as a non-reference-counted
version, so why muddy the conceptual waters by embedding implementation decisions in the names of classes that correspond to abstract concepts? Why indeed? So we don’t.
Here’s StringValue:
class String {
private:
struct StringValue {
size_t 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 a reference-counted
string. For one thing, there’s neither a copy constructor nor an assign-
Reference Counting
187B
ment operator, and for another, there’s no manipulation of the refCount field. Worry not — the missing functionality will be provided by
the String class. The primary purpose of StringValue is to give us a
place to associate a particular value with a count of the number of
String objects sharing that value. StringValue gives us that, and
that’s enough.
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 a new StringValue object,
then we make the String object we’re constructing point to the newlyminted StringValue:
String::String(const char *initValue)
: value(new StringValue(initValue))
{}
For client code that looks like this,
String s("More Effective C++");
we end up with a data structure that looks like this:
s
1
More Effective C++
String objects constructed separately, but with the same initial value
do not share a data structure, so client code of this form,
String s1("More Effective C++");
String s2("More Effective C++");
yields this data structure:
s1
1
More Effective C++
s2
1
More Effective C++
It is possible to eliminate such duplication by having String (or
StringValue) keep track of existing StringValue objects and create
new ones only for truly unique strings, but such refinements on refer-
188B
Item 29
ence counting are somewhat off the beaten path. As a result, I’ll leave
them in the form of the feared and hated exercise for the reader.
The String copy constructor is not only unfeared and unhated, it’s
also efficient: the newly created String object shares 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:
s1
2
More Effective C++
s2
This is substantially more efficient than a conventional (non-referencecounted) String class, because there is no need to allocate memory
for the second copy of the string value, no need to deallocate that memory later, and no need to copy the value that would go in that memory.
Instead, we merely copy a pointer and increment a reference count.
The String destructor is also easy to implement, because most of the
time it doesn’t do anything. As long as the reference count for a
StringValue is non-zero, at least one String object is using the
value; it must therefore not be destroyed. Only when the String being
destructed is the sole user of the value — i.e., when the value’s reference count is 1 — should the String destructor destroy the
StringValue object:
class String {
public:
~String();
...
};
String::~String()
{
if (--value->refCount == 0) delete value;
}
Reference Counting
189B
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. Provided that different String objects do in fact sometimes have
the same values, the implementation above will sometimes 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 String assignment 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’s reference count should therefore be incremented during the assignment. Furthermore, the
StringValue object that s1 pointed to prior to the assignment should
have its reference count decremented, because s1 will no longer have
that value. If s1 was the only String with that value, the value should
be destroyed. In C++, all that looks like this:
String& String::operator=(const
{
if (value == rhs.value) {
return *this;
}
String& rhs)
// do nothing if the values
// are already the same; this
// subsumes the usual test of
// this against &rhs
if (--value->refCount == 0) { // destroy *this’s value if
delete value;
// no one else is using it
}
value = rhs.value;
++value->refCount;
return *this;
}
// have *this share rhs’s
// value
190B
Item 29
Copy-on-Write
To round out our examination of reference-counted strings, consider
an array-bracket operator ([]), which allows individual characters
within strings to be read and written:
class String {
public:
const char&
operator[](int index) const;
char& operator[](int index);
// for const Strings
// for non-const Strings
...
};
Implementation of the const version of this function is straightforward, because it’s a read-only operation; the value 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 a character, but it might be called
to write one, too:
String s;
...
cout << s[3];
s[5] = ’x’;
// this is a read
// 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 the const 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 that happen to be sharing the
same StringValue object. Unfortunately, there is no way for C++
compilers to tell us whether a particular use of operator[] is for a
read or a write, so we must be pessimistic and assume that all calls 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 StringValue to be modified by the
Reference Counting
191B
presumed write. In short, we must ensure that the reference count for
a String’s StringValue object is exactly one any time we return a reference to a character inside that StringValue object. Here’s how we
do it:
char& String:
Download