[17] Exceptions and error handling  Updated! 
(Part of C++ FAQ, Copyright © 1991-2011, Marshall Cline, cline@parashift.com)


FAQs in section [17]:


[17.1] What are some ways try / catch / throw can improve software quality?

By eliminating one of the reasons for if statements.

The commonly used alternative to try / catch / throw is to return a return code (sometimes called an error code) that the caller explicitly tests via some conditional statement such as if. For example, printf(), scanf() and malloc() work this way: the caller is supposed to test the return value to see if the function succeeded.

Although the return code technique is sometimes the most appropriate error handling technique, there are some nasty side effects to adding unnecessary if statements:

So compared to error reporting via return-codes and if, using try / catch / throw is likely to result in code that has fewer bugs, is less expensive to develop, and has faster time-to-market. Of course if your organization doesn't have any experiential knowledge of try / catch / throw, you might want to use it on a toy project first just to make sure you know what you're doing — you should always get used to a weapon on the firing range before you bring it to the front lines of a shooting war.

TopBottomPrevious sectionNext sectionSearch the FAQ ]


[17.2] I'm still not convinced: a 4-line code snippet shows that return-codes aren't any worse than exceptions; why should I therefore use exceptions on an application that is orders of magnitude larger?

Because exceptions scale better than return-codes.

The problem with a 4-line example is that it has only 4 lines. Give it 4,000 lines and you'll see the difference.

Here's a classic 4-line example, first with exceptions:

 try {
   f();
   
...
 } catch (std::exception& e) {
   
...code that handles the error...
 }

Here's the same example, this time using return-codes (rc stands for "return code"):

 int rc = f();
 if (rc == 0) {
   
...
 } else {
   
...code that handles the error...
 }

People point to those "toy" examples and say, "Exceptions don't improve coding or testing or maintenance cost in that; why should I therefore use them in a 'real' project?"

Reason: exceptions help you with real-world applications. You won't likely see much if any benefit on a toy example.

In the real world, the code that detects a problem must typically propagate error information back to a different function that will handle the problem. This "error propagation" often needs to go through dozens of functions — f1() calls f2() calls f3(), etc., and a problem is discovered way down in f10() (or f100()). The information about the problem needs to get propagated all the way back to f1(), because only f1() has enough context to actually know what should be done about the problem. In an interactive app, f1() is typically up near the main event loop, but no matter what, the code that detects the problem often isn't the same as the code that handles the problem, and the error information needs to get propagated through all the stack frames in between.

Exceptions make it easy to do this "error propagation":

 void f1() 
 {
   try {
     ... 
     f2(); 
     ... 
   } catch (some_exception& e) {
     
...code that handles the error...
   }
 }
 
 void f2() { ...; f3(); ...; }
 void f3() { ...; f4(); ...; }
 void f4() { ...; f5(); ...; }
 void f5() { ...; f6(); ...; }
 void f6() { ...; f7(); ...; }
 void f7() { ...; f8(); ...; }
 void f8() { ...; f9(); ...; }
 void f9() { ...; f10(); ...; }
 
 void f10()
 {
   
...
   if (...some error condition...)
     throw some_exception();
   
...
 }

Only the code that detects the error, f10(), and the code that handles the error, f1(), have any clutter.

However using return-codes forces "error propagation clutter" into all the functions in between those two. Here is the equivalent code that uses return codes:

 int f1()
 {
   
...
   int rc = f2();
   if (rc == 0) {
     
...
   } else {
     
...code that handles the error...
   }
 }
 
 int f2()
 {
   
...
   int rc = f3();
   if (rc != 0)
     return rc;
   
...
   return 0;
 }
 
 int f3()
 {
   
...
   int rc = f4();
   if (rc != 0)
     return rc;
   
...
   return 0;
 }
 
 int f4()
 {
   
...
   int rc = f5();
   if (rc != 0)
     return rc;
   
...
   return 0;
 }
 
 int f5()
 {
   
...
   int rc = f6();
   if (rc != 0)
     return rc;
   
...
   return 0;
 }
 
 int f6()
 {
   
...
   int rc = f7();
   if (rc != 0)
     return rc;
   
...
   return 0;
 }
 
 int f7()
 {
   
...
   int rc = f8();
   if (rc != 0)
     return rc;
   
...
   return 0;
 }
 
 int f8()
 {
   
...
   int rc = f9();
   if (rc != 0)
     return rc;
   
...
   return 0;
 }
 
 int f9()
 {
   
...
   int rc = f10();
   if (rc != 0)
     return rc;
   
...
   return 0;
 }
 
 int f10()
 {
   
...
   if (...some error condition...)
     return some_nonzero_error_code;
   
...
   return 0;
 }

