Programming Pandit

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


Latest Update

Friday, October 23, 2026

Exception Handling: Fundamentals

 

Exception Handling: Fundamentals

 

Learning Objectives

After completing this module, students should be able to:

•      Explain what an exception is and why exception handling is better than traditional error codes.

•      Use the try, throw and catch keywords and describe the flow of control.

•      Write multiple handlers and a catch-all handler, and state the rules for matching a thrown value to a handler.

•      Explain exception propagation and stack unwinding, including the destruction of local objects.

•      Use nested try blocks and rethrowing, and explain what happens to an uncaught exception.

 

1. Introduction

Programs do not always run as expected. A file may not exist, memory may run out, a user may enter invalid data, a number may be divided by zero, or an array index may go out of range. Such unusual events that occur while a program is running are errors; the ones that the program can detect and respond to are handled with exceptions.

Definition: An exception is an abnormal condition or run-time error that occurs during program execution and disturbs its normal flow. Exception handling is the C++ mechanism that detects such a condition, transfers control to special code written to deal with it, and lets the program continue or terminate safely.

 

1.1 Types of errors

Type of error

When it is detected

Example

Syntax (compile-time) error

During compilation

Missing semicolon, undeclared variable

Logical error

Not detected automatically; the output is wrong

Using + where * was intended

Run-time error

During execution

Division by zero, invalid memory access, file not found

 

Exception handling deals with run-time errors that the program can anticipate. It does not repair syntax errors and does not find logical errors.

1.2 Traditional error handling and its problems

Before exceptions, errors were reported through return codes, global error variables or exit(). These methods have several drawbacks:

Problem

Explanation

Error code can be ignored

The caller may forget to check the return value, and the program continues with wrong data

Error and result are mixed

A function returning -1 for failure cannot also return -1 as a valid result

Code becomes cluttered

Every call must be followed by if tests, hiding the real logic

Errors must be passed by hand

Each function in a chain has to return the error upwards itself

Constructors cannot return values

There is no way to report a failure from a constructor with a return code

 

Feature

Traditional (return codes)

Exception handling

Error detection and handling

Mixed with normal code

Separated: normal code in try, handling in catch

Ignoring an error

Easy to do by mistake

An unhandled exception cannot be ignored: the program terminates

Passing error to a caller

Manual, at every level

Automatic (propagation)

Clean-up of local objects

Manual

Automatic (stack unwinding)

Readability

Lower

Higher

 

1.3 Advantages of exception handling

1.      Separates the error-handling code from the normal program logic.

2.      Propagates the error automatically to the code that knows how to deal with it.

3.      Cleans up automatically: local objects are destroyed while the exception travels outward.

4.      Works with constructors and operators, which cannot return error codes.

5.      Allows different error types to be handled differently, through different handlers.

 

2. Exception Handling Fundamentals

C++ exception handling is built on three keywords:

Keyword

Purpose

try

Encloses the code that may cause an exception (the code being monitored)

throw

Signals ("raises") that an exceptional condition has occurred and passes a value describing it

catch

Defines a handler that receives and processes an exception of a given type

 

try {

    // statements that may throw an exception

    throw value;                       // raises an exception

}

catch (type1 parameter) {

    // handler for exceptions of type1

}

catch (type2 parameter) {

    // handler for exceptions of type2

}

 

•      A try block must be followed immediately by at least one catch handler.

•      The value given to throw can be of any type: a built-in type, a string, or an object of a class.

•      The code that discovers the problem (throw) and the code that handles it (catch) may be in different functions.

 

Version Note: The try, throw and catch mechanism is the same in all C++ standards from C++98 to C++23. The features that did change between standards (function exception specifications, unexpected(), uncaught_exception()) are explained in Modules 5 and 6 with a version note each.

 

2.1 The try block

The statements that may raise an exception are placed inside the braces of a try block. If no exception occurs, the block completes normally and all handlers are skipped. If an exception is thrown, the rest of the block is abandoned and control goes to a matching handler.

2.2 The throw statement

throw expression; creates an exception object (a copy of the expression value) and starts the search for a handler. Examples:

throw 100;                                // int

throw 'E';                                // char

throw "Division by zero";                 // const char *

throw runtime_error("File not found");    // an object of a class

 

2.3 The catch block

