Robert Schmidt
June 17, 1999
Part 3 sketches out the basic syntax and semantics of Standard C++ exception handling.
This time around, I sketch out the basic syntax and semantics of Standard C++ exception handling. Along the way, I also compare and contrast this handling to the techniques presented in my previous two columns. (As a shorthand notation in this and future columns, I'll refer to Standard C++ exception handling as EH, mimicking Microsoft's related abbreviation SEH.)
EH introduces three new C++ language keywords:
Exceptions are raised via:
throw [expression]
A function defines which exceptions it raises with an exception specification:
throw ( [type-ID-list] )
where the optional type ID list contains one or more type names, separated by commas. Those exceptions are caught by handlers organized within try blocks:
try compound-statement handler-sequence
where a handler sequence contains one or more handlers of the form:
catch ( exception-declaration ) compound-statement
A handler's exception declaration specifies what type of exception that handler catches.
As in SEH, statements following try and catch must appear within {}, while the entire try block itself is considered a single statement in larger syntactic contexts.
Example:
void f() throw(int, some_class_type)
{
int i;
// ... generate an 'int' exception
throw i;
// ...
}
int main()
{
try
{
f();
}
catch(int e)
{
// ... handle 'int' exception ...
}
catch(some_class_type e)
{
// ... handle 'some_class_type' exception ...
}
// ... possibly other handlers ...
return 0;
}
Exception specifications are unique to EH—SEH and Microsoft Foundation Classes (MFC) have no counterpart to this feature. An empty exception specification implies a function throws no exceptions:
void f() throw()
{
// ... function throws no exceptions ...
}
If a function has no exception specification, it may throw anything:
void f()
{
// ... function can throw anything or nothing ...
}
When a function does throw an exception, the throw keyword usually has an associated thrown object:
throw i;
However, throw can also appear without an object:
catch(int e)
{
// ... handle 'int' exception ...
throw;
}
This has the effect of re-throwing the currently handled object (int e). Because such an empty throw is a re-throwing of an existing exception object, it must occur in the context of a catch clause. MFC shares this ability to re-throw a caught exception; SEH, on the other hand, does not declare exception objects local to a handler, and thus has nothing to re-throw.
Much like parameter declarations in function prototypes, exception declarations can have abstract declarators:
catch(char *)
{
// ... handle 'char *' exception ...
}
While this handler catches a char * exception object, it cannot manipulate that object, since the object has no name.
An exception declaration may also have the special form:
catch(...)
{
// ... handle any type of exception ...
}
Just as ... in a parameter list matches any argument type, so to does ... in an exception declaration match any exception type.
Standard Library functions may report errors. Within the Standard C Library, errors manifest in the manner described in Part 1 of this series. Within the Standard C++ Library, some functions throw specified exceptions, while others throw no exceptions at all.
Where the Standard is silent, a C++ Library function may throw anything or nothing; however, the Standard encourages library implementers to report errors via exceptions from (or derived from) these classes defined in <stdexcept>:
namespace std
{
class logic_error; // : public exception
class domain_error; // : public logic_error
class invalid_argument; // : public logic_error
class length_error; // : public logic_error
class out_of_range; // : public logic_error
class runtime_error; // : public exception
class range_error; // : public runtime_error
class overflow_error; // : public runtime_error
class underflow_error; // : public runtime_error
}
logic_errors arise from internal program bugs and are theoretically preventable, while runtime_errors occur outside the program's control and are difficult to predict.
All of these classes are meant to constrain the Standard C++ Library. In your own code, you can throw (and catch) exceptions of any type you want.
The Standard Library header <exception> declares several EH types and functions:
namespace std
{
//
// types
//
class bad_exception;
class exception;
typedef void (*terminate_handler)();
typedef void (*unexpected_handler)();
//
// functions
//
terminate_handler set_terminate(terminate_handler) throw();
unexpected_handler set_unexpected(unexpected_handler) throw();
void terminate();
void unexpected();
bool uncaught_exception();
}
Synopsis:
EH implements the now-familiar five stages of an exception's lifetime:
These steps are captured in this simple example:
#include <stdio.h>
static void f(int n)
{
if (n != 0) // Stage 1
throw 123; // Stage 2
}
extern int main()
{
try
{
f(1);
printf("resuming, should never appear\n");
}
catch(int) // Stage 3
{
// Stage 4
printf("caught 'int' exception\n");
}
catch(char *) // Stage 3
{
// Stage 4
printf("caught 'char *' exception\n");
}
catch(...) // Stage 3
{
// Stage 4
printf("caught typeless exception\n");
}
// Stage 5
printf("terminating, after 'try' block\n");
return 0;
}
/*
When run yields
caught 'int' exception
terminating, after 'try' block
*/
The Standard C Library exception mechanisms suffer fundamental problems in C++:
Even the Microsoft-specific mechanisms are not without their limitations:
Standard C++ exception handling addresses the above shortcomings:
For a more complete rationale, including alternative EH designs considered by the C++ Standard's committee, check out Chapter 16 of the D&E.
In the next few columns, I'll dig more into both EH core language features and EH Standard Library support. I'll also show how Microsoft Visual C++® implements EH under the hood. Along the way I'll start flagging EH features that Visual C++ supports either partially or not at all, and will look into ways to get around those limitations.
While I believe the fundamental design of EH is sound, I also believe EH induces some serious unintended consequences. Rather than accusing the authors of the C++ Standard of being short-sighted, I bear in mind how difficult it is to design and implement effective exception handling. As we encounter those unintended consequences, I'll demonstrate their sly effects on your code, and propose techniques to mitigate those effects.
Robert Schmidt is a technical writer for MSDN. His other major writing distraction is the C/C++ Users Journal (http://www.cuj.com/), for which he is a contributing editor and columnist. In previous career incarnations he's been a radio DJ, wild-animal curator, astronomer, pool-hall operator, private investigator, newspaper carrier, and college tutor.