Exception Handling in C++ (original) (raw)

Exception Handling in C++ is a mechanism used to handle runtime errors and abnormal conditions, allowing a program to continue execution smoothly even when unexpected situations occur.

try-catch Block

C++ provides an inbuilt feature for handling exceptions using try and catch block. It is an exception handling mechanism where the code that may cause an exception is placed inside the try block and the code that handles the exception is placed inside the catch block.

Systax

try {
// Code that may throw exception
}
catch (ExceptionType e) {
// Exception handling code
}

C++ `

#include using namespace std;

int main() {

try {
    int a = 10, b = 0;

    if (b == 0) {
        throw "Division by zero is not allowed";
    }

    cout << a / b;
}

catch (const char* msg) {
    cout << "Exception Caught: " << msg;
}

return 0;

}

`

Output

Exception Caught: Division by zero is not allowed

Explanation

Throwing Exceptions

Throwing exception means returning some kind of value that represent the exception from the try block. The matching catch block is found using the type of the thrown value. The throw keyword is used to throw the exception.

Syntax

throw value;

C++ `

#include using namespace std;

int main() {

int x = 7;

try {

    if (x % 2 != 0) {

        // Throwing integer exception
        throw -1;
    }
}

catch (int e) {
    cout << "Exception Caught: " << e;
}

return 0;

}

`

Output

Exception Caught: -1

Types of Exceptions in C++

There are mainly three types of exceptions in C++:

Throwing Built-in Types

Throwing built-in types is very simple but it does not provide any useful information. For example:

C++ `

#include <bits/stdc++.h> using namespace std;

int main() { int x = 7; try { if (x % 2 != 0) {

        // Throwing int
        throw -1;
    }
}

// Catching int
catch (int e) {
    cout << "Exception Caught: " << e;
}
return 0;

}

`

Output

Exception Caught: -1

Here, we have to make decision based on the value thrown. It is not much different from handling errors using if else. There is a better technique available in C++. Instead of throwing simple values, we can throw objects of classes that contains the information about the nature of exception in themselves.

Throwing Standard Exceptions

Standard exceptions are the set of classes that represent different types of common exceptions. All these classes are defined inside header file and mainly derived from std::exception class which act as the base class for inbuilt exceptions. The below image shows standard exceptions hierarchy in C++:

cpp-exception-hierarchy

These exceptions are thrown by C++ library components so we should know how to handle them. The **what() method is present in every standard exception to provide information about the exception itself.

For example, the .at() method throws an out_of_range exception when the element with given index does not exists.

C++ `

#include <bits/stdc++.h> using namespace std;

int main() { vector v = {1, 2, 3};

try {
    // Accessing out of bound element
    v.at(10);
}
catch (out_of_range e) {
    cout << "Caught: " << e.what();
}
return 0;

}

`

Output

Caught: vector::_M_range_check: __n (which is 10) >= this->size() (which is 3)

**Note: We can also manually throw standard exceptions using throw statement.

Throwing Custom Exceptions

When the standard exceptions cannot satisfy our requirement, we can create a custom exception class. It is recommended to inherit standard exception in this class to provide seamless integrity with library components though, it is not compulsory.

Here’s a complete example of a custom exception in C++ with code

C++ `

#include #include using namespace std;

// Custom exception class class NegativeValueException : public exception { private: int value; public: // Constructor NegativeValueException(int val) : value(val) {}

// Override what() method
const char* what() const noexcept override {
    return "Negative value error occurred!";
}

// Optional: method to get the invalid value
int getValue() const {
    return value;
}

};

// Function that throws the custom exception void checkValue(int x) { if (x < 0) { throw NegativeValueException(x); } else { cout << "Value is: " << x << endl; } }

int main() { int numbers[] = {10, -5, 20};

for (int n : numbers) {
    try {
        checkValue(n);
    }
    catch (NegativeValueException &e) {
        cout << "Exception caught: " << e.what() 
             << " Value = " << e.getValue() << endl;
    }
}

return 0;

}

`

Output

Value is: 10 Exception caught: Negative value error occurred! Value = -5 Value is: 20

Catching Exceptions

As already told, the catch block is used to catch the exceptions thrown in the try block. The catch block takes one argument, which should be of the same type as the exception.

catch (ExceptionType e) {
// Handling code
}

Here, e is the name given to the exception. Statements inside the catch block will be executed if the exception of exceptionType is thrown in try block.

Catching Multiple Exceptions

There can be multiple catch blocks associated with a single try block to handle multiple types of exceptions. For example:

try {
// Code that might throw an exception
}
catch (type1 e) {
// executed when exception is of type1
}
catch (type2 e) {
// executed when exception is of type2
}
catch (...) {
// executed when no matching catch is found
}

