Page Content

Tutorials

Exception Handling In C++: Analyzing try, catch, and throw

Exception Handling in C++

C++’s exception handling is a strong tool for handling “exceptional conditions” or run-time irregularities that stop a program from operating normally. These are unforeseen circumstances that might happen, such losing a database connection, running into invalid user input, or forgetting to allocate memory, rather than typical conditions that you desire to happen.

Distinguishing a program’s error-detection and error-resolution components is the main goal of exception handling. In other words, one section of your code can identify an issue that it is unable to fix locally and “throw” an exception to indicate that something went wrong without requiring knowledge of which other sections of the program would “catch” and deal with the issue.

Try, throw, and catch are the three keywords that form the foundation of C++ exception handling.

try Block: This block marks a potential exception-prone portion of code. Program control is moved to a related catch block if an exception is raised inside the try block. Catch clauses cannot access variables stated inside a try block.

throw Expression: Used to indicate the occurrence of an exceptional condition by the program’s detecting component. Throw expressions consist of the keyword throw and an expression whose type determines the exception thrown. Thrown exceptions stop the current function’s execution and prevent the following statements from being executed.

catch Block: After a try block, exception handlers appear. capture blocks capture exceptions of the type declared in parenthesis. If types match, catch block code runs. Execution usually restarts with the statement that comes just after the last catch clause connected to that try block when the catch block is finished.

Types of Exceptions

Generally speaking, exceptions fall into:

Synchronous Exceptions: These arise during program execution as a result of logical errors (e.g., division by zero, overflow, underflow, and out-of-range errors) or errors in the input data. The main purpose of C++ exception handling is for these.

Asynchronous Exceptions: These are brought on by uncontrollable and unrelated occurrences, like disc failures, hardware issues, or keyboard interruptions. Asynchronous events are not handled directly by C++’s exception mechanism.

Flow of Control and Stack Unwinding

The C++ runtime environment starts a procedure known as stack unwinding when an exception is thrown. This includes:

Suspension of Execution: The current code block in which the exception was thrown has its execution instantly paused.

Destruction of Automatic Objects: All fully formed automated (local) objects in each exited stack frame have their destructors called as the exception moves up the call stack. To stop resource leaks, this is essential. However, unless smart pointers or RAII techniques are used to manage them, items created on the heap are not automatically removed.

Search for Handler: Starting at the function where the exception was thrown, the system reverses the call chain to identify a suitable handler.

Program Termination: The program invokes the library function std::terminate(), which by default invokes abort() to terminate the program, if no suitable catch handler is discovered after unwinding all the way to the main function.

Example: Basic Try-Throw-Catch

#include <iostream> // For input/output operations 
#include <stdexcept> // For standard exception classes like runtime_error
// Function that performs division and throws an exception if the denominator is zero
double safeDivision(int a, int b)
{
    if (b == 0) // Check for the error condition 
    {
        // Throw a runtime_error object with a descriptive message 
        throw std::runtime_error("Division by zero condition!"); // The type of the expression determines the exception 
    }
    return (static_cast<double>(a) / b);
}
int main()
{
    int numerator = 10;
    int denominator = 0; // This will cause an exception
    try // Code that might cause an exception is enclosed in a try block 
    {
        std::cout << "Attempting to divide " << numerator << " by " << denominator << std::endl;
        double result = safeDivision(numerator, denominator); // Call the function that might throw
        // This line will NOT execute if an exception is thrown from safeDivision
        std::cout << "Result: " << result << std::endl;
    }
    // Catch block to handle exceptions of type std::runtime_error 
    catch (const std::runtime_error& e) // Catching by const reference is the recommended practice 
    {
        // e.what() returns a C-style string describing the exception 
        std::cerr << "Caught exception: " << e.what() << std::endl; // Error messages are often sent to cerr 
    }
    // Execution continues here after the exception is handled by the catch block 
    std::cout << "Program continues after exception handling." << std::endl;
    return 0;
}

Output

Attempting to divide 10 by 0
Caught exception: Division by zero condition!
Program continues after exception handling.

Standard and Custom Exception Classes

A hierarchy of exception classes, all descended from the base class std::exception, is provided by the C++ Standard Library. The virtual member method what() of this base class returns a string explanation of the exception in C language.

Among the important standard exception classes are:

std::bad_alloc: Thrown when memory allocation by the new operator is unsuccessful.

Std::logic_error: Reading the code may reveal issues like std::invalid_argument or std::out_of_range.

std::runtime_error: Represents runtime-detectable errors like std::overflow_error, std::underflow_error, and std::range_error.

std::bad_cast: Thrown for an incorrect cast by dynamic_cast.

By deriving them from std::exception or one of its specialized derived classes, you may even create your own unique exception classes. This enables you to provide particular error information (such as defective values or the originating function) in the exception object so that the handler may obtain it.

