Programming Pandit

c/c++/c#/Javav/Python


Latest Update

Friday, October 23, 2026

Exception Handling: terminate(), unexpected(), uncaught_exception() and the Standard Exception Classes

 

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

 

Title: Evolution of the exception-related features across C++ standards. - Description: Evolution of the exception-related features across C++ standards.

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?

Title: Situations that lead to std::terminate() and what happens next. - Description: Situations that lead to std::terminate() and what happens next.

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

Title: Standard exception classes derived from std::exception. - Description: Standard exception classes derived from std::exception.

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