Exception handling in C++





Exception handling in C++ is a mechanism that allows a program to handle unexpected or exceptional situations, often referred to as "exceptions." Exceptions are used to manage errors or abnormal situations that occur during the execution of a program.

In C++, when an error occurs or an exceptional situation arises, the program can throw an exception. This means the program generates an object (usually derived from the std::exception class) that contains information about the error. The exception is then propagated through the call stack until it is caught by an appropriate exception handler.

The key components of exception handling in C++ are:

  1. try block: The code that might throw an exception is placed inside the try block. If an exception is thrown within the try block, the control immediately jumps to the nearest catch block.
  2. catch block: The catch block follows the try block and specifies the type of exception it can handle. If an exception of the specified type is thrown, the corresponding catch block is executed. Multiple catch blocks can be used to handle different types of exceptions.
  3. throw statement: The throw statement is used to explicitly throw an exception. When the throw statement is encountered in the try block, the program looks for a matching catch block to handle the thrown exception.

List of C++ Exception Classes

In C++, exception classes are used to represent different types of errors and exceptional situations that can occur during program execution. Here is a list of some common exception classes:

  • std::exception: The base class for all standard exceptions.
  • std::bad_alloc: Thrown when a dynamic memory allocation fails due to insufficient memory.
  • std::bad_cast: Thrown when a dynamic_cast operation fails during runtime type information (RTTI) casting.
  • std::bad_exception: Thrown when an exception cannot be caught by a catch block.
  • std::logic_error: Base class for exceptions related to errors in the program's logic.
    • std::invalid_argument: Thrown when an invalid argument is passed to a function.
    • std::domain_error: Thrown when a mathematical function is used with arguments that are outside of its domain.
    • std::length_error: Thrown when an object exceeds its maximum allowable size.
  • std::runtime_error: Base class for exceptions that represent runtime errors.
    • std::range_error: Thrown when a value is out of range, such as an index outside the valid range of a container.
    • std::overflow_error: Thrown when an arithmetic overflow occurs.
    • std::underflow_error: Thrown when an arithmetic underflow occurs.
    • std::system_error: Thrown when a system error is encountered, such as file I/O or network-related issues.
  • std::bad_typeid: Thrown when an invalid typeid operation is performed.
  • std::bad_function_call: Thrown when a std::function object is called without a valid callable target.

In C++, when an exception is thrown and not caught, it will lead to program termination. Here's a simple program that demonstrates this:

#include <iostream>
#include <stdexcept>

void simulateException() {
    throw std::runtime_error("This is an unhandled exception!");
}

int main() {
    simulateException();

    // This code will never be reached if an exception is thrown
    std::cout << "This line will not be executed." << std::endl;

    return 0;
}
#include <iostream>
using namespace std;

float division(int x, int y) {
   
    return static_cast(x) / y;
}

int main() {
    int i = 50;
    int j = 0;
    float k = 0;

    k = division(i, j);

    cout << k << endl;

    return 0;
}

Here's an alternate version of your program that performs division by explicitly checking for a zero divisor and handling it without relying on exceptions:

#include <iostream>
using namespace std;

bool isDivisorZero(int y) {
    return y == 0;
}

float division(int x, int y) {
    if (isDivisorZero(y)) {
        cout << "Error: Division by zero is undefined." << endl;
        return 0.0;  // You can choose a meaningful default value or handle it differently
    }
    return static_cast(x) / y;
}

int main() {
    int i = 50;
    int j = 0;
    float k = 0;

    k = division(i, j);

    cout << k << endl;

    return 0;
}

C++ program that demonstrates the use of exceptions and how to handle them:

#include <iostream>
#include <stdexcept>

using namespace std; // Use the standard namespace to simplify code

int divide(int numerator, int denominator) {
    if (denominator == 0) {
        throw invalid_argument("Denominator cannot be zero");
    }
    return numerator / denominator;
}

int main() {
    try {
        int numerator, denominator, result;

        // Get user input
        cout << "Enter numerator: ";
        cin >> numerator;

        cout << "Enter denominator: ";
        cin >> denominator;

        // Perform division and handle exceptions
        result = divide(numerator, denominator);
        cout << "Result: " << result << endl;

    } catch (const invalid_argument& e) {
        cerr << "Error: " << e.what() << endl;
    } catch (const exception& e) {
        // Catch any other exceptions
        cerr << "An unexpected error occurred: " << e.what() << endl;
    }

    return 0;
}


There is a special catch block called the ‘catch-all’ block, written as catch(…), that can be used to catch all types of exceptions.

A specific type of catch block known as the 'catch all' block, denoted as catch(...), is designed to handle exceptions of any type. In the given program, an integer is thrown as an exception. However, since there is no catch block explicitly designed for integers, the catch(...) block will be triggered to handle the exception.

#include <iostream>
#include <stdexcept>

using namespace std; // Use the standard namespace to simplify code

int main() {
    try {
        // Attempt to perform some operation that may throw an exception
        throw 42; // Throwing an integer as an example

    } catch (const invalid_argument& e) {
        cerr << "Caught invalid_argument: " << e.what() << endl;

    } catch (const runtime_error& e) {
        cerr << "Caught runtime_error: " << e.what() << endl;

    } catch (...) {
        // Catch all other types of exceptions
        cerr << "Caught an unknown exception" << endl;
    }

    return 0;
}

In this example, if an int is thrown (as indicated by throw 42), the catch-all block catch (...) will be executed because there is no specific catch block for int. The ... in catch (...) means "catch any type of exception."

However, it's generally considered good practice to catch specific exceptions whenever possible, as it allows for more targeted handling and better code understanding. Using a catch-all block might make it harder to diagnose and handle specific issues in a meaningful way.



Implicit type conversion doesn’t happen for primitive types in Exception Handling

In C++, implicit type conversion (also known as type coercion or type promotion) typically does occur for primitive types, and this behavior is not affected by exception handling directly. Implicit type conversion is the automatic conversion of one data type to another by the compiler to perform certain operations. However, the rules for type conversion might be different based on the context.

When it comes to exception handling in C++, it's important to understand that exceptions are typically thrown and caught based on the type of the exception object. The type of the exception object is specified in the throw statement and is usually a class type (derived from std::exception or some other base class). The catching mechanism involves matching the type of the thrown object with the type specified in the catch block.

Here's a simple example to illustrate how implicit type conversion can be involved in exception handling:

#include <iostream>
using namespace std;

int main()
{
    try {
        throw 'a';
    }
    catch (int x) {
        cout << "Caught " << x;
    }
    catch (...) {
        cout << "Default Exception\n";
    }
    return 0;
}