In the above code, the last statement catch(...) creates a catch-all block which is executed when none of the above catch statements are matched. For example,

C++ `

#include <bits/stdc++.h> using namespace std;

int main() {

// Code that might throw an exception
try {
    int choice;
    cout << "Enter 1 for invalid argument, "
        << "2 for out of range: ";
    cin >> choice;

    if (choice == 1) {
        throw invalid_argument("Invalid argument");
    }
    else if (choice == 2) {
        throw out_of_range("Out of range");
    }
    else {
        throw "Unknown error";
    }

}

// executed when exception is of type invalid_argument
catch (invalid_argument e) {
    cout << "Caught exception: " << e.what() << endl;
}

// executed when exception is of type out_of_range
catch (out_of_range e) {
    cout << "Caught exception: " << e.what() << endl;
}

// executed when no matching catch is found
catch (...) {
    cout << "Caught an unknown exception." << endl;
}
return 0;

}

`

**Output 1

Enter 1 for invalid argument, 2 for out of range: 2
Caught exception: Out of range

**Output 2

Enter 1 for invalid argument, 2 for out of range: 1
Caught exception: Invalid argument

**Output 3

Enter 1 for invalid argument, 2 for out of range: 10
Caught an unknown exception.

Catch by Value or Reference

Just like function arguments, the catch block can catch exceptions either by value or by reference. Both of the methods have their own advantage.

Catch by Value

Catching exceptions by value creates a new copy of the thrown object in the catch block. Generally, the exceptions objects are not very large so there is not much overhead of creating copies.

C++ `

#include <bits/stdc++.h> using namespace std;

int main() { try { throw runtime_error("This is runtime exception"); }

// Catching by value
catch (runtime_error e) {
    cout << "Caught: " << e.what();
}

return 0;

}

`

Output

Caught: This is runtime exception

Catch by Reference

Catch by reference method just pass the reference to the exception thrown instead of creating a copy. Although it reduces the copy overhead, it is not the primary advantage of this method. The main advantage of this method is in catching polymorphic exception types. For example,

C++ `

#include <bits/stdc++.h> using namespace std;

int main() { try { throw runtime_error("This is runtime exception"); }

// Catching by Reference
catch (exception& e) {
    cout << "Caught: " << e.what();
}

return 0;

}

`

Output

Caught: This is runtime exception

In the above example, we were able to catch the **runtime_error exception using the reference to **exception class. This is very helpful in handling exceptions derived from other exceptions like in standard exception hierarchy.

Handle Uncaught Exceptions

If an exception is not caught by any catch block, the program terminates automatically using the default exception handler. C++ also allows customizing this behavior using the set_terminate() function.

Exception Propagation

Exception propagation tells us how the exception travels in the call stack.

Let's understand the flow using the following example:

C++ `

#include <bits/stdc++.h> using namespace std;

// A dummy class class GfG { public: GfG() { cout << "Object Created" << endl; } ~GfG() { cout << "Object Destroyed" << endl; } };

int main() { try { cout << "Inside try block" << endl; GfG gfg; throw 10; cout << "After throw" << endl; } catch (int e) { cout << "Exception Caught" << endl; } cout << "After catch"; return 0; }

`

Output

Inside try block Object Created Object Destroyed Exception Caught After catch

Explanation

try_and_catch

Nested Try Catch Blocks

In C++, try-catch blocks can be defined inside another try or catch blocks. For example:

try {
// Code...... throw e2
try {
// code..... throw e1
}
catch (eType1 e1) {
// handling exception
}
}
catch (eType e2) {
// handling exception
}

If the exception thrown in the inner try block is of type eType2 instead of eType1, then it will be handled by the outer exception block. Refer to the given article to know more - Nested Try Blocks in C++

Rethrowing an Exception

Rethrowing an exception in C++ involves catching an exception within a try block and instead of dealing with it locally throwing it again to be caught by an outer catch block. By doing this, we preserve the type and details of the exception ensuring that it can be handled at the appropriate level within our program.

This approach is generally used when handling exception at multiple levels.

Exception Specification

C++ provides exception specifications to specify that the given function may or may not throw an exception. In modern C++, there are two types of exception specifications:

These exception specifications are used in the function declaration as shown:

void func1(int a) noexcept {
...
}
void func2(int b) noexcept(false) {
...
}

There were other exception specifications, but they have been deprecated since C++ 11.

Need for Exception Handling

Errors can be handled using conditional checks (as in C), but exception handling offers a cleaner and more robust approach with the following advantages:

Best Practices of Exception Handling