In this article, we’ll take a look at performing Exception Handling in C++.
Exception Handling is an important topic, especially in the context of Object-Oriented Programming. C++ is similar to Java, with the fact that it can raise and catch Exceptions.
We denote a separate section of the code to handle exceptions, as they make things modular and easier for programmers. If any additional errors are detected, only the exception handling code must be modified.
We must enclose any code which throws an exception under a try
block. So the three major parts of Exception Handling are:
- The
try
block - Throwing an exception, using an optional object, along with the
throw
keyword. - A
catch
block, to handle the Exception. This block is usually immediately after thetry
block. The program will jump to this section immediately after a suitable exception is thrown.
The overall structure of the program will be:
1 2 3 4 5 6 7 |
try { ... throw exception; } catch(object) { ... } |
Let’s investigate this in more detail, in the below sections.
Throwing an Exception in C++
In C++ terms, we call the raising of an exception as throwing an exception.
This is done using the throw
keyword. This can take any object (or a primitive type) and pass it into the exception handling code.
The syntax for throwing an object is:
1 2 3 4 5 6 7 8 9 |
// Must enclose the code in a try block try { int x = 0; call_function(x); if (x < 0) { // Throw the object, if something unwanted happened throw x; } } |
Now, the program is obviously not complete, since there is no catch
block.
Let’s write that now!
Catching Exceptions
We mentioned that the program will jump from the try
block to a suitable catch
block, if available.
A catch
block is of the following structure:
1 2 3 |
catch(int x) { std::cout << "Exception thrown. Object " << x << " caught.n"; } |
Whenever our function_call(x)
makes x become negative, it will throw an exception, and a message will be printed.
Let’s look at the complete program now.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
#include <iostream> void function_call(int& x) { // Do some work, and modify the value of x, so we're passing by reference if (x == 0) { // Set it to some negative value. We'll use this to throw an exception x = -10; // Throw an exception, and pass the object x throw x; } else { x = 100; } } int main() { int x = 0; try { function_call(x); std::cout << "If this line executes, then x != 0n"; } catch(int x) { std::cout << "Exception thrown. Object " << x << " caught.n"; } return 0; } |
Since x
was set to 0 initially, we will throw an exception, and print the modified value of x
( set to -10), showing that it has executed the catch
block.
Also, notice that the line immediately below the function_call(x)
statement never gets executed, since the program directly jumps to catch
.
Output
1 |
Exception thrown. Object -10 caught. |
Catch-All types of Exceptions
What if you want your code to handle all arbitrary kinds of exceptions? C++ introduces a feature which allows you to perform this, using a special matching object using ...
.
To catch all kinds of exceptions, we can write catch(...)
to catch every object that we throw. This is called the Default Exception and is the fallback option, when we cannot match with any of the other exceptions.
To illustrate this, here is an example.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
#include <iostream> #include <string> void function_call(int& x) { // Do some work, and modify the value of x, so we're passing by reference if (x == 0) { // Throw an exception, and pass on the object x throw x; } else if (x < 0) { // If x is negative, throw a random string throw std::string("Default String"); } else { x = 100; } } int main() { for (int i=0; i>=-3; i--) { try { function_call(i); std::cout << "If this line executes, then x != 0n"; } catch(int x) { std::cout << "Exception thrown. Currently, x = " << x << " caught.n"; } catch(...) { std::cout << "Default exception. Currently, i = " << i << " caught.n"; } } return 0; } |
Output
1 2 3 4 |
Exception thrown. Currently, x = 0 caught. Default exception. Currently, i = -1 caught. Default exception. Currently, i = -2 caught. Default exception. Currently, i = -3 caught. |
As you can see, initially, when i = 0
, it goes to the first catch
block, since we return an integer.
But, after that, since we return the string “Default String”, it does not match with the first block, and it goes to the default block.
Unfortunately, C++ does not give us a way to pass the object in the default case, so we cannot get the “Default String” string. This shows that we must use this only for adding functionality to our code, to account for unknown errors.
Passing Pointers and References to Exceptions
This is also possible since we’re working with C++!
A general rule is to handle all the freeing related to the pointers in the exception handling code.
1 2 3 4 5 6 7 8 9 10 11 |
try { call_function(base_ptr); } // Catch the Custom Exception Pointer using our Exception Class catch(MyException* ex_obj) { delete ex_obj; } // Catch the reference to an Exception Class object catch(MyException& ex_obj) { // Do stuff ... } |
Let’s consider the below exception handler class MyException
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
#include <iostream> #include <string> class MyException { public: int err; // Error Code std::string err_msg; // String containing Error Message MyException(int val = 0) { // Base Constructor err = val; switch(val) { case(-1): err_msg = "Case -1"; break; case(-2): err_msg = "Case -2"; break; case(-3): err_msg = "Case -3"; break; default: err_msg = ""; // No error message for other cases } } ~MyException() { std::cout << "Destructor for Exception Class calledn"; } }; void function_call(int& x) { // Do some work, and modify the value of x, so we're passing by reference if (x <= 0) { // Throw an exception, and pass on the object x MyException* ex = new MyException(x); throw ex; // Throw the exception pointer } else { x = 100; } } int main() { for (int i=0; i>=-3; i--) { try { function_call(i); std::cout << "If this line executes, then x != 0n"; } catch(MyException* ex) { std::cout << "Exception thrown. Error Message: " << ex->err_msg << "n"; // Remember to free the pointer to the Exception!!! delete ex; } catch(...) { std::cout << "Default exception. Currently, i = " << i << " caught.n"; } } return 0; } |
Instead of passing on the integer directly, we construct an exception class and pass a pointer to an object, which has the relevant error code and error message.
Here, you can see how powerful using an Exception Class handler for this is!
Not only can we add debug messages to it, in the constructor, but when we actually pass it around, we only move the pointer, which is 8 bytes only! This is something other languages like Java can’t do easily.
Output
1 2 3 4 5 6 7 8 |
Exception thrown. Error Message: Destructor for Exception Class called Exception thrown. Error Message: Case -1 Destructor for Exception Class called Exception thrown. Error Message: Case -2 Destructor for Exception Class called Exception thrown. Error Message: Case -3 Destructor for Exception Class called |
Indeed, this is what we expect! Only for values of -1, -2, and -3, we’ve instructed the constructor to set error messages. For other values, it is simply a NULL
string.
Additional Takeaways for Exception Handling in C++
- When using
try
andcatch
blocks, there is NO implicit type conversion taking place. So achar
is not converted to anint
, when passed to exception handlers. - Always use a
catch
block if you use atry
block. Otherwise, if you throw an exception, this will result in an abnormal termination of the program. - Modularize your code so that your core program logic is different from the Error handling logic. For Exceptions, try to use related Exception Handler classes and polymorphism to handle errors.
- If you use raw pointers, always remember to free them in your
catch
blocks! If you find raw points too troublesome, use smart pointers instead!
I hope you found this article useful for learning more about Exception Handling in C++. If you have any doubts, or even found any mistakes, please do mention them in the comment section below!