Exception Handling: terminate(), unexpected(), uncaught_exception() and the Standard Exception Classes
|
Learning
Objectives After completing this module,
students should be able to: •
Explain when terminate() is called and how set_terminate() changes its
behaviour. •
Explain the purpose of unexpected() and function exception specifications,
and know that they were removed from modern C++. •
Explain uncaught_exception() and its
replacement uncaught_exceptions(). •
Describe the exception class, bad_exception and the standard exception hierarchy. •
Use noexcept, the modern
replacement for exception specifications. |
|
Important: This module
covers several features that were part of C++98 and are deprecated or
removed in newer standards. They are explained fully because they appear
in the syllabus and in older books, and each one carries a Version Note
that says in which standard it can still be used and what to use today.
Programs for legacy features were compiled with g++
-std=c++14; the others with -std=c++17. |
1. Overview and Version Summary
The header <exception>
declares the support functions and classes for exception handling. They deal
with the situations in which the normal mechanism of try,
throw and catch
cannot continue.
|
Feature |
Purpose |
Status
in current C++ |
|
terminate(), set_terminate() |
Called when exception handling fails; ends the program |
Available in every standard |
|
unexpected(), set_unexpected() |
Called when a function throws an exception not allowed by
its specification |
Removed in C++17 (deprecated in C++11) |
|
Dynamic exception specification throw(T) |
Lists the exceptions a function may throw |
Removed in C++17; throw() removed in C++20 |
|
noexcept |
Declares that a function does not throw |
Introduced in C++11; the modern replacement |
|
uncaught_exception() |
Tells whether an exception is being handled |
Deprecated in C++17, removed in C++20 |
|
uncaught_exceptions() |
Number of exceptions being handled |
Introduced in C++17 |
|
exception, bad_exception |
Base class and a special exception type |
Available in every standard |
Figure 5.1: Evolution
of the exception-related features across C++ standards.
|
Version Note: The
standard removes a feature when a new edition is published, but compilers
usually keep it available as an extension for a long time so that old
programs still build. For example, GCC 13 still accepts std::unexpected()
and std::uncaught_exception()
in C++17 and C++20 mode and prints a deprecated warning. A program
that compiles on your machine is therefore not proof that the feature is part
of the standard you are studying. |
2. The terminate()
Function
terminate() is called when
exception handling cannot continue and the program has no safe way to proceed.
By default it ends the program abnormally by calling abort().
Nothing after the call is executed, and local objects are not guaranteed to be
destroyed.
2.1 When is terminate()
called?