The return-code solution "spreads out" the error logic. Functions f2() through f9() have explicit, hand-written code related to propagating the error condition back up to f1(). That is badness:

If you look very narrowly at f1() and f10() in the above examples, exceptions won't give you much of an improvement. But if you instead open your eyes to the big picture, you will see a substantial difference in all the functions in between.

Conclusion: one of the benefits of exception handling is a cleaner, simpler way to propagate error information back to the caller that can handle the error. Another benefit is your function doesn't need extra machinery to propagate both the "successful" and "unsuccessful" cases back to the caller. Toy examples often don't emphasize either error propagation or handling of the two-return-types problem, therefore they don't represent Real World code.

TopBottomPrevious sectionNext sectionSearch the FAQ ]


[17.3] How do exceptions simplify my function return type and parameter types?

When you use return codes, you often need two or more distinct return values: one to indicate that the function succeeded and to give the computed result, and another to propagate the error information back to the caller. If there are, say, 5 ways the function could fail, you could need as many as 6 different return values: the "successful computation" return value, and a possibly different package of bits for each of the 5 error cases.

Let's simplify it down to two cases:

Let's work a simple example: we would like to create a Number class that supports the four arithmetic operations: add, subtract, multiply and divide. This is an obvious place for overloaded operators, so let's define them:

 class Number {
 public:
   friend Number operator+ (Number const& x, Number const& y);
   friend Number operator- (Number const& x, Number const& y);
   friend Number operator* (Number const& x, Number const& y);
   friend Number operator/ (Number const& x, Number const& y);
   
...
 };

It's very easy to use:

 void f(Number x, Number y)
 {
   
...
   Number sum  = x + y;
   Number diff = x - y;
   Number prod = x * y;
   Number quot = x / y;
   
...
 }

But then we have a problem: handling errors. Adding numbers could cause overflow, dividing could cause divide-by-zero or underflow, etc. Whoops. How can we report both the "I succeeded and the result is xxx" as well as "I failed and the error information is yyy"?

If we use exceptions, it's easy. Think of exceptions as a separate return type that gets used only when needed. So we just define all the exceptions and throw them when needed:

 void f(Number x, Number y)
 {
   try {
     
...
     Number sum  = x + y;
     Number diff = x - y;
     Number prod = x * y;
     Number quot = x / y;
     
...
   }
   catch (Number::Overflow& exception) {
     
...code that handles overflow...
   }
   catch (Number::Underflow& exception) {
     
...code that handles underflow...
   }
   catch (Number::DivideByZero& exception) {
     
...code that handles divide-by-zero...
   }
 }

But if we use return codes instead of exceptions, life gets hard and messy. When you can't shove both the "good" number and the error information (including details about what went wrong) inside the Number object, you will probably end up using extra by-reference parameters to handle one of the two cases: either "I succeeded" or "I failed" or both. Without loss of generality, I will handle the computed result via a normal return value and the "I failed" case via a by-reference parameter, but you can just as easily do the opposite. Here's the result:

 class Number {
 public:
   enum ReturnCode {
     Success,
     Overflow,
     Underflow,
     DivideByZero
   };
 
   Number add(Number const& y, ReturnCode& rc) const;
   Number sub(Number const& y, ReturnCode& rc) const;
   Number mul(Number const& y, ReturnCode& rc) const;
   Number div(Number const& y, ReturnCode& rc) const;
   
...
 };