Example: Custom Exception Class and Multiple Catch Blocks

Example for Valid Operation

#include <iostream>  // For input/output
#include <string>    // For std::string in exception messages
#include <exception> // For std::exception base class
// Define a custom exception class derived from std::exception
struct CustomAppError : public std::exception // Your custom base for application errors 
{
    std::string detailMessage;
    int errorCode;
    CustomAppError(const std::string& msg, int code) : detailMessage(msg), errorCode(code) {}
    // Override the virtual what() function to provide custom error messages
    const char* what() const noexcept override
    {
        return detailMessage.c_str();
    }
};
// Derived custom exception for division issues
struct DivisionByZeroException : public CustomAppError // Specific error derived from CustomAppError
{
    DivisionByZeroException() : CustomAppError("Attempted division by zero!", 101) {}
};
// Derived custom exception for invalid input
struct InvalidInputException : public CustomAppError
{
    InvalidInputException(const std::string& input)
        : CustomAppError("Invalid input received: " + input, 201) {}
};
int processInput(int value, int divisor)
{
    if (divisor == 0)
    {
        throw DivisionByZeroException(); // Throw an object of the specific exception type
    }
    if (value < 0 || divisor < 0)
    {
        throw InvalidInputException("Negative values not allowed for this operation.");
    }
    return value / divisor;
}
int main()
{
    // First try block: demonstrate specific exception handling
    try
    {
        std::cout << "--- Scenario : Valid Operation ---" << std::endl;
        std::cout << "Result of 20 / 4: " << processInput(20, 4) << std::endl;
       
    }
    // Catch specific derived exceptions first (from most derived to least derived) 
    catch (const DivisionByZeroException& e) // Catches DivisionByZeroException 
    {
        std::cerr << "Caught specific DivisionByZeroException: " << e.what() << " (Code: " << e.errorCode << ")" << std::endl;
    }
    catch (const InvalidInputException& e) // Catches InvalidInputException
    {
        std::cerr << "Caught specific InvalidInputException: " << e.what() << " (Code: " << e.errorCode << ")" << std::endl;
    }
    catch (const CustomAppError& e) // Catches any CustomAppError (base class) if not caught by derived 
    {
        std::cerr << "Caught CustomAppError (base): " << e.what() << " (Code: " << e.errorCode << ")" << std::endl;
    }
    catch (const std::exception& e) // Catches any standard library exception not explicitly handled 
    {
        std::cerr << "Caught std::exception (general): " << e.what() << std::endl;
    }
    catch (...) // The catch-all handler, catches any type of exception 
    {
        std::cerr << "Caught an unexpected/unknown exception!" << std::endl;
    }
    
    return 0;
}

Output

--- Scenario : Valid Operation ---
Result of 20 / 4: 5

Example for Division by Zero

#include <iostream>  // For input/output
#include <string>    // For std::string in exception messages
#include <exception> // For std::exception base class
// Define a custom exception class derived from std::exception
struct CustomAppError : public std::exception // Your custom base for application errors 
{
    std::string detailMessage;
    int errorCode;
    CustomAppError(const std::string& msg, int code) : detailMessage(msg), errorCode(code) {}
    // Override the virtual what() function to provide custom error messages
    const char* what() const noexcept override
    {
        return detailMessage.c_str();
    }
};
// Derived custom exception for division issues
struct DivisionByZeroException : public CustomAppError // Specific error derived from CustomAppError
{
    DivisionByZeroException() : CustomAppError("Attempted division by zero!", 101) {}
};
// Derived custom exception for invalid input
struct InvalidInputException : public CustomAppError
{
    InvalidInputException(const std::string& input)
        : CustomAppError("Invalid input received: " + input, 201) {}
};
int processInput(int value, int divisor)
{
    if (divisor == 0)
    {
        throw DivisionByZeroException(); // Throw an object of the specific exception type
    }
    if (value < 0 || divisor < 0)
    {
        throw InvalidInputException("Negative values not allowed for this operation.");
    }
    return value / divisor;
}
int main()
{
    // First try block: demonstrate specific exception handling
    try
    {
        
        std::cout << "--- Scenario : Division by Zero ---" << std::endl;
        processInput(10, 0); // This will throw DivisionByZeroException
    }
    // Catch specific derived exceptions first (from most derived to least derived) 
    catch (const DivisionByZeroException& e) // Catches DivisionByZeroException 
    {
        std::cerr << "Caught specific DivisionByZeroException: " << e.what() << " (Code: " << e.errorCode << ")" << std::endl;
    }
    catch (const InvalidInputException& e) // Catches InvalidInputException
    {
        std::cerr << "Caught specific InvalidInputException: " << e.what() << " (Code: " << e.errorCode << ")" << std::endl;
    }
    catch (const CustomAppError& e) // Catches any CustomAppError (base class) if not caught by derived 
    {
        std::cerr << "Caught CustomAppError (base): " << e.what() << " (Code: " << e.errorCode << ")" << std::endl;
    }
    catch (const std::exception& e) // Catches any standard library exception not explicitly handled 
    {
        std::cerr << "Caught std::exception (general): " << e.what() << std::endl;
    }
    catch (...) // The catch-all handler, catches any type of exception [123, 130, 187, 190-195]
    {
        std::cerr << "Caught an unexpected/unknown exception!" << std::endl;
    }
    
    return 0;
}

