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.
- Handles abnormal conditions like Division by zero, Invalid memory access, File handling failures, and Out-of-range errors that occur during program execution.
- Helps maintain program stability by preventing unexpected program termination.
- Instead of abruptly stopping the program when an error occurs, C++ Exception Handling allows you to throw exceptions using throw and handle them gracefully using try and catch blocks.
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
}
#include <iostream>
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
- The exception is thrown inside the try block using throw.
- Program execution immediately stops inside the try block.
- Control transfers to the matching catch block.
- The exception is handled gracefully.
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;
#include <iostream>
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++:
- Built-in Types
- Standard Exceptions
- Custom Exceptions
Throwing Built-in Types
Throwing built-in types is very simple but it does not provide any useful information. For example:
#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 <stdexcept> 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++:

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.
#include <bits/stdc++.h>
using namespace std;
int main() {
vector<int> 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
#include <iostream>
#include <exception>
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,
#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.
#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,
#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.
- Uncaught exceptions cause abnormal program termination.
- A custom terminate handler can be set using set_terminate() to control program termination behavior.
Exception Propagation
Exception propagation tells us how the exception travels in the call stack.
- When an exception is thrown, the current block terminates immediately, and local resources are released (except dynamically allocated memory using new).
- The stack unwinding occurs as the thrown exception propagates through the call stack to look for the matching catch block.
- When the corresponding catch block is found, exception is caught and handled. If not caught, the program terminates
Let's understand the flow using the following example:
#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
- When try block runs, and throws 10, then code after the exception is not executed (as "after throw" is not printed). The object gfg is destroyed (as "Object Destroyed" is printed) and the matching catch block is searched for.
- As the matching catch block is found with integer exception, the code inside the block is executed (as "Exception Caught" is printed). Then the execution of the code after the catch block is continued (as "after catch" is printed).

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:
- noexcept: Tells that the given function is guaranteed to not throw an exception.
- noexcept(false): Tells that the given function may throw an exception. It is default behaviour in case no exception specification is used.
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:
- Separation of concerns: try–catch blocks keep error-handling logic separate from normal program flow, improving readability and maintainability.
- Selective handling: A function can handle only the exceptions it chooses and propagate others to the caller, enabling flexible and layered error handling.
- Explicit exception specification: Functions can throw exceptions using throw, allowing callers to handle or further propagate them.
- Structured error grouping: C++ allows throwing basic types or objects, making it possible to organize and categorize errors using exception class hierarchies.
Best Practices of Exception Handling
- Don't use exceptions for regular control flow. Only use them for exceptional situations (errors).
- Catch exceptions by reference whenever possible to avoid copying overhead and enable polymorphic selection.
- Always catch exceptions by reference to a constant to avoid accidental modification.
- Catching generic exceptions using catch(...) does not provide information about exception type, so better to catch specific exceptions to handle errors appropriately.
- Failing to catch exceptions can cause the program to terminate unexpectedly. Always ensure exceptions are caught and handled properly to maintain stability.
- Not releasing resources (e.g., memory, file handles) after an exception can lead to resource leaks. Use proper cleanup mechanisms such as RAII (destructors, smart pointers).