Now here's how to use it — this code is equivalent to the above:

 int f(Number x, Number y)
 {
   
...
 
   Number::ReturnCode rc;
   Number sum = x.add(y, rc);
   if (rc == Number::Overflow) {
     
...code that handles overflow...
     return -1;
   } else if (rc == Number::Underflow) {
     
...code that handles underflow...
     return -1;
   } else if (rc == Number::DivideByZero) {
     
...code that handles divide-by-zero...
     return -1;
   }
 
   Number diff = x.sub(y, rc);
   if (rc == Number::Overflow) {
     
...code that handles overflow...
     return -1;
   } else if (rc == Number::Underflow) {
     
...code that handles underflow...
     return -1;
   } else if (rc == Number::DivideByZero) {
     
...code that handles divide-by-zero...
     return -1;
   }
 
   Number prod = x.mul(y, rc);
   if (rc == Number::Overflow) {
     
...code that handles overflow...
     return -1;
   } else if (rc == Number::Underflow) {
     
...code that handles underflow...
     return -1;
   } else if (rc == Number::DivideByZero) {
     
...code that handles divide-by-zero...
     return -1;
   }
 
   Number quot = x.div(y, rc);
   if (rc == Number::Overflow) {
     
...code that handles overflow...
     return -1;
   } else if (rc == Number::Underflow) {
     
...code that handles underflow...
     return -1;
   } else if (rc == Number::DivideByZero) {
     
...code that handles divide-by-zero...
     return -1;
   }
 
   
...
 }

The point of this is that you normally have to muck up the interface of functions that use return codes, particularly if there is more error information to propagate back to the caller. For example, if there are 5 error conditions and the "error information" requires different data structures, you might end up with a fairly messy function interface.

None of this clutter happens with exceptions. Exceptions can be thought of as a separate return value, as if the function automatically "grows" new return types and return values based on what the function can throw.

Note: Please don't write me saying that you propose using return codes and storing the error information in a global or static variable, such as Number::lastError(). That isn't thread-safe. Even if you don't have multiple threads today, you rarely want to permanently prevent anyone in the future from using your class with multiple threads. Certainly if you do, you should write lots and lots of BIG UGLY COMMENTS warning future programmers that your code is not thread-safe, and that it probably can't be made thread-safe without a substantial rewrite.

TopBottomPrevious sectionNext sectionSearch the FAQ ]


[17.4] What does it mean that exceptions separate the "good path" (or "happy path") from the "bad path"?

It's another benefit of exceptions over return-codes.

The "good path" (sometimes called the "happy path") is the control-flow path that happens when everything goes well — when there are no problems.

The "bad path" (or "error path") is the path that control-flow takes when something goes wrong — when there is a problem.

Exceptions, when done right, separate the happy path from the error path.

Here is a simple example: function f() is suppoesd to call functions g(), h(), i() and j(), in sequence, as shown below. If any of those fail with a "foo" or "bar" error, f() is to handle the error immediately then return successfully. If any other error occurs, f() is to propagate the error information back to the caller.

Here is the code if exceptions are used:

 void f()   using exceptions
 {
   try {
     GResult gg = g();
     HResult hh = h();
     IResult ii = i();
     JResult jj = j();
     
...
   }
   catch (FooError& e) {
     
...code that handles "foo" errors...
   }
   catch (BarError& e) {
     
...code that handles "bar" errors...
   }
 }

The "good" path and the "bad" path are cleanly separated. The "good" (or "happy") path is the body of the try block — you can read that linearly, and if there are no errors, control flows in a simplistic path through those lines. The "bad" path is the body of the catch block and the body of any matching catch blocks in any caller.

Using return codes instead of exception clutters this to the point where it is difficult to see the relatively simple algorithm. The "good" ("happy") and "bad" paths are hopelessly intermixed:

 int f()   using return-codes
 {
   int rc;  
//"rc" stands for "return code"
 
   GResult gg = g(rc);
   if (rc == FooError) {
     
...code that handles "foo" errors...
   } else if (rc == BarError) {
     
...code that handles "bar" errors...
   } else if (rc != Success) {
     return rc;
   }
 
   HResult hh = h(rc);
   if (rc == FooError) {
     
...code that handles "foo" errors...
   } else if (rc == BarError) {
     
...code that handles "bar" errors...
   } else if (rc != Success) {
     return rc;
   }
 
   IResult ii = i(rc);
   if (rc == FooError) {
     
...code that handles "foo" errors...
   } else if (rc == BarError) {
     
...code that handles "bar" errors...
   } else if (rc != Success) {
     return rc;
   }
 
   JResult jj = j(rc);
   if (rc == FooError) {
     
...code that handles "foo" errors...
   } else if (rc == BarError) {
     
...code that handles "bar" errors...
   } else if (rc != Success) {
     return rc;
   }
 
   
...
 
   return Success;
 }

By intermixing the good/happy path with the bad/error path, it's harder to see what the code is supposed to do. Contrast that with the version that used exceptions, which is almost self-documenting — the basic functionality is very obvious.