Output

--- Scenario : Division by Zero ---
Caught specific DivisionByZeroException: Attempted division by zero! (Code: 101)

Example for Invalid Input (Negative)

#include <iostream>  // For input/output
#include <string>    // For std::string in exception messages
#include <exception> // For std::exception base class
// Define a custom exception class derived from std::exception
struct CustomAppError : public std::exception // Your custom base for application errors 
{
    std::string detailMessage;
    int errorCode;
    CustomAppError(const std::string& msg, int code) : detailMessage(msg), errorCode(code) {}
    // Override the virtual what() function to provide custom error messages
    const char* what() const noexcept override
    {
        return detailMessage.c_str();
    }
};
// Derived custom exception for division issues
struct DivisionByZeroException : public CustomAppError // Specific error derived from CustomAppError
{
    DivisionByZeroException() : CustomAppError("Attempted division by zero!", 101) {}
};
// Derived custom exception for invalid input
struct InvalidInputException : public CustomAppError
{
    InvalidInputException(const std::string& input)
        : CustomAppError("Invalid input received: " + input, 201) {}
};
int processInput(int value, int divisor)
{
    if (divisor == 0)
    {
        throw DivisionByZeroException(); // Throw an object of the specific exception type
    }
    if (value < 0 || divisor < 0)
    {
        throw InvalidInputException("Negative values not allowed for this operation.");
    }
    return value / divisor;
}
int main()
{
    // First try block: demonstrate specific exception handling
    
    std::cout << "\n--- Scenario : Invalid Input (Negative) ---" << std::endl;
    try
    {
        processInput(-5, 2); // This will throw InvalidInputException
    }
    catch (const InvalidInputException& e)
    {
        std::cerr << "Caught specific InvalidInputException: " << e.what() << " (Code: " << e.errorCode << ")" << std::endl;
    }
    catch (const CustomAppError& e)
    {
        std::cerr << "Caught CustomAppError (base): " << e.what() << " (Code: " << e.errorCode << ")" << std::endl;
    }
    // ... other catch blocks as needed
    std::cout << "\nProgram execution completed." << std::endl;
    return 0;
}

Output

--- Scenario : Invalid Input (Negative) ---
Caught specific InvalidInputException: Invalid input received: Negative values not allowed for this operation. (Code: 201)
Program execution completed.

Rethrowing Exceptions

There are situations where a catch block must complete some local cleanup or logging before passing the exception higher up the call stack. A rethrow statement, which is just throw; without an operand, is used to accomplish this. Only within a catch block or a function that is called either directly or indirectly from a catch block is a rethrow allowed.

Function try Blocks for Constructors

Catch clauses can be linked to both the body and the initialization (or destruction) phases of a constructor or destructor using a unique syntax known as a function try block. This is crucial because exceptions may be raised when base classes or member objects are initialized, which takes place prior to the constructor body being entered.

Exception Specifications (noexcept)

In the past, C++ permitted exception specifications to specify which exceptions a function can throw (throw(type-list) or throw()). However, because to their potential for runtime complexity and difficulty in effectively utilising, non-empty exception specifications (throw(Bad, Worse)) have been deprecated in current C++ (C++11 and later).

C++11 introduced the noexcept specifier to prevent functions from throwing exceptions. Noexcept is an empty throw() specification. It can help with compiler optimizations and make code reasoning easier by marking routines as noexcept.

Compiler Support

Utilizing compiler options to enable exception support is frequently required when producing C++ code, particularly when utilising the Standard Library. By supposing extern “C” functions do not throw C++ exceptions, the /EHsc flag, for instance, enables optimisations with the Microsoft C/C++ Optimising Compiler and offers the required support for synchronous (C++) exceptions. Both synchronous C++ exceptions and asynchronous Windows Structured Exception Handling (SEH) exceptions could be caught by using /EHa.

You can also read Containership In C++: Understanding “Has-A” Relationships

Agarapu Geetha
Agarapu Geetha
My name is Agarapu Geetha, a B.Com graduate with a strong passion for technology and innovation. I work as a content writer at Govindhtech, where I dedicate myself to exploring and publishing the latest updates in the world of tech.
Index