A catch block is a handler for one type of exception. The parameter names the type that the handler accepts and receives the thrown value. If the value is not needed, the parameter name can be omitted. A handler for a class type should normally take the parameter by reference to const, as in catch (const runtime_error &e), to avoid copying and slicing (Module 4).

2.4 Program: division by zero

Program 3.1: Detecting and handling an exceptional condition.

#include <iostream>

#include <stdexcept>

using namespace std;

 

int main()

{

    int a = 10;

    int b = 0;

 

    try {

        if (b == 0)

            throw runtime_error("Division by zero is not allowed");

        cout << "Result: " << a / b << endl;

    }

    catch (const runtime_error &e) {

        cout << "Exception caught: " << e.what() << endl;

    }

 

    cout << "Program continues normally." << endl;

    return 0;

}

 

Output:

Exception caught: Division by zero is not allowed

Program continues normally.

 

1.      The statements in the try block are executed. Because b == 0, throw creates a runtime_error object carrying the message.

2.      The remaining statement of the block (cout << a / b) is skipped, so the dangerous division is never performed.

3.      The catch block receives the object; e.what() returns the message text.

4.      After the handler, execution continues with the first statement after the try-catch construct, so “Program continues normally.” is printed.

 

3. Flow of Control

Title: Flow of control in a try-catch construct. - Description: Flow of control in a try-catch construct.

Figure 3.1: Flow of control in a try-catch construct.

1.      No exception: the entire try block executes, every catch block is skipped, and execution continues after the last handler.

2.      Exception thrown: the remaining statements of the try block are skipped. The local objects created in the block are destroyed.

3.      The handlers are examined in the order in which they are written; the first one that matches is executed, and the others are ignored.

4.      After the handler finishes, control passes to the statement following the handlers. It does not return to the point of the throw: C++ uses the termination model of exception handling.

5.      If no handler matches, the exception moves to the calling function (Section 5). If no function handles it, the program terminates (Section 7).

 

4. Multiple catch Blocks and Handler Matching

A try block can be followed by several handlers, one for each type of exception that may arise. The special handler catch (...) (the catch-all handler) accepts an exception of any type; it is normally written last.

Program 3.2: Handlers for int, char and any other type.

#include <iostream>

using namespace std;

 

int main()

{

    int choice;

    cout << "Enter 1 (int), 2 (char) or 3 (other): ";

    cin >> choice;

 

    try {

        if (choice == 1)

            throw 100;

        else if (choice == 2)

            throw 'E';

        else if (choice == 3)

            throw 3.14;

        else

            cout << "No exception thrown." << endl;

    }

    catch (int e) {

        cout << "Integer exception caught: " << e << endl;

    }

    catch (char e) {

        cout << "Character exception caught: " << e << endl;

    }

    catch (...) {

        cout << "Some other exception caught." << endl;

    }

 

    cout << "Execution completed." << endl;

    return 0;

}

 

Output for the three inputs 1, 2 and 3:

Enter 1 (int), 2 (char) or 3 (other): 1

Integer exception caught: 100

Execution completed.

 

Enter 1 (int), 2 (char) or 3 (other): 2

Character exception caught: E

Execution completed.

 

Enter 1 (int), 2 (char) or 3 (other): 3

Some other exception caught.

Execution completed.

 

For any other input (for example 9) the program prints No exception thrown.; the handlers are not used.

•      throw 100; has type int and is received by catch (int e).

•      throw 'E'; has type char and is received by catch (char e).

•      throw 3.14; has type double; no handler has that type, so catch (...) receives it.

 

4.1 Rules for matching

Rule

Explanation

Exact type match

The handler type must be the same as the thrown type (ignoring const and references).

No ordinary conversions

A thrown int is not converted to double or long; a thrown char does not match int; a thrown const char * does not match string.

Derived to base is allowed

A handler for a base class also receives objects of its derived classes (Module 4).

First match wins

Handlers are tried in order; later handlers are not considered once one has matched.

catch (...)

Matches everything, so it must be the last handler.

 

Program 3.3: The thrown type decides which handler runs; no conversion is applied.

#include <iostream>

#include <string>

using namespace std;

 

/* The type of the thrown value must match the handler type;

   ordinary conversions (such as int to double) are NOT applied. */