TopBottomPrevious sectionNext sectionSearch the FAQ ]


[17.5] Okay, so you're saying exception handling is easy and simple, right?  Updated! 

[Recently minor wordsmithing/grammatical improvement (in 5/11). Click here to go to the next FAQ in the "chain" of recent changes.]

Wrong:

Fortunately there is plenty of wisdom and insight on the proper use of exceptions. Exception handling is not new. The industry as a whole has seen many millions of lines of code and many person-centuries of effort using exceptions. The jury has returned its verdict: exceptions can be used properly, and when they are used properly, they improve code.

Learn how.

TopBottomPrevious sectionNext sectionSearch the FAQ ]


[17.6] Exception handling seems to make my life more difficult; clearly I'm not the problem, am I??

Absolutely you might be the problem!

The C++ exception handling mechanism can be powerful and useful, but if you use it with the wrong mindset, the result can be a mess. If you're getting bad results, for instance, if your code seems unnecessarily convoluted or overly cluttered with try blocks, you might be suffering from a "wrong mindset." This FAQ gives you a list of some of those wrong mindsets.

Warning: do not be simplistic about these "wrong mindsets." They are guidelines and ways of thinking, not hard and fast rules. Sometimes you will do the exact opposite of what they recommend — do not write me about some situation that is an exception (no pun intended) to one or more of them — I guarantee that there are exceptions. That's not the point.

Here are some "wrong exception-handling mindsets" in no apparent order:

There are other "wrong exception-handling mindsets," but hopefully those will help you out. And remember: don't take those as hard and fast rules. They are guidelines, and there are exceptions to each.

TopBottomPrevious sectionNext sectionSearch the FAQ ]


[17.7] I have too many try blocks; what can I do about it?

You might have the mindset of return codes even though you are using the syntax of try/catch/throw. For instance, you might put a try block around just about every call:

 void myCode()
 {
   try {
     foo();
   }
   catch (FooException& e) {
     
...
   }
 
   try {
     bar();
   }
   catch (BarException& e) {
     
...
   }
 
   try {
     baz();
   }
   catch (BazException& e) {
     
...
   }
 }

Although this uses the try/catch/throw syntax, the overall structure is very similar to the way things are done with return codes, and the consequent software development/test/maintenance costs are basically the same as they were for return codes. In other words, this approach doesn't buy you much over using return codes. In general, it is bad form.

One way out is to ask yourself this question for each try block: "Why am I using a try block here?" There are several possible answers:

Main point is to ask "Why?". If you discover the reason you're doing it, you might find that there are better ways to achieve your goal.

Having said all this, there are, unfortunately, some people who have the return-code-mindset burned so deeply into their psyche that they just can't seem to see any alternatives. If that is you, there is still hope: get a mentor. If you see it done right, you'll probably get it. Style is sometimes caught, not just taught.

TopBottomPrevious sectionNext sectionSearch the FAQ ]


[17.8] How can I handle a constructor that fails?

Throw an exception.

Constructors don't have a return type, so it's not possible to use return codes. The best way to signal constructor failure is therefore to throw an exception. If you don't have the option of using exceptions, the "least bad" work-around is to put the object into a "zombie" state by setting an internal status bit so the object acts sort of like it's dead even though it is technically still alive.

The idea of a "zombie" object has a lot of down-side. You need to add a query ("inspector") member function to check this "zombie" bit so users of your class can find out if their object is truly alive, or if it's a zombie (i.e., a "living dead" object), and just about every place you construct one of your objects (including within a larger object or an array of objects) you need to check that status flag via an if statement. You'll also want to add an if to your other member functions: if the object is a zombie, do a no-op or perhaps something more obnoxious.

In practice the "zombie" thing gets pretty ugly. Certainly you should prefer exceptions over zombie objects, but if you do not have the option of using exceptions, zombie objects might be the "least bad" alternative.

Note: if a constructor finishes by throwing an exception, the memory associated with the object itself is cleaned up — there is no memory leak. For example:

 void f()
 {
   X x;             
 if X::X() throws, the memory for x itself will not leak
   Y* p = new Y();  
 if Y::Y() throws, the memory for *p itself will not leak
 }

There is some fine print on this topic, so you need to keep reading. Specifically you need to know how to prevent memory leaks if the constructor itself allocates memory, and you also need to be aware of what happens if you use "placement" new rather than the ordinary new used in the sample code above.