Figure 5.2: Situations
that lead to std::terminate() and what happens next.
1.
An exception is thrown and no matching handler
is found in any calling function (uncaught exception).
2.
An exception leaves a noexcept
function (or an unexpected exception reaches the end of a legacy function
that uses a specification and has no handler).
3.
A rethrow throw;
is executed when no exception is being handled.
4.
A destructor throws an exception while stack
unwinding is already in progress for another exception.
5.
The program calls terminate()
explicitly.
2.2 Calling terminate()
explicitly
Program 5.1: An explicit call to terminate().
#include <iostream>
#include <exception>
using namespace std;
int main()
{
cout
<< "Program started." << endl;
cout
<< "Calling terminate()." << endl;
terminate();
cout
<< "This statement will not execute." << endl;
return 0;
}
Output with GCC (the program is aborted;
the message text is compiler-specific):
Program started.
Calling terminate().
terminate called without an active exception
The last two statements of main()
are never executed. Because no exception was active, GCC prints terminate
called without an active exception; for an uncaught exception it names the
type, as in Module 3.
2.3 Changing the handler with set_terminate()
The function that terminate()
calls is the terminate handler. The default handler calls abort(). A program can install its own handler with set_terminate(), for example to write a log message
before the program ends.
terminate_handler set_terminate(terminate_handler
handler) noexcept;
// terminate_handler is a pointer to a function: void handler();
set_terminate() returns the
previous handler. Since C++11 get_terminate()
returns the current one.
Program 5.2: A user-defined terminate
handler.
#include <iostream>
#include <exception>
#include <cstdlib>
using namespace std;
void myTerminateHandler()
{
cerr
<< "Fatal error: program is terminating." << endl;
abort(); // a
terminate handler must not return
}
int main()
{
set_terminate(myTerminateHandler);
cout
<< "Program started." << endl;
throw
42; // no handler:
terminate() is called
return 0;
}
Output:
Program started.
Fatal error: program is terminating.
•
set_terminate(myTerminateHandler)
replaces the default handler.
•
throw 42;
has no handler, so terminate() calls
the user-defined handler, which prints the message (to cerr)
and calls abort().
•
A terminate handler must not return. It must end
the program, by calling abort()
or exit(). If it returns, the
behaviour is undefined (in practice the program is aborted).
•
A terminate handler cannot recover the program; it is
only a place for last-moment actions such as logging and flushing important
data.
|
Note: exit() runs the
destructors of static objects and flushes files; abort()
ends immediately, without clean-up. Use the one suited to the situation. |
3. noexcept
and Function Exception Specifications
3.1 The legacy dynamic exception specification
(C++98)
In C++98 a function could list the exceptions it was allowed to throw:
void func() throw(int, char); // may throw only int or char
void safe() throw(); // throws nothing
The idea was to give the caller a guarantee. If the function threw any other
exception, the unexpected exception could not leave the function: the runtime
called unexpected() instead (Section 4).
The check was performed at run time, not at compile time, which made
specifications slow and of little value; they were widely avoided.
|
Version Note: C++11:
dynamic exception specifications throw(T) are deprecated;
throw()
is deprecated as an alias of noexcept. C++17:
throw(T)
with a type list is removed; only throw() remains. C++20:
throw()
is removed too. GCC 13 behaviour for a
function void f() throw(int): a deprecation
warning with -std=c++11 or -std=c++14,
and an error with -std=c++17: |
p5_unexpected_legacy.cpp:13:18: error: ISO C++17 does
not allow dynamic exception specifications
3.2 noexcept (C++11 and later)
noexcept replaces the old
specifications. It has two forms:
•
The specifier noexcept
(or noexcept(expression)) after a
function declaration says that the function does not throw. It is checked as
part of the function type (since C++17) and lets the compiler generate faster
code.
•
The operator noexcept(expression)
is a compile-time test that yields true
if the expression cannot throw.
If an exception tries to leave a noexcept
function, std::terminate() is called
immediately; the exception is not passed to any handler, not even catch (...) in the caller.
Program 5.3: The noexcept
specifier and operator.
#include <iostream>
using namespace std;
void safe() noexcept { cout << "safe(): no
exception escapes" << endl; }
void risky() { throw 1; }
void bad() noexcept
{
// an
exception tries to escape a noexcept function
throw 1;
}
int main()
{
cout
<< boolalpha;
// the
noexcept OPERATOR
cout
<< "noexcept(safe()) = "
<< noexcept(safe()) << endl;
cout
<< "noexcept(risky()) = " << noexcept(risky()) <<
endl;
safe();
try {
//
terminate() is called: the handler below is NOT used
bad();
}
catch (...)
{
cout
<< "never reached" << endl;
}
return 0;
}
Output (the program ends with terminate()):
noexcept(safe())
= true
noexcept(risky()) = false
safe(): no exception escapes
terminate called after throwing an instance of 'int'
•
noexcept(safe())
is true because safe() is declared noexcept;
noexcept(risky()) is false.
•
Calling bad()
makes the thrown int try to leave a noexcept function, so terminate()
is called; the message “never reached” is not printed.
|
Version Note: noexcept
needs -std=c++11
or later. Destructors are noexcept by default
since C++11, which is why throwing from a destructor ends the program (Module
6). |
4. The unexpected()
Function (Legacy)
|
Version Note: Legacy
feature: C++98 to C++14 only. unexpected()
and set_unexpected()
are deprecated in C++11 and removed from the standard in C++17, together with
the dynamic exception specifications they support. They are described here
because they are part of this syllabus. The programs of this section were
compiled with g++ -std=c++14. |
When a function with a specification throws an exception that is not
listed in it, the function unexpected()
is called. It calls the unexpected handler.
4.1 Default behaviour
The default unexpected handler calls terminate(),
so the program ends.
Program 5.4: A function that violates its
specification.
// Legacy C++: without a handler, unexpected() calls
terminate()
#include <iostream>
using namespace std;
void f() throw(int)
{
throw 'A';
}
int main()
{
try { f();
}
catch (int)
{ cout << "int caught" << endl; }
return 0;
}
Output (compiled with -std=c++14; the program is aborted):
terminate called after throwing an instance of 'char'
The function f() may throw only int, but it throws a char.
The exception does not reach the handler in main();
unexpected() is called and the
default handler terminates the program.
4.2 Changing the handler with set_unexpected()
unexpected_handler set_unexpected(unexpected_handler
handler) throw();
A user-defined unexpected handler can do one of the following:
•
terminate the program (by calling terminate(), abort()
or exit());
•
throw an exception that is allowed by the
specification, so the program can continue;
•
throw (or rethrow) another exception that is not
allowed. Then: if the specification contains bad_exception,
the exception is replaced by a bad_exception
object; otherwise terminate() is
called.
Program 5.5: The unexpected exception is
converted to bad_exception.
// Legacy C++ (C++98/03/11/14 only): dynamic
exception specification + unexpected()
#include <iostream>
#include <exception>
using namespace std;
void myUnexpected()
{
cout
<< "myUnexpected() called" << endl;
// rethrow:
still not allowed by the specification
throw;
}
void calculate() throw(int, bad_exception) // may throw only int or bad_exception
{
throw
'A'; // a char:
violates the specification
}
int main()
{
set_unexpected(myUnexpected);
try {
calculate();
}
catch (int)
{
cout
<< "int caught" << endl;
}
catch
(bad_exception &) {
cout
<< "bad_exception caught (the char was replaced by
bad_exception)"
<< endl;
}
return 0;
}
Output (compiled with -std=c++14):
myUnexpected() called
bad_exception caught (the char was replaced by
bad_exception)
1.
calculate()
is allowed to throw only int or bad_exception, but throws a char.
2.
unexpected()
calls myUnexpected(), which prints a
message and executes throw;, rethrowing
the char, which is still not allowed.
3.
Because bad_exception
appears in the specification, the exception is replaced by a bad_exception object, which is caught by the second
handler in main().
Compiling the same file with -std=c++17
fails: the specification throw(int,
bad_exception) is no longer part of the language (see the error in
Section 3.1).
|
Important: Modern
replacement: instead of listing the exceptions a function may throw, document
them, and use noexcept for functions that
must never throw. There is no equivalent of unexpected()
in current C++. |
5. The uncaught_exception()
Function
Sometimes code must know whether it is running because an exception is
being handled. A typical case is a destructor: during stack unwinding it
must not throw another exception, whereas during normal scope exit it could
still do extra work that might fail. uncaught_exception()
returns true from the moment an exception
object has been created by throw
until a handler has been entered (i.e. while the exception is still uncaught).
|
Version Note: Legacy
feature. bool uncaught_exception()
is available in C++98 to C++14, deprecated in C++17 and removed in
C++20. In C++17 and later use int uncaught_exceptions().
GCC 13 still provides the old function in C++20 mode but warns that it is
deprecated. |
Program 5.6 (legacy): Using uncaught_exception() inside a destructor.
// Legacy C++ (up to C++17): uncaught_exception()
returns bool
#include <iostream>
#include <exception>
using namespace std;
class Demo
{
public:
~Demo()
{
cout
<< "Exception in progress? " << (uncaught_exception() ?
"yes" : "no")
<< endl;
}
};
int main()
{
{ Demo a;
} // normal
scope exit
try {
Demo b;
throw
1; // b is
destroyed during stack unwinding
}
catch (int)
{
cout
<< "Caught" << endl;
}
return 0;
}
Output (compiled with -std=c++14):
Exception in progress? no
Exception in progress? yes
Caught
At the normal end of the first block no exception is active (no).
In the second block the destructor of b
runs while the exception 1 is
travelling to its handler, so the function returns true
(yes). With -std=c++17 -Wall
the compiler warns:
p5_uncaught_legacy.cpp:11:65: warning: ‘bool
std::uncaught_exception()’ is deprecated: use 'std::uncaught_exceptions()'
instead [-Wdeprecated-declarations]
5.1 The modern replacement: uncaught_exceptions()
A single bool is not sufficient in nested
situations: a destructor may itself be called during unwinding but may create
an object whose own destructor should behave as in normal operation. C++17
therefore returns the number of exceptions currently in flight. A guard
object stores this number when it is created and compares it in its destructor:
if the number is larger, the object is being destroyed because of a new
exception.
Program 5.7 (C++17): uncaught_exceptions() returns a count.
// C++17 or later: uncaught_exceptions() returns the
NUMBER of uncaught exceptions
#include <iostream>
#include <exception>
#include <stdexcept>
using namespace std;
class Demo
{
public:
~Demo()
{
cout
<< "Uncaught exceptions: " << uncaught_exceptions()
<< endl;
}
};
int main()
{
{ Demo a;
} // normal
scope exit: 0
try {
Demo b;
throw
runtime_error("Example exception");
}
catch
(const exception &e) {
cout
<< "Caught: " << e.what() << endl;
}
return 0;
}
Output:
Uncaught exceptions: 0
Uncaught exceptions: 1
Caught: Example exception
Remember: a destructor must not let an exception escape, so even
when this information is available, the correct response is to avoid operations
that can throw, not to throw conditionally.
6. The exception
and bad_exception Classes
6.1 The class std::exception
std::exception (header <exception>) is the base class of all exceptions
thrown by the standard library. Its interface is small:
class exception {
public:
exception()
noexcept;
exception(const exception &) noexcept;
exception
&operator=(const exception &) noexcept;
virtual
~exception();
virtual
const char *what() const noexcept; //
description of the error
};
•
what()
returns a null-terminated string describing the exception. The exact text is implementation-defined,
so programs should not compare against it.
•
It is virtual, so a handler for const
exception & calls the what()
of the real (derived) object.
•
A handler for exception
catches every standard exception and every user class derived from it.
|
Version Note: Before
C++11 the member functions were declared with throw()
(virtual
const char *what() const throw()). In C++11 and later
they are declared noexcept; user classes that
override what() must use a compatible specification. |
6.2 The class hierarchy