void test(int which)

{

    try {

        if (which == 1) throw 10;           // int

        if (which == 2) throw 'x';          // char

        if (which == 3) throw "text";       // const char *

        if (which == 4) throw string("s");  // std::string

    }

    catch (double d) { cout << "double handler: " << d << endl; }

    catch (int i)    { cout << "int handler: " << i << endl; }

    catch (const string &s) { cout << "string handler: " << s << endl; }

    catch (...)      { cout << "catch-all handler (no exact match)" << endl; }

}

 

int main()

{

    for (int i = 1; i <= 4; i++)

        test(i);

    return 0;

}

 

Output:

int handler: 10

catch-all handler (no exact match)

catch-all handler (no exact match)

string handler: s

 

The value 10 is an int and is received by the int handler although a double handler is written before it (it does not match). The character 'x' and the C-string "text" match neither int, double nor string, so they reach the catch-all handler. Only the real string object matches the string handler.

Note: Because a C string literal has type const char *, throw "message"; is caught by catch (const char *msg) and not by catch (const string &). For messages, throw a std::runtime_error or another class from <stdexcept>.

 

5. Exception Propagation and Stack Unwinding

5.1 Propagation

A function that throws an exception does not need to handle it. If there is no matching handler in the function, the exception is passed to the function that called it, then to that function’s caller, and so on, until a matching handler is found. This is called exception propagation.

Program 3.4: An exception thrown in functionB() and handled in main().

#include <iostream>

using namespace std;

 

void functionB()

{

    cout << "Inside functionB()" << endl;

    throw 100;

}

 

void functionA()

{

    cout << "Inside functionA()" << endl;

    functionB();

    cout << "functionA() ends" << endl;       // never executed

}

 

int main()

{

    try {

        functionA();

    }

    catch (int e) {

        cout << "Exception caught in main(): " << e << endl;

    }

    cout << "Program continues." << endl;

    return 0;

}

 

Output:

Inside functionA()

Inside functionB()

Exception caught in main(): 100

Program continues.

 

Title: The exception leaves functionB() and functionA() and is caught in main(). - Description: The exception leaves functionB() and functionA() and is caught in main().

Figure 3.2: The exception leaves functionB() and functionA() and is caught in main().

The statement “functionA() ends” is never printed: after throw 100 the remaining code of both functions is skipped.

5.2 Stack unwinding

While the exception travels back through the active functions, every function that is left is removed from the call stack. The local (automatic) objects of each function are destroyed on the way, and their destructors are called in the reverse order of construction. This process is called stack unwinding.

Program 3.5: Destructors run automatically during stack unwinding.

#include <iostream>

#include <string>

using namespace std;

 

class Demo

{

    string name;

 

public:

    Demo(const string &n) : name(n) { cout << "Constructor: " << name << endl; }

    ~Demo() { cout << "Destructor : " << name << endl; }

};

 

void inner()

{

    Demo d3("d3 (in inner)");

    cout << "inner(): throwing" << endl;

    throw 10;

}

 

void outer()

{

    Demo d1("d1 (in outer)");

    Demo d2("d2 (in outer)");

    inner();

    cout << "outer() ends" << endl;            // never executed

}

 

int main()

{

    try {

        outer();

    }

    catch (int e) {

        cout << "Caught in main(): " << e << endl;

    }

    return 0;

}

 

Output:

Constructor: d1 (in outer)

Constructor: d2 (in outer)

Constructor: d3 (in inner)

inner(): throwing

Destructor : d3 (in inner)

Destructor : d2 (in outer)

Destructor : d1 (in outer)

Caught in main(): 10

 

Title: Stack unwinding: d3 is destroyed first, then d2, then d1, then the handler runs. - Description: Stack unwinding: d3 is destroyed first, then d2, then d1, then the handler runs.

Figure 3.3: Stack unwinding: d3 is destroyed first, then d2, then d1, then the handler runs.

1.      d1 and d2 are created in outer() and d3 in inner().

2.      throw 10 in inner() has no handler there. The function is left and ~d3 runs.

3.      The exception reaches outer(), which has no handler either: d2 is destroyed, then d1 (reverse order of creation).

4.      The handler in main() finally runs.

 

