More Effective C++ | Item 13: Catch exceptions by reference Back to Item 12: Understand how throwing an exception differs from passing a parameter or calling a virtual function Continue to Item 14: Use exception specifications judiciously Item 13: Catch exceptions by reference. When you write a catch clause, you must specify how exception objects are to be passed to that clause. You have three choices, just as when specifying how parameters should be passed to functions: by pointer, by value, or by reference. Let us consider first catch by pointer. In theory, this should be the least inefficient way to implement the invariably slow process of moving an exception from throw site to catch clause (see Item 15). That's because throw by pointer is the only way of moving exception information without copying an object (see Item 12). For example: class exception { ... }; // from the standard C++ // library exception // hierarchy (see Item 12) void someFunction() { static exception ex; // exception object ... throw &ex; // throw a pointer to ex ... } void doSomething() { try { someFunction(); // may throw an exception* } catch (exception *ex) { // catches the exception*; ... // no object is copied } } This looks neat and tidy, but it's not quite as well-kept as it appears. For this to work, programmers must define exception objects in a way that guarantees the objects exist after control leaves the functions throwing pointers to them. Global and static objects work fine, but it's easy for programmers to forget the constraint. If they do, they typically end up writing code like this: void someFunction() { exception ex; // local exception object; // will be destroyed when // this function's scope is ... // exited throw &ex; // throw a pointer to an ... // object that's about to } // be destroyed This is worse than useless, because the catch clause handling this exception receives a pointer to an object that no longer exists. An alternative is to throw a pointer to a new heap object: void someFunction() { ... throw new exception; // throw a pointer to a new heap- ... // based object (and hope that } // operator new see Item 8 // doesn't itself throw an // exception!) This avoids the I-just-caught-a-pointer-to-a-destroyed-object problem, but now authors of catch clauses confront a nasty question: should they delete the pointer they receive? If the exception object was allocated on the heap, they must, otherwise they suffer a resource leak. If the exception object wasn't allocated on the heap, they mustn't, otherwise they suffer undefined program behavior. What to do? It's impossible to know. Some clients might pass the address of a global or static object, others might pass the address of an exception on the heap. Catch by pointer thus gives rise to the Hamlet conundrum: to delete or not to delete? It's a question with no good answer. You're best off ducking it. Furthermore, catch-by-pointer runs contrary to the convention established by the language itself. The four standard exceptions bad_alloc (thrown when operator new (see Item 8) can't satisfy a memory request), bad_cast (thrown when a dynamic_cast to a reference fails; see Item 2), bad_typeid (thrown when dynamic_cast is applied to a null pointer), and bad_exception (available for unexpected exceptions; see Item 14) are all objects, not pointers to objects, so you have to catch them by value or by reference, anyway. Catch-by-value eliminates questions about exception deletion and works with the standard exception types. However, it requires that exception objects be copied twice each time they're thrown (see Item 12). It also gives rise to the specter of the slicing problem, whereby derived class exception objects caught as base class exceptions have their derivedness "sliced off." Such "sliced" objects are base class objects: they lack derived class data members, and when virtual functions are called on them, they resolve to virtual functions of the base class. (Exactly the same thing happens when an object is passed to a function by value see Item E22.) For example, consider an application employing an exception class hierarchy that extends the standard one: class exception { // as above, this is a public: // standard exception class virtual const char * what() throw(); // returns a brief descrip. ... // of the exception (see // Item 14 for info about }; // the "throw()" at the // end of the declaration) class runtime_error: // also from the standard public exception { ... }; // C++ exception hierarchy class Validation_error: // this is a class added by public runtime_error { // a client public: virtual const char * what() throw(); // this is a redefinition ... // of the function declared }; // in class exception above void someFunction() // may throw a validation { // exception ... if (a validation test fails) { throw Validation_error(); } ... } void doSomething() { try { someFunction(); // may throw a validation } // exception catch (exception ex) { // catches all exceptions // in or derived from // the standard hierarchy cerr << ex.what(); // calls exception::what(), ... // never } // Validation_error::what() } The version of what that is called is that of the base class, even though the thrown exception is of type Validation_error and Validation_error redefines that virtual function. This kind of slicing behavior is almost never what you want. That leaves only catch-by-reference. Catch-by-reference suffers from none of the problems we have discussed. Unlike catch-by-pointer, the question of object deletion fails to arise, and there is no difficulty in catching the standard exception types. Unlike catch-by-value, there is no slicing problem, and exception objects are copied only once. If we rewrite the last example using catch-by-reference, it looks like this: void someFunction() // nothing changes in this { // function ... if (a validation test fails) { throw Validation_error(); } ... } void doSomething() { try { someFunction(); // no change here } catch (exception& ex) { // here we catch by reference // instead of by value cerr << ex.what(); // now calls // Validation_error::what(), ... // not exception::what() } } There is no change at the throw site, and the only change in the catch clause is the addition of an ampersand. This tiny modification makes a big difference, however, because virtual functions in the catch block now work as we expect: functions in Validation_error are invoked if they redefine those in exception. What a happy confluence of events! If you catch by reference, you sidestep questions about object deletion that leave you damned if you do and damned if you don't; you avoid slicing exception objects; you retain the ability to catch standard exceptions; and you limit the number of times exception objects need to be copied. So what are you waiting for? Catch exceptions by reference! Back to Item 12: Understand how throwing an exception differs from passing a parameter or calling a virtual function Continue to Item 14: Use exception specifications judiciously