Figure 5.3: Standard
exception classes derived from std::exception.
|
Class |
Header |
Thrown
by |
|
bad_alloc |
<new> |
new when memory cannot be allocated |
|
bad_array_new_length |
<new> |
new with an invalid array length
(C++11) |
|
bad_cast |
<typeinfo> |
dynamic_cast to a reference type that fails |
|
bad_typeid |
<typeinfo> |
typeid applied to a null pointer to a
polymorphic type |
|
bad_exception |
<exception> |
Replaces an unexpected exception (legacy specifications) |
|
logic_error family: invalid_argument, domain_error, length_error, out_of_range |
<stdexcept> |
Errors in program logic that could be avoided by checking:
for example vector::at(), stoi() |
|
runtime_error family: range_error, overflow_error, underflow_error, system_error |
<stdexcept>, <system_error> |
Errors detectable only at run time (arithmetic,
operating-system and library failures) |
Program 5.8: Standard exceptions thrown by
library operations, and one handler for the common base class.
#include <iostream>
#include <vector>
#include <string>
#include <new>
#include <typeinfo>
#include <stdexcept>
using namespace std;
class Base { public: virtual ~Base() {} };
class Derived : public Base {};
int main()
{
// 1.
out_of_range from vector::at()
try {
vector<int> v(3); v.at(10); }
catch
(const out_of_range &e) {
cout
<< "out_of_range : thrown
by vector::at()" << endl;
}
// 2.
invalid_argument from stoi()
try {
stoi("abc"); }
catch
(const invalid_argument &e) {
cout
<< "invalid_argument: thrown by stoi(\"abc\")"
<< endl;
}
// 3.
bad_alloc from new
try {
volatile size_t big = ~static_cast<size_t>(0) / 4;
char *p
= new char[big];
delete[] p;
}
catch
(const bad_alloc &e) {
cout
<< "bad_alloc : thrown
by new" << endl;
}
// 4.
bad_cast from a failed dynamic_cast to a reference
try { Base
b; Derived &d = dynamic_cast<Derived &>(b); (void)d; }
catch
(const bad_cast &e) {
cout
<< "bad_cast : thrown
by dynamic_cast<Derived&>" << endl;
}
// 5.
length_error from string::resize()
try {
string s; s.resize(s.max_size() + 1); }
catch
(const length_error &e) {
cout
<< "length_error : thrown
by string::resize()" << endl;
}
// All of
them are caught by one handler for the common base class
try {
vector<int> v(2); v.at(5); }
catch
(const exception &e) {
cout
<< "std::exception can catch all of the above" << endl;
}
return 0;
}
Output:
out_of_range
: thrown by vector::at()
invalid_argument: thrown by stoi("abc")
bad_alloc
: thrown by new
bad_cast
: thrown by dynamic_cast<Derived&>
length_error
: thrown by string::resize()
std::exception can catch all of the above
|
Note: The program prints
fixed text and not e.what() because the text of what()
differs between compilers. The bad_alloc test requests
an impossibly large block; on some systems with lazy memory allocation a
smaller request may succeed. |
6.3 The class bad_exception
bad_exception is a standard
exception class derived from exception.
Its purpose is to give programs a type for an unexpected exception: when
a function with a specification that includes bad_exception
throws a disallowed exception, the runtime replaces it with a bad_exception object (Program 5.5). It can also be thrown
and caught like any other exception.
Program 5.9: Throwing and catching bad_exception.
#include <iostream>
#include <exception>
using namespace std;
int main()
{
try {
throw
bad_exception();
}
catch
(const bad_exception &e) {
cout
<< "bad_exception caught: " << e.what() << endl;
}
catch
(const exception &e) {
cout
<< "General exception caught." << endl;
}
return 0;
}
Output:
bad_exception caught: std::bad_exception
The bad_exception handler is placed
before the handler for its base class exception,
as required. The value of what()
is implementation-defined (std::bad_exception
in GCC).
|
Version Note: bad_exception
itself is still part of C++, but because the specification mechanism that
used it has been removed, it is rarely needed in modern programs. |
7. Legacy versus Modern C++:
Summary
|
Task |
Legacy
way (C++98 to C++14) |
Modern
way (C++11 onwards; required from C++17) |
|
Say that a function never throws |
void
f() throw(); |
void
f() noexcept; |
|
List the exceptions a function may throw |
void
f() throw(int, char); |
Document it; no language feature |
|
React when a specification is violated |
unexpected(), set_unexpected(), bad_exception |
Not applicable: violations of noexcept call terminate() |
|
Know whether an exception is in flight |
uncaught_exception() (bool) |
uncaught_exceptions() (int), C++17 |
|
Customize termination |
set_terminate() |
Same; plus get_terminate() (C++11) |
|
Signature of what() |
const
char *what() const throw() |
const
char *what() const noexcept override |
|
Carry an exception across threads or layers |
Not possible |
exception_ptr, nested exceptions (C++11) |
8. Quick Revision
•
terminate()
ends the program (default: abort());
set_terminate() installs a
handler that must not return.
•
It is called for uncaught exceptions, exceptions
leaving noexcept functions, a bare throw; with no active exception, and throwing destructors
during unwinding.
•
unexpected()
(legacy) runs when a function throws an exception outside its throw(...) list; default: terminate();
it can be replaced using set_unexpected()
and removed in C++17.
•
uncaught_exception()
(deprecated in C++17, removed in C++20) became uncaught_exceptions()
returning a count.
•
std::exception
is the root of the standard hierarchy; what()
describes the error; bad_exception
replaces unexpected exceptions.
•
Use noexcept
in modern code.
9. Important Examination
Questions
Short-answer questions
1.
When is terminate()
called? What is its default action?
2.
What is the use of set_terminate()?
May the handler return?
3.
What is unexpected()?
When is it called? What is its default handler?
4.
What is a function exception specification? Why was it
removed from C++?
5.
What is the purpose of uncaught_exception()?
What replaced it?
6.
What is the class exception?
What does what() return?
7.
What is bad_exception?
8.
What is noexcept?
Long-answer and
programming questions
1.
Explain terminate()
and set_terminate() with a program.
2.
Explain unexpected(),
set_unexpected() and bad_exception with a program, and state in which C++
versions it works.
3.
Explain uncaught_exception()
with a program and describe its modern replacement.
4.
Draw and explain the hierarchy of the standard
exception classes.
5.
Write a program that demonstrates three different
standard exceptions.
6.
Compare the legacy and modern ways of specifying and
handling exceptions in C++.
No comments:
Post a Comment