TopBottomPrevious sectionNext sectionSearch the FAQ ]


[17.9] How can I handle a destructor that fails?

Write a message to a log-file. Or call Aunt Tilda. But do not throw an exception!

Here's why (buckle your seat-belts):

The C++ rule is that you must never throw an exception from a destructor that is being called during the "stack unwinding" process of another exception. For example, if someone says throw Foo(), the stack will be unwound so all the stack frames between the throw Foo() and the } catch (Foo e) { will get popped. This is called stack unwinding.

During stack unwinding, all the local objects in all those stack frames are destructed. If one of those destructors throws an exception (say it throws a Bar object), the C++ runtime system is in a no-win situation: should it ignore the Bar and end up in the } catch (Foo e) { where it was originally headed? Should it ignore the Foo and look for a } catch (Bar e) { handler? There is no good answer — either choice loses information.

So the C++ language guarantees that it will call terminate() at this point, and terminate() kills the process. Bang you're dead.

The easy way to prevent this is never throw an exception from a destructor. But if you really want to be clever, you can say never throw an exception from a destructor while processing another exception. But in this second case, you're in a difficult situation: the destructor itself needs code to handle both throwing an exception and doing "something else", and the caller has no guarantees as to what might happen when the destructor detects an error (it might throw an exception, it might do "something else"). So the whole solution is harder to write. So the easy thing to do is always do "something else". That is, never throw an exception from a destructor.

Of course the word never should be "in quotes" since there is always some situation somewhere where the rule won't hold. But certainly at least 99% of the time this is a good rule of thumb.

TopBottomPrevious sectionNext sectionSearch the FAQ ]


[17.10] How should I handle resources if my constructors may throw exceptions?

Every data member inside your object should clean up its own mess.

If a constructor throws an exception, the object's destructor is not run. If your object has already done something that needs to be undone (such as allocating some memory, opening a file, or locking a semaphore), this "stuff that needs to be undone" must be remembered by a data member inside the object.

For example, rather than allocating memory into a raw Fred* data member, put the allocated memory into a "smart pointer" member object, and the destructor of this smart pointer will delete the Fred object when the smart pointer dies. The template std::auto_ptr is an example of such as "smart pointer." You can also write your own reference counting smart pointer. You can also use smart pointers to "point" to disk records or objects on other machines.

By the way, if you think your Fred class is going to be allocated into a smart pointer, be nice to your users and create a typedef within your Fred class:

 #include <memory>
 
 class Fred {
 public:
   typedef std::auto_ptr<Fred> Ptr;
   
...
 };

That typedef simplifies the syntax of all the code that uses your objects: your users can say Fred::Ptr instead of std::auto_ptr<Fred>:

 #include "Fred.h"
 
 void f(std::auto_ptr<Fred> p);  
// explicit but verbose
 void f(Fred::Ptr           p);  
// simpler
 
 void g()
 {
   std::auto_ptr<Fred> p1( new Fred() );  
// explicit but verbose
   Fred::Ptr           p2( new Fred() );  
// simpler
   
...
 }

TopBottomPrevious sectionNext sectionSearch the FAQ ]


[17.11] How do I change the string-length of an array of char to prevent memory leaks even if/when someone throws an exception?

If what you really want to do is work with strings, don't use an array of char in the first place, since arrays are evil. Instead use an object of some string-like class.

For example, suppose you want to get a copy of a string, fiddle with the copy, then append another string to the end of the fiddled copy. The array-of-char approach would look something like this:

 void userCode(char const* s1, char const* s2)
 {
   char* copy = new char[strlen(s1) + 1];    
// make a copy
   strcpy(copy, s1);                         
//   of s1...
 
   
// use a try block to prevent memory leaks if we get an exception
   
// note: we need the try block because we used a "dumb" char* above
   try {
 
     
...code that fiddles with copy...
 
     char* copy2 = new char[strlen(copy) + strlen(s2) + 1];  
// append s2
     strcpy(copy2, copy);                                    
//   onto the
     strcpy(copy2 + strlen(copy), s2);                       
//   end of
     delete[] copy;                                          
//   copy...
     copy = copy2;
 
     
...code that fiddles with copy again...
 
   }
   catch (...) {
     delete[] copy;   
// we got an exception; prevent a memory leak
     throw;           
// re-throw the current exception
   }
 
   delete[] copy;     
// we did not get an exception; prevent a memory leak
 }

