“I’d like to congratulate the both of you for a very impressive work! Not only did I find your book to be an enjoyable and rewarding read … I was astounded by the accuracy both in terms of technical correctness and use of the language … I believe that you have attained a level of craftsmanship that is simply outstanding.”
Bjorn Karlsson
Editorial Board, C/C++ Users Journal
“This book is a tremendous achievement. You owe it to yourself to have a copy on your shelf.”
Al Stevens
Contributing Editor, Doctor Dobbs Journal
“Eckel’s book is the only one to so clearly explain how to rethink program construction for object orientation. That the book is also an excellent tutorial on the ins and outs of C++ is an added bonus.”
Andrew Binstock
Editor, Unix Review
“Bruce continues to amaze me with his insight into C++, and Thinking in C++ is his best collection of ideas yet. If you want clear answers to difficult questions about C++, buy this outstanding book.”
Gary Entsminger
Author, The Tao of Objects
“Thinking in C++ patiently and methodically explores the issues of when and how to use inlines, references, operator overloading, inheritance and dynamic objects, as well as advanced topics such as the proper use of templates, exceptions and multiple inheritance. The entire effort is woven in a fabric that includes Eckel’s own philosophy of object and program design. A must for every C++ developer’s bookshelf, Thinking in C++ is the one C++ book you must have if you’re doing serious development with C++.”
Richard Hale Shaw
Contributing Editor, PC Magazine


