Handling Exceptions
An exception interrupts the normal flow of control in a program. An exception throws an object from the point where the exception is raised to the point where the exception is handled. In between those points, function calls are aborted, and local scopes are abruptly exited. Local and temporary objects in those scopes are destroyed.
This section discusses exception handling in general and the
try
statement in particular. For information about other
aspects of exception handling, see Chapter
3, which describes the throw
expression, and Chapter
5, which describes function try
blocks and throw
specifications. See also <exception>
and <stdexcept>
in Chapter 13 for information about the
standard exception classes and related functions.
Tip
As the name implies, an exception is used for exceptional
circumstances, such as indexing a vector with an index that is out of
bounds. The intention in C++ is that try
statements should have near zero cost,
but throwing and handling an exception can be expensive in terms of
performance.
A try
statement establishes a
local scope plus exception handlers. A try
statement begins with the try
keyword followed by a compound statement
and one or more catch
handlers. Each
catch
handler has an exception type
in parentheses followed by a compound statement. The exception type can
be a sequence of type specifiers followed by an optional declarator, or
it can be an ellipsis:
trycompound-statement
catch(type
declarator
)compound-statement
catch(type
)compound-statement
catch(...)compound-statement
Each compound statement (after try
and for each exception handler) forms a
separate local scope, following the rules for any other compound
statement.
Every time a local scope is entered—for example, by calling a
function or by entering a compound statement or other statement that
establishes a scope—you can think of the scope as pushing an entry on an
execution stack. When execution leaves the scope, the scope is popped
from the stack. Each try
statement is
also pushed on the stack.
When an exception is thrown, the execution stack is unwound. Local
scopes are popped from the stack (destroying any automatic objects that
were declared in each scope) until a try
statement is found. The try
statement is popped from the exception
stack. Then the type of the exception object is compared with each
exception handler. If a match is found, the exception object is copied
to the locally-declared exception object, and execution transfers to the
compound statement for that handler. After the handler finishes, the
exception object is destroyed, and execution continues with the
statement that follows the try
statement (and its handlers).
If no matching handler is found, the stack continues to be popped
until the next try
statement is
reached. Its handlers are compared with the exception type, and the
process repeats until a matching handler is found. If no try
statement has a matching handler, and the
entire execution stack is popped, the terminate
function is called. (See <exception>
in Chapter 13.)
An exception handler can throw an exception, in which case, the
usual rules apply for unwinding the stack. Note that the try
statement has already been popped from the
stack, so the same try
statement
cannot catch the exception. The handler can throw a new exception or
rethrow the existing exception (in which case, the exception object is
not freed yet).
A catch
handler can use an ellipsis to mean it catches any type
of exception. When an ellipsis is used, the handler must be last in the
list of exception handlers.
Handlers are checked in order. The type
in each catch clause is compared with the type
T
of the exception object according to the
following rules:
If
type
is the same asT
orT
&
(not considering cv-qualifiers), the handler matches.If
type
is a base class ofT
, the handler matches.If
type
is a pointer that can be converted toT
using a standard conversion (Chapter 3)—e.g.,type
isvoid*
—the handler matches.An ellipsis (
..
.) always matches.
Thus, it is important to put the most specific exceptions first, and put base classes later. If you use an ellipsis, it must be last.
A common idiom is to throw an exception object and catch a reference. This avoids unnecessary copies of the exception object.
Example 4-10 shows a
typical use of a try
statement. It also shows a try
function block, which is covered in Chapter 5.
#include <cstdlib> #include <fstream> #include <iostream> #include <numeric> #include <ostream> #include <sstream> #include <stdexcept> #include <string> #include <vector> class bad_data : public std::out_of_range { public: bad_data(int value, int min, int max) : std::out_of_range(make_what(value, min, max)) {} private: std::string make_what(int value, int min, int max); }; std::string bad_data::make_what(int value, int min, int max) { std::ostringstream out; out << "Invalid datum, " << value << ", must be in [" << min << ", " << max << "]"; return out.str( ); } // Read a set of numbers from an input stream. Verify that all data is within the // defined boundaries. Throw bad_data if any data is invalid. If an exception is thrown, tmp's destructor is automatically called (if it has a destructor). template<typename T, typename charT, typename traits> void getdata(std::basic_istream<charT,traits>& in, std::vector<T>& data, T min, T max) { T tmp; while (in >> tmp) { if (tmp < min)throw bad_data(tmp, min, max);
else if (tmp > max)throw bad_data(tmp, min, max);
else data.push_back(tmp); } } // Arbitrary precision integer class bigint { public: bigint( ); ~bigint( ); ... }; int main(int argc, char** argv) { using namespace std; if (argc < 2) { cerr << "usage: " << argv[0] << " FILE\n"; return EXIT_FAILURE; } vector<bigint> data; ifstream in(argv[1]); if (! in) { perror(argv[1]); return EXIT_FAILURE; }try {
getdata(in, data, bigint(0), bigint(10));} catch (const bad_data& ex) {
cerr << argv[1] << ": " << ex.what( ) << '\n'; return EXIT_FAILURE;} catch(...) {
std::cerr << "fatal error: unknown exception\n"; return EXIT_FAILURE; } if (data.size( ) == 0) cout << "no data\n"; else { bigint sum(accumulate(data.begin(),data.end( ),bigint( ))); std::cout << "avg=" << sum / data.size( ) << '\n'; } }
Get C++ In a Nutshell now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.