Using char*s like this is tedious and error prone. Why not just use an object of some string class? Your compiler probably supplies a string-like class, and it's probably just as fast and certainly it's a lot simpler and safer than the char* code that you would have to write yourself. For example, if you're using the std::string class from the standardization committee, your code might look something like this:

 #include <string>           // Let the compiler see std::string
 
 void userCode(std::string const& s1, std::string const& s2)
 {
   std::string copy = s1;    
// make a copy of s1
   
...code that fiddles with copy...
   copy += s2;               
// append s2 onto the end of copy
   
...code that fiddles with copy again...
 }

The char* version requires you to write around three times more code than you would have to write with the std::string version. Most of the savings came from std::string's automatic memory management: in the std::string version, we didn't need to write any code...

TopBottomPrevious sectionNext sectionSearch the FAQ ]


[17.12] What should I throw?

C++, unlike just about every other language with exceptions, is very accomodating when it comes to what you can throw. In fact, you can throw anything you like. That begs the question then, what should you throw?

Generally, it's best to throw objects, not built-ins. If possible, you should throw instances of classes that derive (ultimately) from the std::exception class. By making your exception class inherit (ultimately) from the standard exception base-class, you are making life easier for your users (they have the option of catching most things via std::exception), plus you are probably providing them with more information (such as the fact that your particular exception might be a refinement of std::runtime_error or whatever).

The most common practice is to throw a temporary:

 #include <stdexcept>
 
 class MyException : public std::runtime_error {
 public:
   MyException() : std::runtime_error("MyException") { }
 };
 
 void f()
 {
    
// ...
    throw MyException();
 }

Here, a temporary of type MyException is created and thrown. Class MyException inherits from class std::runtime_error which (ultimately) inherits from class std::exception.

TopBottomPrevious sectionNext sectionSearch the FAQ ]


[17.13] What should I catch?

In keeping with the C++ tradition of "there's more than one way to do that" (translation: "give programmers options and tradeoffs so they can decide what's best for them in their situation"), C++ allows you a variety of options for catching.

In fact, you have all the flexibility that you have in declaring function parameters, and the rules for whether a particular exception matches (i.e., will be caught by) a particular catch clause are almost exactly the same as the rules for parameter compatibility when calling a function.

Given all this flexibility, how do you decide what to catch? Simple: unless there's a good reason not to, catch by reference. Avoid catching by value, since that causes a copy to be made and the copy can have different behavior from what was thrown. Only under very special circumstances should you catch by pointer.

TopBottomPrevious sectionNext sectionSearch the FAQ ]


[17.14] But MFC seems to encourage the use of catch-by-pointer; should I do the same?

Depends. If you're using MFC and catching one of their exceptions, by all means, do it their way. Same goes for any framework: when in Rome, do as the Romans. Don't try to force a framework into your way of thinking, even if "your" way of thinking is "better." If you decide to use a framework, embrace its way of thinking — use the idioms that its authors expected you to use.

But if you're creating your own framework and/or a piece of the system that does not directly depend on MFC, then don't catch by pointer just because MFC does it that way. When you're not in Rome, you don't necessarily do as the Romans. In this case, you should not. Libraries like MFC predated the standardization of exception handling in the C++ language, and some of these libraries use a backwards-compatible form of exception handling that requires (or at least encourages) you to catch by pointer.

The problem with catching by pointer is that it's not clear who (if anyone) is responsible for deleting the pointed-to object. For example, consider the following:

 MyException x;
 
 void f()
 {
   MyException y;
 
   try {
     switch ((rand() >> 8) % 3) {  
// the ">> 8" (typically) improves the period of the lowest 2 bits
       case 0: throw new MyException;
       case 1: throw &x;
       case 2: throw &y;
     }
   }
   catch (MyException* p) {
     ...        
 should we delete p here or not???!?
   }
 }

There are three basic problems here:

  1. It might be tough to decide whether to delete p within the catch clause. For example, if object x is inaccessible to the scope of the catch clause, such as when it's buried in the private part of some class or is static within some other compilation unit, it might be tough to figure out what to do.
  2. If you solve the first problem by consistently using new in the throw (and therefore consistently using delete in the catch), then exceptions always use the heap which can cause problems when the exception was thrown because the system was running low on memory.
  3. If you solve the first problem by consistently not using new in the throw (and therefore consistently not using delete in the catch), then you probably won't be able to allocate your exception objects as locals (since then they might get destructed too early), in which case you'll have to worry about thread-safety, locks, semaphores, etc. (static objects are not intrinsically thread-safe).