CIP DATA AVAILABLE
Vice President and Editorial Director, ECS: Marcia J. Horton
Publisher: Alan R. Apt
Associate Editor: Toni Dianne Holm
Editorial Assistant: Patrick Lindner
Vice President and Director of Production and Manufacturing, ESM: David W. Riccardi
Executive Managing Editor: Vince O’Brien
Managing Editor: Camille Trentacoste
Production Editor: Irwin Zucker
Director of Creative Services: Paul Belfanti
Creative Director: Carole Anson
Cover and Interior Designer: Daniel Will-Harris
Cover Illustrations: Tina Jensen
Manufacturing Manager: Trudy Pisciotti
Manufacturing Buyer: Lisa McDowell
Marketing Manager: Pamela Shaffer
©2004 MindView, Inc.
Published by Pearson Prentice Hall
Pearson Education, Inc.
Upper Saddle River, NJ 07458
All rights reserved. No part of this book may be reproduced in any form or by any means, without permission in writing from the publisher.
Pearson Prentice Hall® is a trademark of Pearson Education, Inc.
The authors and publisher of this book have used their best efforts in preparing this book. These efforts include the development, research, and testing of the theories and programs to determine their effectiveness. The authors and publisher make no warranty of any kind, expressed or implied, with regard to these programs or the documentation contained in this book. The authors and publisher shall not be liable in any event for incidental or consequential damages in connection with, or arising out of, the furnishing, performance, or use of these programs.
Printed in the United States of America
10 9 8 7 6 5 4 3 2 1
ISBN 0-13-035313-2
Pearson Education Ltd., London
Pearson Education Australia Pty. Ltd., Sydney
Pearson Education Singapore, Pte. Ltd.
Pearson Education North Asia Ltd., Hong Kong
Pearson Education Canada, Inc., Toronto
Pearson Educación de Mexico, S.A. de C.V.
Pearson Education-Japan, Tokyo
Pearson Education Malaysia, Pte. Ltd.
Pearson Education, Inc., Upper Saddle River, New Jersey
Dedication
To all those who have
worked tirelessly
to develop the C++ language
What’s inside…
Goals.......................................... 1
Chapters..................................... 2
Exercises..................................... 5
Exercise solutions............... 5
Source code................................. 5
Compilers.................................... 7
Language standards..................... 9
Seminars, CD–ROMs
& consulting................................. 9
Errors....................................... 10
About the cover......................... 10
Acknowledgements..................... 10
Traditional error handling............. 16
Throwing an exception................ 18
Catching an exception................. 20
The try block.................... 20
Exception handlers........... 20
Termination
and resumption................ 22
Exception matching..................... 23
Catching any exception..... 25
Uncaught exceptions......... 26
Cleaning up................................ 28
Making everything
an object......................... 32
auto_ptr.......................... 35
Function–level try blocks... 36
Standard exceptions................... 38
Exception specifications............... 40
Better exception
specifications?.................. 45
Exception specifications
and inheritance................ 46
When not to use
exception specifications..... 47
Exception safety......................... 48
Programming with exceptions....... 52
Typical uses of exceptions. 54
Overhead................................... 58
Summary................................... 60
Exercises................................... 61
Assertions................................. 66
A simple unit test framework........ 70
Automated testing............ 71
Test suites....................... 79
Debugging techniques................. 87
Trace macros................... 87
Trace file......................... 88
Finding memory leaks....... 90
Summary................................... 96
Exercises................................... 97
II: The Standard C++ Library 101
What’s in a string?.................... 104
Creating and initializing
C++ strings............................. 106
Operating on strings................. 109
Appending, inserting, and
concatenating strings...... 110
Replacing string
characters...................... 112
Concatenation using nonmember
overloaded operators...... 117
Searching in strings.................. 117
Finding in reverse........... 123
Finding first/last of
a set of characters.......... 124
Removing characters
from strings................... 126
Comparing strings.......... 129
Strings and
character traits............... 134
A string application................... 140
Summary................................. 145
Exercises................................. 146
Why iostreams?........................ 151
Iostreams to the rescue............ 156
Inserters and extractors.. 156
Common usage.............. 161
Line–oriented input......... 164
Handling stream errors.............. 165
File iostreams........................... 168
A File–Processing
Example........................ 169
Open modes................... 171
Iostream buffering.................... 173
Seeking in iostreams................. 175
String iostreams....................... 179
Input string streams........ 180
Output string streams...... 182
Output stream formatting.......... 186
Format flags................... 186
Format fields.................. 188
Width, fill, and precision.. 190
Manipulators............................. 194
Manipulators with
arguments..................... 196
Creating manipulators..... 199
Effectors........................ 201
Iostream examples.................... 203
Maintaining class library source code................... 204
A simple data logger....... 211
Internationalization................... 216
Wide Streams................. 216
Locales.......................... 218
Summary................................. 221
Exercises................................. 222
Template parameters................. 227
Non–type
template parameters....... 228
Default template
arguments..................... 230
Template template
parameters.................... 232
Using the template
keyword as a hint........... 240
Function template issues........... 245
Type deduction of function
template arguments........ 245
Function template
overloading.................... 249
Taking the address
of a generated
function template............ 251
Applying a function to an STL sequence......... 255
Partial ordering of
function templates.......... 259
Template specialization............... 260
Explicit specialization....... 261
Partial Specialization....... 263
A practical example........ 265
Preventing template
code bloat...................... 268
Name lookup issues.................. 273
Names in templates........ 273
Templates and friends..... 279
Template programming idioms.... 285
Traits............................. 285
Policies.......................... 291
The curiously recurring
template pattern............. 294
Template metaprogramming....... 297
Compile–time
programming................. 298
Expression templates...... 308
Template compilation models...... 315
The inclusion model........ 315
Explicit instantiation........ 316
The separation model...... 319
Summary................................. 320
Exercises................................. 321
A first look............................... 325
Predicates...................... 329
Stream iterators............. 331
Algorithm complexity...... 333
Function objects....................... 335
Classification of
function objects.............. 336
Automatic creation of
function objects.............. 338
Adaptable function objects 341
More function
object examples............. 343
Writing your own
function object adaptors.. 358
A catalog of STL algorithms....... 362
Support tools for
example creation............ 365
Filling and generating...... 368
Counting........................ 370
Comparing ranges.......... 385
Sorting and operations
on sorted ranges............ 393
Heap operations............. 403
Applying an operation to
each element in a range.. 405
Numeric algorithms......... 413
General utilities.............. 417
Creating your own
STL–style algorithms................. 419
Summary................................. 420
Exercises................................. 421
Containers and iterators............ 429
STL reference documentation................ 431
A first look............................... 432
Containers of strings....... 438
Inheriting from STL containers................ 440
A plethora of iterators............... 442
Iterators in reversible containers....... 445
Iterator categories.......... 446
Predefined iterators........ 448
The basic sequences:
vector, list, deque..................... 454
vector............................ 457
deque............................ 465
Converting between
sequences...................... 467
list................................. 471
set.......................................... 479
A completely
reusable tokenizer.......... 482
stack....................................... 487
queue...................................... 491
Priority queues......................... 496
Holding bits.............................. 506
bitset<n>....................... 507
vector<bool>................. 511
Associative containers............... 513
Generators and fillers
for associative containers 518
The magic of maps......... 521
Multimaps and
duplicate keys................ 523
Multisets........................ 527
Combining STL containers.......... 530
Cleaning up containers of pointers............... 534
Creating your own containers..... 536
STL extensions......................... 538
Non–STL containers.................. 540
Summary................................. 546
Exercises................................. 546
8: Runtime Type Identification 551
Runtime casts.......................... 551
The typeid operator.................. 557
Casting to intermediate levels......... 560
void pointers.................. 561
Using RTTI with templates................ 562
Multiple inheritance.................... 563
Sensible uses for RTTI............... 564
A trash recycler.............. 565
Mechanism and overhead of RTTI...................... 570
Summary................................. 570
Exercises................................. 571
Perspective............................... 573
Interface inheritance.................. 575
Implementation inheritance........ 579
Duplicate subobjects................. 585
Virtual base classes................... 589
Name lookup issues.................. 599
Avoiding MI.............................. 603
Extending an interface............... 603
Summary................................. 608
Exercises................................. 609
The pattern concept.................. 613
Prefer composition
to inheritance................. 615
Classifying patterns................... 615
Features, idioms, patterns......................... 616
Simplifying Idioms..................... 617
Messenger..................... 617
Collecting Parameter....... 618
Singleton................................. 619
Variations on Singleton.... 621
Command: choosing the operation........................... 626
Decoupling event handling
with Command............... 628
Object decoupling..................... 631
Proxy: fronting for
another object................ 632
State: changing object behavior.............. 634
Adapter................................... 636
Template Method....................... 639
Strategy: choosing the algorithm at runtime.................. 640
Chain of Responsibility: trying a sequence of strategies... 642
Factories: encapsulating object creation......................... 645
Polymorphic factories...... 647
Abstract factories............ 651
Virtual constructors......... 654
Builder: creating complex objects....................... 660
Observer.................................. 667
The “inner class” idiom.... 671
Multiple dispatching................... 679
Multiple dispatching with Visitor..................... 683
Summary................................. 687
Exercises................................. 688
Motivation................................ 692
Concurrency in C++.................. 694
Installing ZThreads......... 695
Defining Tasks.......................... 696
Using Threads.......................... 698
Creating responsive user interfaces............... 700
Simplifying with Executors....................... 702
Yielding.......................... 706
Sleeping........................ 707
Priority.......................... 709
Sharing limited resources........... 711
Ensuring the existence of objects........ 711
Improperly accessing resources....................... 715
Controlling access........... 719
Simplified coding with Guards.................... 721
Thread local storage....... 724
Terminating tasks...................... 727
Preventing iostream collision......................... 727
Terminating when blocked................. 733
Interruption.................... 735
Cooperation between threads..... 741
Wait and signal............... 742
Producer–consumer relationships................... 747
Solving threading problems with queues.................... 750
Broadcast...................... 757
Deadlock.................................. 764
Summary................................. 770
Exercises................................. 773
General C++............................ 777
Bruce’s books................. 777
Chuck’s books................ 779
In–depth C++.......................... 779
Design Patterns........................ 781
In Volume 1 of this book, you learned the fundamentals of C and C++. In this volume, we look at more advanced features, with an eye towards developing techniques and ideas that produce robust C++ programs.
We assume you are familiar with the material presented in Volume 1.
Our goals in this book are to:
1. Present the material a simple step at a time, so the reader can easily digest each concept before moving on.
2. Teach “practical programming” techniques that you can use on a day-to-day basis.
3. Give you what we think is important for you to understand about the language, rather than everything we know. We believe there is an “information importance hierarchy,” and there are some facts that 95% of programmers will never need to know, but that would just confuse people and add to their perception of the complexity of the language. To take an example from C, if you memorize the operator precedence table (we never did) you can write clever code. But if you must think about it, it will confuse the reader/maintainer of that code. So forget about precedence and use parentheses when things aren’t clear. This same attitude will be taken with some information in the C++ language, which is more important for compiler writers than for programmers.
4. Keep each section focused enough so the lecture time—and the time between exercise periods—is small. Not only does this keep the audience’ minds more active and involved during a hands-on seminar, but it gives the reader a greater sense of accomplishment.
5. We have endeavored not to use any particular vendor’s version of C++. We have tested the code on all the implementations we could (described later in this introduction), and when one implementation absolutely refused to work because it doesn’t conform to the C++ Standard, we’ve flagged that fact in the example (you’ll see the flags in the source code) to exclude it from the build process.
6. Automate the compiling and testing of the code in the book. We have discovered that code that isn’t compiled and tested is probably broken, so in this volume we’ve instrumented the examples with test code. In addition, the code that you can download from http://www.MindView.net has been extracted directly from the text of the book using programs that automatically create makefiles to compile and run the tests. This way we know that the code in the book is correct.
Here is a brief description of the chapters contained in this book:
Part 1: Building Stable Systems
1. Exception handling. Error handling has always been a problem in programming. Even if you dutifully return error information or set a flag, the function caller may simply ignore it. Exception handling is a primary feature in C++ that solves this problem by allowing you to “throw” an object out of your function when a critical error happens. You throw different types of objects for different errors, and the function caller “catches” these objects in separate error handling routines. If you throw an exception, it cannot be ignored, so you can guarantee that something will happen in response to your error. The decision to use exceptions affects code design in positive, fundamental ways.
2. Defensive Programming. Many software problems can be prevented. To program defensively is to craft code in such a way that bugs are found and fixed early before they can damage in the field. Using assertions is the single most important way to validate your code during development, while at the same time leaving an executable documentation trail in your code that reveals your thoughts while you wrote the code in the first place. Rigorously test your code before you let out of your hands. An automated unit testing framework is an indispensable tool for successful, everyday software development.
Part 2: The Standard C++ Library
3. Strings in Depth. The most common programming activity is text processing. The C++ string class relieves the programmer from memory management issues, while at the same time delivering a powerhouse of text processing capability. C++ also supports the use of wide characters and locales for internationalized applications.
4. Iostreams. One of the original C++ libraries—the one that provides the essential I/O facility—is called iostreams. Iostreams is intended to replace C’s stdio.h with an I/O library that is easier to use, more flexible, and extensible—you can adapt it to work with your new classes. This chapter teaches you how to make the best use of the existing iostream library for standard I/O, file I/O, and in-memory formatting.
5. Templates in Depth. The distinguishing feature of “modern C++” is the broad power of templates. Templates do more than just create generic containers. They support development of robust, generic, high-performance libraries. There is a lot to know about templates—they constitute, as it were, a sub-language within the C++ language, and give the programmer an impressive degree of control over the compilation process. It is not an overstatement to say that templates have revolutionized C++ programming.
6. Generic Algorithms. Algorithms are at the core of computing, and C++, through its template facility, supports an impressive entourage of powerful, efficient, and easy-to-use generic algorithms. The standard algorithms are also customizable through function objects. This chapter looks at every algorithm in the library. (Chapters 6 and 7 cover that portion of the Standard C++ library commonly known as the Standard Template Library, or STL.)
7. Generic Containers & Iterators. C++ supports all the common data structures in a type-safe manner. You never need to worry about what such a container holds. The homogeneity of its objects is guaranteed. Separating the traversing of a container from the container itself, another accomplishment of templates, is made possible through iterators. This ingenious arrangement allows a flexible application of algorithms to containers using the simplest of designs.
Part 3: Special Topics
8. Runtime type identification. Runtime type identification (RTTI) finds the exact type of an object when you only have a pointer or reference to the base type. Normally, you’ll want to intentionally ignore the exact type of an object and let the virtual function mechanism implement the correct behavior for that type. But occasionally (like when writing software tools such as debuggers) it is helpful to know the exact type of an object—with this information, you can often perform a special-case operation more efficiently. This chapter explains what RTTI is for and how to use it.
9. Multiple inheritance. This sounds simple at first: A new class is inherited from more than one existing class. However, you can end up with ambiguities and multiple copies of base-class objects. That problem is solved with virtual base classes, but the bigger issue remains: When do you use it? Multiple inheritance is only essential when you need to manipulate an object through more than one common base class. This chapter explains the syntax for multiple inheritance and shows alternative approaches—in particular, how templates solve one typical problem. Using multiple inheritance to repair a “damaged” class interface is demonstrated as a valuable use of this feature.
10. Design Patterns. The most revolutionary advance in programming since objects is the introduction of design patterns. A design pattern is a language-independent codification of a solution to a common programming problem, expressed in such a way that it can apply to many contexts. Patterns such as Singleton, Factory Method, and Visitor now find their way into daily discussions around the keyboard. This chapter shows how to implement and use some of the more useful design patterns in C++.
11. Concurrent Programming. People have come to expect responsive user interfaces that (seem to) process multiple tasks simultaneously. Modern operating systems allow processes to have multiple threads that share the process address space. Multithreaded programming requires a different mindset, however, and comes with its own set of difficulties. This chapter uses a freely available library (the ZThread library by Eric Crahen of IBM) to show how to effectively manage multithreaded applications in C++.
We have discovered that simple exercises are exceptionally useful during a seminar to complete a student’s understanding. You’ll find a set at the end of each chapter.
These are fairly simple, so they can be finished in a reasonable amount of time in a classroom situation while the instructor observes, making sure all the students are absorbing the material. Some exercises are a bit more challenging to keep advanced students entertained. They’re all designed to be solved in a short time and are only there to test and polish your knowledge rather than present major challenges (presumably, you’ll find those on your own—or more likely they’ll find you).
Solutions to exercises can be found in the electronic document The C++ Annotated Solution Guide, Volume 2, available for a nominal fee from http://www.MindView.net.
The source code for this book is copyrighted freeware, distributed via the web site http://www.MindView.net. The copyright prevents you from republishing the code in print media without permission.
In the starting directory where you unpack the code you will find the following copyright notice:
You may use the code in your projects and in the classroom as long as the copyright notice is retained.
Your compiler may not support all the features discussed in this book, especially if you don’t have the newest version of your compiler. Implementing a language like C++ is a Herculean task, and you can expect that the features will appear in pieces rather than all at once. But if you attempt one of the examples in the book and get a lot of errors from the compiler, it’s not necessarily a bug in the code or the compiler—it may simply not be implemented in your particular compiler yet.
We used a number of compilers to test the code in this book, in an attempt to ensure that our code conforms to the C++ Standard and will work with as many compilers as possible. Unfortunately, not all compilers conform to the C++ Standard, and so we have a way of excluding certain files from building with those compilers. These exclusions are reflected in the makefiles automatically created for the package of code for this book that you can download from www.MindView.net. You can see the exclusion tags embedded in the comments at the beginning of each listing, so you will know whether to expect a particular compiler to work on that code (in a few cases, the compiler will actually compile the code but the execution behavior is wrong, and we exclude those as well).
Here are the tags and the compilers that they exclude from the build:
· {-dmc} Walter Bright’s Digital Mars compiler for Windows, freely downloadable at www.DigitalMars.com. This compiler is very conformant and so you will see almost none of these tags throughout the book.
· {-g++} The free Gnu C++ 3.3.1, which comes pre-installed in most Linux packages and Macintosh OSX. It is also part of Cygwin for Windows (see below). It is available for most other platforms from gcc.gnu.org.
· {-msc} Microsoft Version 7 with Visual C++ .NET (only comes with Visual Studio .NET; not freely downloadable).
· {-bor} Borland C++ Version 6 (not the free download; this one is more up to date).
· {-edg} Edison Design Group (EDG) C++. This is the benchmark compiler for standards conformance. This tag occurs only because of library issues, and because we were using a complimentary copy of the EDG front end with a complimentary library implementation from Dinkumware, Ltd. No compile errors occurred because of the compiler alone.
· {-mwcc} Metrowerks Code Warrior for Macintosh OS X. Note that OS X comes with Gnu C++ pre-installed, as well.
If you download and unpack the code package for this book from www.MindView.net, you’ll find the makefiles to build the code for the above compilers. We used the freely-available GNU-make, which comes with Linux, Cygwin (a free Unix shell that runs on top of Windows; see www.Cygwin.com), or can be installed on your platform—see www.gnu.org/software/make. (Other makes may or may not work with these files, but are not supported.) Once you install make, if you type make at the command line you’ll get instructions on how to build the book’s code for the above compilers.
Note that the placement of these tags on the files in this book indicates the state of the particular version of the compiler at the time we tried it. It’s possible and likely that the compiler vendor has improved the compiler since the publication of this book. It’s also possible that while building the book with so many compilers, we may have misconfigured a particular compiler that would otherwise have compiled the code correctly. Thus, you should try the code yourself on your compiler, and also check the code downloaded from www.MindView.net to see what is current.
Throughout this book, when referring to conformance to the ANSI/ISO C standard, we will be referring to the 1989 standard, and will generally just say ‘C.’ Only if it is necessary to distinguish between Standard 1989 C and older, pre-Standard versions of C will we make the distinction. We do not reference C99 in this book.
The ANSI/ISO C++ Committee long ago finished working on the first C++ Standard, commonly known as C++98. We will use the term Standard C++ to refer to this standardized language. If we simply refer to C++, assume we mean “Standard C++.” The C++ Standards Committee continues to address issues important to the C++ community that will become C++0x, a future C++ Standard not likely to be available for many years.
Bruce Eckel’s company, MindView, Inc., provides public hands-on training seminars based on the material in this book, and also for advanced topics. Selected material from each chapter represents a lesson, which is followed by a monitored exercise period so each student receives personal attention. We also provide on-site training, consulting, mentoring, and design & code walkthroughs. Information and sign-up forms for upcoming seminars and other contact information is found at http://www.MindView.net.
No matter how many tricks writers use to detect errors, some always creep in and these often leap off the page for a fresh reader. If you discover anything you believe to be an error, please use the feedback system built into the electronic version of this book, which you will find at http://www.MindView.net. Your help is appreciated.
The cover artwork was painted by Larry O’Brien’s wife, Tina Jensen (yes, the Larry O’Brien who was the editor of Software Development Magazine for so many years). Not only are the pictures beautiful, they are also excellent suggestions of polymorphism. The idea for using these images came from Daniel Will-Harris, the cover designer (www.Will-Harris.com), working with Bruce.
Volume 2 of this book languished in a half-completed state for a long time while Bruce got distracted with other things, notably Java, Design Patterns and especially Python (see www.Python.org). If Chuck hadn’t been willing (foolishly, he has sometimes thought) to finish the other half and bring things up-to-date, this book almost certainly wouldn’t have happened. There aren’t that many people whom Bruce would have felt comfortable entrusting this book to. Chuck’s penchant for precision, correctness and clear explanation is what has made this book as good as it is.
Jamie King acted as an intern under Chuck’s direction during the completion of this book. He was an essential part of making sure the book got finished, not only by providing feedback for Chuck, but especially because of his relentless questioning and picking of every single possible nit that he didn’t completely understand. If your questions are answered by this book, it’s probably because Jamie asked them first. Jamie also enhanced a number of the sample programs and created many of the exercises at the end of each chapter. Scott Baker, another of Chuck’s interns funded by MindView, Inc., helped with the exercises for Chapter 3.
Eric Crahen of IBM was instrumental in the completion of Chapter 11 (Concurrency). When we were looking for a threads package, we sought out one that was intuitive and easy to use, while being sufficiently robust to do the job. With Eric we got that and then some—he was extremely cooperative and has used our feedback to enhance his library, while we have benefited from his insights as well.
We are grateful to Pete Becker for being our technical editor. Few people are as articulate and discriminating as Pete, not to mention as expert in C++ and software development in general. We also thank Bjorn Karlsson for his gracious and timely technical assistance as he reviewed the entire manuscript with short notice.
Walter Bright made Herculean efforts to make sure that his Digital Mars C++ compiler would compile the examples in this book. He makes the compiler available for free downloads at http://www.DigitalMars.com. Thanks, Walter!
The ideas and understanding in this book have come from many other sources, as well: friends like Andrea Provaglio, Dan Saks, Scott Meyers, Charles Petzold, and Michael Wilk; pioneers of the language like Bjarne Stroustrup, Andrew Koenig, and Rob Murray; members of the C++ Standards Committee like Nathan Myers (who was particularly helpful and generous with his insights), Herb Sutter, PJ Plauger, Kevlin Henney, David Abrahams, Tom Plum, Reg Charney, Tom Penello, Sam Druker, Uwe Steinmueller, John Spicer, Steve Adamczyk, and Daveed Vandevoorde; people who have spoken in the C++ track at the Software Development Conference (which Bruce created and developed, and Chuck spoke in); Colleagues of Chuck like Michael Seaver, Huston Franklin, David Wagstaff, and often students in seminars, who ask the questions we need to hear to make the material clearer.
The book design, typeface selection, cover design, and cover photo were created by Bruce’s friend Daniel Will-Harris, noted author and designer, who used to play with rub-on letters in junior high school while he awaited the invention of computers and desktop publishing. However, we produced the camera-ready pages ourselves, so the typesetting errors are ours. Microsoft® Word XP was used to write the book and to create camera-ready pages. The body typeface is Verdana and the headlines are in Verdana. The code type face is Courier New.
We also wish to thank the generous professionals at the Edison Design Group and Dinkumware, Ltd., for giving us complimentary copies of their compiler and library (respectively). Without their expert assistance, graciously given, some of the examples in this book could not have been tested. We also wish to thank Howard Hinnant and the folks at Metrowerks for a copy of their compiler, and Sandy Smith and the folks at SlickEdit for keeping Chuck supplied with a world-class editing environment for so many years. Greg Comeau also provided a copy of his successful EDG-based compiler, Comeau C++.
A special thanks to all our teachers, and all our students (who are our teachers as well).
Evan Cofsky (Evan@TheUnixMan.com) provided all sorts of assistance on the server as well as development of programs in his now-favorite language, Python. Sharlynn Cobaugh and Paula Steuer were instrumental assistants, preventing Bruce from being washed away in a flood of projects.
Bruce’s sweetie Dawn McGee provided much-appreciated inspiration and enthusiasm during this project. The supporting cast of friends includes, but is not limited to: Mark Western, Gen Kiyooka, Kraig Brockschmidt, Zack Urlocker, Andrew Binstock, Neil Rubenking, Steve Sinofsky, JD Hildebrandt, Brian McElhinney, Brinkley Barr, Bill Gates at Midnight Engineering Magazine, Larry Constantine & Lucy Lockwood, Tom Keffer, Greg Perry, Dan Putterman, Christi Westphal, Gene Wang, Dave Mayer, David Intersimone, Claire Sawyers, The Italians (Andrea Provaglio, Laura Fallai, Marco Cantu, Corrado, Ilsa and Christina Giustozzi), Chris & Laura Strand, The Almquists, Brad Jerbic, John Kruth & Marilyn Cvitanic, Holly Payne (yes, the famous novelist!), Mark Mabry, The Robbins Families, The Moelter Families (& the McMillans), The Wilks, Dave Stoner, Laurie Adams, The Cranstons, Larry Fogg, Mike & Karen Sequeira, Gary Entsminger & Allison Brody, Chester Andersen, Joe Lordi, Dave & Brenda Bartlett, The Rentschlers, The Sudeks, Lynn & Todd, and their families. And of course, Mom & Dad, Sandy, James & Natalie, Kim& Jared, Isaac, and Abbi.
Software engineers spend about as much time validating code as they do creating it. Quality is or should be the goal of every programmer, and one can go a long way towards that goal by eliminating problems before they happen. In addition, software systems should be robust enough to behave reasonably in the presence of unforeseen environmental problems.
Exceptions were introduced into C++ to support sophisticated error handling without cluttering code with an inordinate amount of error-handling logic. Chapter 1 shows how proper use of exceptions can make for well-behaved software, and also introduces the design principles that underlie exception-safe code. In Chapter 2 we cover unit testing and debugging techniques intended to maximize code quality long before it’s released. The use of assertions to express and enforce program invariants is a sure sign of an experienced software engineer. We also introduce a simple framework to support unit testing.
Improving error recovery is one of the most powerful ways you can increase the robustness of your code.
Unfortunately, it’s almost accepted practice to ignore error conditions, as if we’re in a state of denial about errors. One reason, no doubt, is the tediousness and code bloat of checking for many errors. For example, printf( ) returns the number of characters that were successfully printed, but virtually no one checks this value. The proliferation of code alone would be disgusting, not to mention the difficulty it would add in reading the code.
The problem with C’s approach to error handling could be thought of as coupling—the user of a function must tie the error-handling code so closely to that function that it becomes too ungainly and awkward to use.
One of the major features in C++ is exception handling, which is a better way of thinking about and handling errors. With exception handling:
1. Error-handling code is not nearly so tedious to write, and it doesn’t become mixed up with your “normal” code. You write the code you want to happen; later in a separate section you write the code to cope with the problems. If you make multiple calls to a function, you handle the errors from that function once, in one place.
2. Errors cannot be ignored. If a function needs to send an error message to the caller of that function, it “throws” an object representing that error out of the function. If the caller doesn’t “catch” the error and handle it, it goes to the next enclosing dynamic scope, and so on until the error is either caught or the program terminates because there was no handler to catch that type of exception.
This chapter examines C’s approach to error handling (such as it is), discusses why it did not work well for C, and explains why it won’t work at all for C++. This chapter also covers try, throw, and catch, the C++ keywords that support exception handling.
In most of the examples in these volumes, we use assert( ) as it was intended: for debugging during development with code that can be disabled with #define NDEBUG for the shipping product. Runtime error checking uses the require.h functions (assure( ) and require( )) developed in Chapter 9 in Volume 1 and repeated here in Appendix B. These functions are a convenient way to say, “There’s a problem here you’ll probably want to handle with some more sophisticated code, but you don’t need to be distracted by it in this example.” The require.h functions might be enough for small programs, but for complicated products you’ll want to write more sophisticated error-handling code.
Error handling is quite straightforward when you know exactly what to do, because you have all the necessary information in that context. You can just handle the error at that point.
The problem occurs when you don’t have enough information in that context, and you need to pass the error information into a different context where that information does exist. In C, you can handle this situation using three approaches:
1. Return error information from the function or, if the return value cannot be used this way, set a global error condition flag. (Standard C provides errno and perror( ) to support this.) As mentioned earlier, the programmer is likely to ignore the error information because tedious and obfuscating error checking must occur with each function call. In addition, returning from a function that hits an exceptional condition might not make sense.
2. Use the little-known Standard C library signal-handling system, implemented with the signal( ) function (to determine what happens when the event occurs) and raise( ) (to generate an event). Again, this approach involves high coupling because it requires the user of any library that generates signals to understand and install the appropriate signal-handling mechanism. In large projects the signal numbers from different libraries might clash.
3. Use the nonlocal goto functions in the Standard C library: setjmp( ) and longjmp( ). With setjmp( ) you save a known good state in the program, and if you get into trouble, longjmp( ) will restore that state. Again, there is high coupling between the place where the state is stored and the place where the error occurs.
When considering error-handling schemes with C++, there’s an additional critical problem: The C techniques of signals and setjmp( )/longjmp( ) do not call destructors, so objects aren’t properly cleaned up. (In fact, if longjmp( ) jumps past the end of a scope where destructors should be called, the behavior of the program is undefined.) This makes it virtually impossible to effectively recover from an exceptional condition because you’ll always leave objects behind that haven’t been cleaned up and that can no longer be accessed. The following example demonstrates this with setjmp/longjmp:
The setjmp( ) function is odd because if you call it directly, it stores all the relevant information about the current processor state (such as the contents of the instruction pointer and runtime stack pointer) in the jmp_buf and returns zero. In this case it behaves like an ordinary function. However, if you call longjmp( ) using the same jmp_buf, it’s as if you’re returning from setjmp( ) again—you pop right out the back end of the setjmp( ). This time, the value returned is the second argument to longjmp( ), so you can detect that you’re actually coming back from a longjmp( ). You can imagine that with many different jmp_bufs, you could pop around to many different places in the program. The difference between a local goto (with a label) and this nonlocal goto is that you can return to any pre-determined location higher up in the runtime stack with setjmp( )/longjmp( ) (wherever you’ve placed a call to setjmp( )).
The problem in C++ is that longjmp( ) doesn’t respect objects; in particular it doesn’t call destructors when it jumps out of a scope.[1] Destructor calls are essential, so this approach won’t work with C++. In fact, the C++ Standard states that branching into a scope with goto (effectively bypassing constructor calls), or branching out of a scope with longjmp( ) where an object on the stack has a destructor, constitutes undefined behavior.
If you encounter an exceptional situation in your code—that is, if you don’t have enough information in the current context to decide what to do—you can send information about the error into a larger context by creating an object that contains that information and “throwing” it out of your current context. This is called throwing an exception. Here’s what it looks like:
MyError is an ordinary class, which in this case takes a char* as a constructor argument. You can use any type when you throw (including built-in types), but usually you’ll create special classes for throwing exceptions.
The keyword throw causes a number of relatively magical things to happen. First, it creates a copy of the object you’re throwing and, in effect, “returns” it from the function containing the throw expression, even though that object type isn’t normally what the function is designed to return. A naive way to think about exception handling is as an alternate return mechanism (although you’ll find you can get into trouble if you take that analogy too far). You can also exit from ordinary scopes by throwing an exception. In any case, a value is returned, and the function or scope exits.
Any similarity to a return statement ends there because where you return is some place completely different from where a normal function call returns. (You end up in an appropriate part of the code—called an exception handler—that might be far removed from where the exception was thrown.) In addition, any local objects created by the time the exception occurs are destroyed. This automatic cleanup of local objects is often called “stack unwinding.”
In addition, you can throw as many different types of objects as you want. Typically, you’ll throw a different type for each category of error. The idea is to store the information in the object and in the name of its class so that someone in a calling context can figure out what to do with your exception.
As mentioned earlier, one of the advantages of C++ exception handling is that you can concentrate on the problem you’re trying to solve in one place, and then deal with the errors from that code in another place.
If you’re inside a function and you throw an exception (or a called function throws an exception), the function exits because of the thrown exception. If you don’t want a throw to leave a function, you can set up a special block within the function where you try to solve your actual programming problem (and potentially generate exceptions). This block is called the try block because you try your various function calls there. The try block is an ordinary scope, preceded by the keyword try:
If you check for errors by carefully examining the return codes from the functions you use, you need to surround every function call with setup and test code, even if you call the same function several times. With exception handling, you put everything in a try block and handle exceptions after the try block. Thus, your code is a lot easier to write and to read because the goal of the code is not confused with the error handling.
Of course, the thrown exception must end up some place. This place is the exception handler, and you need one exception handler for every exception type you want to catch. However, polymorphism also