Important: Stack unwinding makes exceptions safe for resources: a resource owned by an object (a file, a lock, memory in a smart pointer) is released by its destructor even when an exception passes through. Memory obtained with a raw new and released later with delete would be leaked (Module 6, RAII).

 

6. Nested try Blocks and Rethrowing

A try block may contain another try block. An exception is first looked for in the inner handlers; if none matches, the search moves outward. A handler can also decide that it cannot fully deal with an exception and rethrow it with a throw; statement that has no operand. The same exception object then continues to the outer handlers.

Program 3.6: Inner handler rethrows to the outer handler.

#include <iostream>

using namespace std;

 

int main()

{

    try {

        cout << "Outer try block" << endl;

        try {

            throw 20;

        }

        catch (int e) {

            cout << "Inner handler: " << e << endl;

            throw;                              // rethrow the same exception

        }

    }

    catch (int e) {

        cout << "Outer handler: " << e << endl;

    }

    return 0;

}

 

Output:

Outer try block

Inner handler: 20

Outer handler: 20

 

The inner handler performs its part of the work (printing a message) and passes the exception on; the outer handler completes the handling. A bare throw; is valid only while a handler is active; used elsewhere it calls std::terminate(). Rethrowing, and the difference between throw; and throw e;, are discussed in Module 4.

7. Uncaught Exceptions

If an exception is thrown and no function on the call stack has a matching handler, it is an uncaught exception. C++ then calls the function std::terminate(), which by default ends the program abnormally by calling abort(). Whether the stack is unwound before this happens is implementation-defined.

Program 3.7: No handler for a thrown exception.

#include <iostream>

using namespace std;

 

int main()

{

    cout << "Before throw" << endl;

    throw 42;                                   // no handler anywhere

    cout << "After throw" << endl;

    return 0;

}

 

Output with GCC (the exact text of the message depends on the compiler; the shell then reports that the program was aborted, for example Aborted (core dumped)):

Before throw

terminate called after throwing an instance of 'int'

 

“After throw” is never printed. The functions terminate(), set_terminate() and the related features are studied in Module 5. A well-written program therefore handles every exception that can occur, or at least installs a catch-all handler in main().

8. Rules, Limitations and Common Mistakes

Mistake

Correction

Writing try without a catch

Every try must be followed by at least one handler

Expecting throw 5 to match catch (double)

The types must match; no conversion is applied

Placing catch (...) before other handlers

The catch-all handler must be last

Expecting execution to resume after the throw

Control continues after the handlers (termination model)

Using exceptions for ordinary control flow

Exceptions are for exceptional conditions, not for normal loops or tests

Throwing a string literal and catching string

Throw std::string or runtime_error, or catch const char *

Using a bare throw; outside a handler

It calls terminate(); use it only inside a catch block

Ignoring clean-up of resources

Let objects (destructors, smart pointers) own the resources

 

Exceptions have a run-time cost when they are thrown, so they should not be used for conditions that occur routinely; for example, reaching the end of a loop.

9. Quick Revision

•      try monitors code, throw raises the exception, catch handles it.

•      Handlers are matched in order by exact type (or derived-to-base); catch (...) matches anything and goes last.

•      After a handler, control continues after the last handler (termination model).

•      An unhandled exception propagates to the callers; if nobody handles it, terminate() is called.

•      Stack unwinding destroys local objects in reverse order of construction.

•      throw; rethrows the current exception from inside a handler.

 

10. Important Examination Questions

Short-answer questions

1.      What is an exception? What is exception handling?

2.      Name the three keywords used in exception handling and state their purpose.

3.      Why is exception handling better than returning error codes?

4.      What is the termination model of exception handling?

5.      What is catch (...)? Where must it be placed?

6.      What is exception propagation? What is stack unwinding?

7.      What happens when an exception is not caught?

8.      How is an exception rethrown?

 

Long-answer and programming questions

1.      Explain the exception handling mechanism of C++ with a program and a diagram of the flow of control.

2.      Write a program that throws and catches an int, a char and a double exception using multiple handlers.

3.      Explain with an example how an exception propagates through several function calls.

4.      Explain stack unwinding and show with a program how destructors are called.

5.      Write a program with nested try blocks and rethrowing.

6.      Discuss the advantages and limitations of exception handling.

No comments:

Post a Comment