This isn't to say it's not possible to work through these issues. The point is simply this: if you catch by reference rather than by pointer, life is easier. Why make life hard when you don't have to?

The moral: avoid throwing pointer expressions, and avoid catching by pointer, unless you're using an existing library that "wants" you to do so.

TopBottomPrevious sectionNext sectionSearch the FAQ ]


[17.15] What does throw; (without an exception object after the throw keyword) mean? Where would I use it?

You might see code that looks something like this:

 class MyException {
 public:
   
...
   void addInfo(std::string const& info);
   
...
 };
 
 void f()
 {
   try {
     
...
   }
   catch (MyException& e) {
     e.addInfo("f() failed");
     throw;
   }
 }

In this example, the statement throw; means "re-throw the current exception." Here, a function caught an exception (by non-const reference), modified the exception (by adding information to it), and then re-threw the exception. This idiom can be used to implement a simple form of stack-trace, by adding appropriate catch clauses in the important functions of your program.

Another re-throwing idiom is the "exception dispatcher":

 void handleException()
 {
   try {
     throw;
   }
   catch (MyException& e) {
     
...code to handle MyException...
   }
   catch (YourException& e) {
     
...code to handle YourException...
   }
 }
 
 void f()
 {
   try {
     
...something that might throw...
   }
   catch (...) {
     handleException();
   }
 }

This idiom allows a single function (handleException()) to be re-used to handle exceptions in a number of other functions.

TopBottomPrevious sectionNext sectionSearch the FAQ ]


[17.16] How do I throw polymorphically?

Sometimes people write code like:

 class MyExceptionBase { };
 
 class MyExceptionDerived : public MyExceptionBase { };
 
 void f(MyExceptionBase& e)
 {
   
// ...
   throw e;
 }
 
 void g()
 {
   MyExceptionDerived e;
   try {
     f(e);
   }
   catch (MyExceptionDerived& e) {
     
...code to handle MyExceptionDerived...
   }
   catch (...) {
     
...code to handle other exceptions...
   }
 }

If you try this, you might be surprised at run-time when your catch (...) clause is entered, and not your catch (MyExceptionDerived&) clause. This happens because you didn't throw polymorphically. In function f(), the statement throw e; throws an object with the same type as the static type of the expression e. In other words, it throws an instance of MyExceptionBase. The throw statement behaves as-if the thrown object is copied, as opposed to making a "virtual copy".

Fortunately it's relatively easy to correct:

 class MyExceptionBase {
 public:
   virtual void raise();
 };
 
 void MyExceptionBase::raise()
 { throw *this; }
 
 class MyExceptionDerived : public MyExceptionBase {
 public:
   virtual void raise();
 };
 
 void MyExceptionDerived::raise()
 { throw *this; }
 
 void f(MyExceptionBase& e)
 {
   
// ...
   e.raise();
 }
 
 void g()
 {
   MyExceptionDerived e;
   try {
     f(e);
   }
   catch (MyExceptionDerived& e) {
     
...code to handle MyExceptionDerived...
   }
   catch (...) {
     
...code to handle other exceptions...
   }
 }

Note that the throw statement has been moved into a virtual function. The statement e.raise() will exhibit polymorphic behavior, since raise() is declared virtual and e was passed by reference. As before, the thrown object will be of the static type of the argument in the throw statement, but within MyExceptionDerived::raise(), that static type is MyExceptionDerived, not MyExceptionBase.

TopBottomPrevious sectionNext sectionSearch the FAQ ]


[17.17] When I throw this object, how many times will it be copied?

Depends. Might be "zero."

Objects that are thrown must have a publicly accessible copy-constructor. The compiler is allowed to generate code that copies the thrown object any number of times, including zero. However even if the compiler never actually copies the thrown object, it must make sure the exception class's copy constructor exists and is accessible.

TopBottomPrevious sectionNext sectionSearch the FAQ ]


E-Mail E-mail the author
C++ FAQTable of contentsSubject indexAbout the author©Download your own copy ]
Revised Jun 26, 2011