Preface 19
Goals........................................ 19
Chapters................................... 20
Exercises................................... 23
Exercise solutions............. 23
Source code............................... 23
Language standards................... 25
Language support............. 25
Seminars, CD-ROMs & consulting. 25
Errors....................................... 26
About the cover......................... 26
Acknowledgements..................... 26
Building Stable Systems 29
1: Exception handling 31
Traditional error handling............. 32
Throwing an exception................ 34
Catching an exception................. 36
The try block.................... 36
Exception handlers........... 36
Exception matching..................... 39
Catching any exception..... 42
Re-throwing an exception.. 42
Uncaught exceptions......... 43
Cleaning up................................ 45
Resource management..... 47
Making everything an object 49
auto_ptr.......................... 52
Function-level try blocks.... 53
Standard exceptions................... 55
Exception specifications............... 59
Better exception specifications? 64
Exception specifications and inheritance 65
When not to use exception specifications 66
Exception safety......................... 67
Programming with exceptions....... 72
When to avoid exceptions.. 72
Typical uses of exceptions. 74
Overhead................................... 78
Summary................................... 80
Exercises................................... 81
2: Defensive programming 83
Assertions................................. 86
A simple unit test framework........ 90
Automated testing............ 92
The TestSuite Framework.. 97
Test suites..................... 101
The test framework code. 102
Debugging techniques............... 110
Trace macros................. 110
Trace file....................... 111
Finding memory leaks..... 112
Summary................................. 119
Exercises................................. 120
The Standard C++ Library 125
3: Strings in depth 127
What’s in a string?.................... 128
Creating and initializing C++ strings 130
Operating on strings................. 133
Appending, inserting, and concatenating strings 134
Replacing string characters 136
Concatenation using nonmember overloaded operators 141
Searching in strings.................. 142
Finding in reverse........... 147
Finding first/last of a set of characters 148
Removing characters from strings 150
Comparing strings.......... 153
Strings and character traits 157
A string application................... 164
Summary................................. 170
Exercises................................. 170
4: Iostreams 171
Why iostreams?........................ 172
Iostreams to the rescue............ 177
Inserters and extractors.. 177
Common usage.............. 182
Line-oriented input.......... 185
Handling stream errors.............. 186
File iostreams........................... 190
A File-Processing Example 192
Open modes................... 194
Iostream buffering.................... 195
Seeking in iostreams................. 198
String iostreams....................... 202
Input string streams........ 203
Output string streams...... 205
Output stream formatting.......... 209
Format flags................... 209
Format fields.................. 211
Width, fill, and precision.. 213
An exhaustive example... 214
Manipulators............................. 218
Manipulators with arguments 219
Creating manipulators..... 223
Effectors........................ 224
Iostream examples.................... 227
Maintaining class library source code 227
Detecting compiler errors 232
A simple datalogger........ 235
Internationalization................... 240
Wide Streams................. 240
Locales.......................... 243
Summary................................. 246
Exercises................................. 246
5: Templates in depth 249
Template parameters................. 249
Non-type template parameters 250
Default template arguments 252
Template template parameters 254
The typename keyword... 261
Using the template keyword as a hint 264
Member Templates......... 266
Function template issues........... 269
Type deduction of function template arguments 269
Function template overloading 274
Taking the address of a generated function template 275
Applying a function to an STL sequence 279
Partial ordering of function templates 282
Template specialization............... 284
Explicit specialization....... 284
Partial Specialization....... 286
A practical example........ 289
Preventing template code bloat 293
Name lookup issues.................. 298
Names in templates........ 298
Templates and friends..... 304
Template programming idioms.... 311
Traits............................. 311
Policies.......................... 317
The curiously-recurring template pattern 321
Template metaprogramming....... 324
Compile-time programming 325
Expression templates...... 336
Template compilation models...... 343
The inclusion model........ 343
The separation model...... 347
Summary................................. 349
Exercises................................. 350
6: Generic algorithms 351
A first look............................... 351
Predicates...................... 355
Stream iterators............. 357
Algorithm complexity...... 360
Function objects....................... 361
Classification of function objects 363
Automatic creation of function objects 364
Adaptable function objects 368
More function object examples 370
Function pointer adapters 379
Writing your own function object adapters 386
A catalog of STL algorithms....... 390
Support tools for example creation 393
Filling and generating...... 397
Counting........................ 399
Manipulating sequences... 400
Searching and replacing.. 406
Comparing ranges.......... 415
Removing elements........ 419
Sorting and operations on sorted ranges 423
Heap operations............. 434
Applying an operation to each element in a range 436
Numeric algorithms......... 445
General utilities.............. 449
Creating your own STL-style algorithms 451
Summary................................. 453
Exercises................................. 453
7: Generic containers 461
Containers and iterators............ 461
STL reference documentation 463
A first look............................... 464
Containers of strings....... 470
Inheriting from STL containers 472
A plethora of iterators............... 475
Iterators in reversible containers 477
Iterator categories.......... 478
Predefined iterators........ 481
The basic sequences: vector, list, deque 487
Basic sequence operations 487
vector............................ 491
deque............................ 498
Converting between sequences 501
Checked random-access. 504
list................................. 505
Swapping basic sequences 512
set.......................................... 513
A completely reusable tokenizer 516
stack....................................... 522
queue...................................... 526
Priority queues......................... 531
Holding bits.............................. 541
bitset<n>....................... 542
vector<bool>................. 547
Associative containers............... 548
Generators and fillers for associative containers 554
The magic of maps......... 557
Multimaps and duplicate keys 559
Multisets........................ 563
Combining STL containers.......... 567
Cleaning up containers of pointers 570
Creating your own containers..... 572
STL extensions......................... 575
Non-STL containers.................. 577
Summary................................. 582
Exercises................................. 583
Special Topics 587
8: Runtime type identification 589
Runtime casts.......................... 589
The typeid operator.................. 595
Casting to intermediate levels 598
void pointers.................. 599
Using RTTI with templates 600
Multiple inheritance.................... 601
Sensible uses for RTTI............... 602
A trash recycler.............. 603
Mechanism and overhead of RTTI 608
Summary................................. 609
Exercises................................. 610
9: Multiple inheritance 611
Perspective............................... 611
Interface inheritance.................. 613
Implementation inheritance........ 617
Duplicate subobjects................. 624
Virtual base classes................... 629
Name lookup issues.................. 639
Avoiding MI.............................. 643
Extending an interface............... 644
Summary................................. 649
Exercises................................. 649
10: Design patterns 651
The pattern concept.................. 651
The singleton........................... 653
Variations on singleton.... 654
Classifying patterns................... 659
Features, idioms, patterns 660
Building complex objects........... 661
Factories: encapsulating object creation 669
Polymorphic factories...... 672
Abstract factories............ 675
Virtual constructors......... 678
Observer.................................. 686
The “inner class” idiom.... 689
The observer example.... 693
Multiple dispatching................... 697
Multiple dispatching with Visitor 701
Exercises................................. 704
11: Concurrency 706
Motivation................................ 707
Concurrency in C++.................. 708
Installing Zthreads.......... 709
Defining Tasks.......................... 711
Using Threads.......................... 713
Creating responsive user interfaces 715
Managing Tasks.............. 717
Simplifying with Executors 718
Yielding.......................... 721
Sleeping........................ 723
Priority.......................... 725
Sharing limited resources........... 727
Ensuring the existence of objects 728
Improperly accessing resources 731
Controlling access........... 735
Simplified coding with Guards 737
Thread local storage....... 741
Terminating tasks...................... 743
Preventing iostream collision 743
The Ornamental Garden.. 744
Terminating when blocked 750
Interruption.................... 751
Cooperation between threads..... 757
Wait and signal............... 758
Broadcast...................... 763
Producer-Consumer relationships 766
Solving Threading problems with Queues 770
Deadlock.................................. 775
Summary................................. 783
Exercises................................. 784
A: Recommended reading 789
C............................................ 789
General C++............................ 789
Bruce’s books................. 790
Depth & dark corners................ 790
The STL................................... 791
Design Patterns........................ 791
B: Etc 793
Index 801
In Volume 1 of this book, you learn 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.
Thus, in this volume we are assuming that you are familiar
with the material developed in Volume 1. Comment
Our goals in this book are to:Comment
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 have to 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, 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 also 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 (a good one!) affects
code design in fundamental ways. Comment
2. Defensive Programming. Many software problems can
be prevented. To program defensively is to craft code in such a way that bugs
can be found and fixed early before they have a chance to do damage in the
field. The use of assertions is the single most important thing you can do to
validate your code during development, while at the same time leaving an
executable documentation trail in your code that reveals what you were thinking
when you wrote the code in the first place. Before you let your code out of
your hands it should be rigorously tested. A framework for automated unit
testing is an indispensable tool for successful, everyday software development.
Part 2: The Standard C++ Library
3. Strings in Depth. Text processing is the most
common programming activity by far. 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 the ins and outs of how to make the
best use of the existing iostream library for standard I/O, file I/O, and
in-memory formatting.Comment
5. Templates in Depth. The distinguishing feature of
“modern C++” is the broad power of templates. Templates are for more than just
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 understatement 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 known to man in a type-safe manner. You
never have 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 by means of the simplest of designs.
Part 3: Special Topics
8. Run-time type identification. Run-time type
identification (RTTI) lets you find 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 very helpful to know the
exact type of an object for which you only have a base pointer; often this
information allows you to perform a special-case operation more efficiently.
This chapter explains what RTTI is for and how to use it. Comment
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 common problem. The use of multiple
inheritance to repair a “damaged” class interface is demonstrated as a
genuinely valuable use of this feature.Comment
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. Users have long been used
to 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. Multi-threaded programming
requires a different mindset, however, and comes with its own set of “gotchas.”
This chapter uses a freely available library (Eric Crahen’s ZThread library) to
show how to effectively manage multi-threaded applications in C++.
We have discovered that simple exercises are exceptionally
useful during a seminar to complete a student’s understanding, so you’ll find a
set at the end of each chapter.Comment
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).Comment
Solutions to exercises can be found in the electronic
document The C++ Annotated Solution Guide, Volume 2, available for a nominal
fee from www.MindView.net.
Comment
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.Comment
In the starting directory where you unpacked the code you
will find the following copyright notice:Comment
//:! :CopyRight.txt
Copyright (c) MindView,
Inc., 2003
Source code file from the
book
"Thinking in C++, 2nd
Edition, Volume 2."
All rights reserved EXCEPT
as allowed by the
following statements: You
can freely use this file
for your own work (personal
or commercial),
including modifications and
distribution in
executable form only.
Permission is granted to use
this file in classroom
situations, including its
use in presentation
materials, as long as the book
"Thinking in C++"
is cited as the source.
Except in classroom
situations, you cannot copy
and distribute this code;
instead, the sole
distribution point is
http://www.MindView.net
(and official mirror sites)
where it is
freely available. You cannot
remove this
copyright and notice. You
cannot distribute
modified versions of the
source code in this
package. You cannot use this
file in printed
media without the express
permission of the
author. The authors makes no
representation about
the suitability of this
software for any purpose.
It is provided "as
is" without express or implied
warranty of any kind,
including any implied
warranty of merchantability,
fitness for a
particular purpose or
non-infringement. The entire
risk as to the quality and
performance of the
software is with you. The
authors and publisher shall not be liable for any damages suffered by you or
any third party as a result of using or distributing software. In no event will
the authors or the publisher be liable for any
lost revenue, profit, or
data, or for direct,
indirect, special,
consequential, incidental, or
punitive damages, however
caused and regardless of
the theory of liability,
arising out of the use of
or inability to use
software, even if Bruce Eckel
and the publisher have been
advised of the
possibility of such damages.
Should the software
prove defective, you assume
the cost of all
necessary servicing, repair,
or correction. If you
think you've found an error,
please submit the
correction using the form
you will find at
www.MindView.net. (Please
use the same
form for non-code errors
found in the book.)
///:~
You may use the code in your projects and in the classroom
as long as the copyright notice is retained. Comment
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. Comment
As this book goes to press the ANSI/ISO C++ committee has 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++ you should assume we mean “Standard C++.” The C++
standards committee continues to address issues important to the C++ community that
will find expression in C++0x, a future C++ standard not likely to be available
for many years. Comment
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. On the Windows platform we have
validated all examples with the C++ compiler found in Microsoft’s Visual Studio
.NET 2003; Borland C++Builder version 6; the GNU projects g++ compiler, version
3.2, running under Cygwin; and the Edison Design Groups C++ front end using the
Dinkumware full C++ library. We have also run all the examples on MAC OS X with
Metrowerks C++ version 8. In those instances where a compiler does not support
the feature required by a sample program, we have so indicated in comments in
the source code. Comment
Seminars, CD-ROMs & consulting
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 can be found at http://www.MindView.net. Comment
No matter how many tricks a writer uses 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.
The feedback system uses unique identifiers on the paragraphs in the book, so
you should click on the identifier next to the paragraph that you wish to
comment on. Your help is appreciated. Comment
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, but they are
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 Eckel.
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 has been instrumental in 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.
Eric Crahen has been instrumental in the completion of
Chapter 11 (Concuurent Programming). 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 has
been extremely cooperative and has used our feedback to enhance his library,
while we have benefited from his insights as well.
We are grateful to have had Pete Becker as a 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 little notice.
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, Pete Becker, Kevlin Henney, David Abrahams,
Tom Plum, Reg Charney, Tom Penello, Sam Druker, and Uwe Steinmueller; people
who have spoken in the C++ track at the Software Development Conference (which
Bruce created and developed, and Chuck spoke in); and very often students in
seminars, who ask the questions we need to hear in order to make the material
clearer. Comment
The book design, 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 Georgia and the headlines are in Verdana. Comment
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 assistance 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 may 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.
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, Larry O’Brien, 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, Michael
Seaver, Huston Franklin, David Wagstaff, 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, Abbi, and even Sheba!
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. Comment
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. Comment
One of the major features in C++ is exception handling,
which is a better way of thinking about and handling errors. With exception
handling the following statements apply: Comment
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. Comment
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. 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 might need to write more sophisticated error-handling code. Comment
Error handling is quite straightforward in situations in which
you know exactly what to do because you have all the necessary information in
that context. Of course, you just handle the error at that point. Comment
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: Comment
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; also 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 very 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: Comment
//: C01:Nonlocal.cpp
// setjmp() & longjmp()
#include <iostream>
#include <csetjmp>
using namespace std;
class Rainbow {
public:
Rainbow() { cout <<
"Rainbow()" << endl; }
~Rainbow() { cout <<
"~Rainbow()" << endl; }
};
jmp_buf kansas;
void oz() {
Rainbow rb;
for(int i = 0; i < 3;
i++)
cout <<
"there's no place like home\n";
longjmp(kansas, 47);
}
int main() {
if(setjmp(kansas) == 0) {
cout <<
"tornado, witch, munchkins...\n";
oz();
} else {
cout <<
"Auntie Em! "
<< "I
had the strangest dream..."
<< endl;
}
} ///:~
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( )). Comment
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. 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. Comment
If you encounter an exceptional situation in your code—that
is, one in which 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: Comment
//: C01:MyError.cpp
class MyError {
const char* const data;
public:
MyError(const char* const
msg = 0) : data (msg) {}
};
void f() {
// Here we
"throw" an exception object:
throw
MyError("something bad happened");
}
int main() {
// As you’ll see shortly,
// we’ll want a "try
block" here:
f();
} ///:~
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. Comment
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 naïve way to think about exception handling is as an
alternate return mechanism (although you find you can get into trouble if you
take the 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. Comment
Any similarity to function returns 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.” Comment
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. Comment
As mentioned earlier, one of the advantages of C++ exception
handling is that it allows you to concentrate on the problem you’re actually
trying to solve in one place, and then deal with the errors from that code in
another place. Comment
The try block
If you’re inside a function and you throw an exception (or a
called function throws an exception), the function exits in the process of
throwing. 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: Comment
try {
// Code that may generate
exceptions
}
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 easier to read because the goal of the code is not confused with the error
checking. Comment
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. Exception handlers immediately follow
the try block and are denoted by the keyword catch: Comment
try {
// Code that may generate
exceptions
} catch(type1 id1) {
// Handle exceptions of
type1
} catch(type2 id2) {
// Handle exceptions of
type2
} catch(type3 id3)
// Etc...
} catch(typeN idN)
// Handle exceptions of
typeN
}
// Normal
execution resumes here...
The syntax of a catch clause resembles functions that
take a single argument. The identifier (id1, id2, and so on) can
be used inside the handler, just like a function argument, although you can
omit the identifier if it’s not needed in the handler. The exception type
usually gives you enough information to deal with it. Comment
The handlers must appear directly after the try
block. If an exception is thrown, the exception-handling mechanism goes hunting
for the first handler with an argument that matches the type of the exception.
It then enters that catch clause, and the exception is considered
handled. (The search for handlers stops once the catch clause is found.)
Only the matching catch clause executes; control then resumes after the
last handler associated with that try block. Comment
Notice that, within the try block, a number of
different function calls might generate the same type of exception, but you
need only one handler. Comment
To illustrate using try and catch, the
following variation of Nonlocal.cpp replaces the call to setjmp( )
with a try block and replaces the call to longjmp( ) with a throw
statement. Comment
//: C01:Nonlocal2.cpp
// Illustrates exceptions
#include <iostream>
using namespace std;
class Rainbow {
public:
Rainbow() { cout <<
"Rainbow()" << endl; }
~Rainbow() { cout <<
"~Rainbow()" << endl; }
};
void oz() {
Rainbow rb;
for(int i = 0; i < 3;
i++)
cout <<
"there's no place like home\n";
throw 47;
}
int main() {
try {
cout <<
"tornado, witch, munchkins...\n";
oz();
}
catch (int) {
cout <<
"Auntie Em! "
<< "I
had the strangest dream..."
<< endl;
}
} ///:~
When the throw statement in oz( ) executes,
program control backtracks until it finds the catch clause that takes an
int parameter, at which point execution resumes with the body of that catch
clause. The most important difference between this program and Nonlocal.cpp
is that the destructor for the object rb is called when the throw
statement causes execution to