Contents
Exception Handling
C++ Exception Handling: A Concept Overview
In C++, exceptions are unexpected events or errors that occur during the execution of a program. When such an event occurs, the program flow is interrupted, and if the exception is not handled, it can cause the program to terminate abnormally. Exception handling provides a way to manage these runtime anomalies and keep the program running by transferring control from one part of the code to another where the exception can be dealt with.
What is a C++ Exception?
An exception is a problem that arises during the execution of a program, leading to an abnormal termination if not handled. Exceptions occur at runtime, meaning the problem is only encountered when the program is running.
Types of Exceptions in C++
In C++, exceptions can be categorized into two types:
1. Synchronous Exceptions: These occur due to errors like dividing by zero, invalid input, or logic errors that can be anticipated by the programmer.
2. Asynchronous Exceptions: These are exceptions caused by external events beyond the program’s control, such as hardware failure, system interrupts, etc.
Exception Handling Mechanism in C++
C++ provides a built-in mechanism for exception handling through three main keywords: try
, catch
, and throw
.
Syntax of try-catch in C++:
try {
// Code that might throw an exception
throw SomeExceptionType("Error message");
}
catch( ExceptionType e ) {
// Code to handle the exception
}
1. Separation of Error–Handling Code from Normal Code: Unlike traditional methods where error-checking code is mixed with normal logic, exceptions keep the code cleaner and more readable.
2. Granular Handling: A function can throw many exceptions but choose to handle only specific ones. The rest can be caught by the calling function.
3. Grouping Error Types: C++ allows you to group exceptions into classes and objects, making it easier to categorize and manage different error types.
Example 1: Basic try-catch Mechanism
#include
#include
using namespace std;
int main() {
try {
int numerator = 10;
int denominator = 0;
int result;
if (denominator == 0) {
throw runtime_error("Division by zero is not allowed.");
}
result = numerator / denominator;
cout << "Result: " << result << endl;
}
catch (const exception& e) {
cout << "Exception caught: " << e.what() << endl;
}
return 0;
}
Output:
Exception caught: Division by zero is not allowed.
In this example, dividing by zero throws a runtime_error
exception, which is caught by the catch
block.
Example 2: Throwing and Catching Exceptions
#include
using namespace std;
int main() {
int value = -1;
cout << "Before try block" << endl;
try {
cout << "Inside try block" << endl;
if (value < 0) {
throw value;
}
cout << "After throw (will not be executed)" << endl;
}
catch (int ex) {
cout << "Caught an exception: " << ex << endl;
}
cout << "After catch block" << endl;
return 0;
}
Output:
Before try block
Inside try block
Caught an exception: -1
After catch block
Here, a negative value throws an exception, and control transfers to the catch
block, where the exception is handled.
Properties of Exception Handling in C++
Property 1: Catch-All Block : –C++ provides a catch-all mechanism to catch exceptions of any type using catch(...)
.
#include
using namespace std;
int main() {
try {
throw 10; // Throwing an integer
}
catch (char*) {
cout << "Caught a char exception." << endl;
}
catch (...) {
cout << "Caught a default exception." << endl;
}
return 0;
}
Output:
Caught a default exception.
Property 2: Uncaught Exceptions Terminate the Program :- If an exception is not caught anywhere in the code, the program terminates abnormally.
#include
using namespace std;
int main() {
try {
throw 'x'; // Throwing a char
}
catch (int) {
cout << "Caught int" << endl;
}
return 0;
}
Output:
terminate called after throwing an instance of 'char'
Property 3: Unchecked Exceptions :- C++ does not enforce checking for exceptions at compile time. However, it is recommended to list possible exceptions.
#include
using namespace std;
void myFunction(int* ptr, int x) throw(int*, int) {
if (ptr == nullptr) throw ptr;
if (x == 0) throw x;
}
int main() {
try {
myFunction(nullptr, 0);
}
catch (...) {
cout << "Caught exception from myFunction" << endl;
}
return 0;
}
Output:
Caught exception from myFunction
Exception Handling in C++
Exception handling is a technique used to manage runtime anomalies or abnormal conditions that a program may encounter during its execution. These anomalies can be categorized into two types:
1. Synchronous Exceptions: These occur due to issues in the program’s input or logic, such as division by zero.
2. Asynchronous Exceptions: These are beyond the program’s control, such as hardware failures like a disk crash.
In C++, the exception handling mechanism uses three primary keywords:
- try: Defines a block of code where exceptions can occur.
- catch: Handles exceptions that were thrown within the corresponding try block.
- throw: Used to throw an exception when a problem arises. It can also be used to declare exceptions that a function may throw but not handle itself.
Problem Statement
You need to create a class named Numbers
with two data members, a
and b
. This class should have methods to:
- Find the greatest common divisor (GCD) of the two numbers using an iterative approach.
- Check if a number is prime and throw a custom exception if the number is prime.
Define a custom exception class MyPrimeException
to handle cases where a number is found to be prime.
Solution Approach
1. Define a class Number with two private data members, a and b. Include two member functions:
- gcd(): Computes the greatest common divisor
- (GCD) using an iterative algorithm.
isPrime(int n): Checks whether a number is prime.
2. Use a constructor to initialize the two data members.
3. Create another class, MyPrimeException, which will be invoked when a number is identified as prime, and an exception is thrown.
#include
using namespace std;
// Class declaration
class Number {
private:
int a, b;
public:
// Constructor
Number(int x, int y) : a(x), b(y) {}
// Function to calculate the GCD of two numbers
int gcd() {
int num1 = a;
int num2 = b;
// Continue until both numbers are equal
while (num1 != num2) {
if (num1 > num2)
num1 = num1 - num2;
else
num2 = num2 - num1;
}
return num1;
}
// Function to check if the given number is prime
bool isPrime(int n) {
if (n <= 1)
return false;
for (int i = 2; i * i <= n; i++) {
if (n % i == 0)
return false;
}
return true;
}
};
// Custom exception class
class MyPrimeException {
public:
void displayMessage() {
cout << "Prime number detected. Exception thrown." << endl;
}
};
int main() {
int x = 13, y = 56;
Number num1(x, y);
// Display the GCD of the two numbers
cout << "GCD of " << x << " and " << y << " is = " << num1.gcd() << endl;
// Prime check and exception handling
try {
if (num1.isPrime(x)) {
throw MyPrimeException();
}
cout << x << " is not a prime number." << endl;
}
catch (MyPrimeException& ex) {
ex.displayMessage();
}
try {
if (num1.isPrime(y)) {
throw MyPrimeException();
}
cout << y << " is not a prime number." << endl;
}
catch (MyPrimeException& ex) {
ex.displayMessage();
}
return 0;
}
Output:
GCD of 13 and 56 is = 1
Prime number detected. Exception thrown.
56 is not a prime number.
Explanation:
1. Class Number: This class encapsulates two private data members, a and b. It has:
- A constructor that initializes these members.
- A method gcd() that computes the GCD of the two numbers using an iterative algorithm.
- A method isPrime(int n) that checks whether a number is prime by dividing it by every integer from 2 up to the square root of the number.
2. Class MyPrimeException: This class is a simple custom exception class used to handle prime number cases.
3. Main Function:
- The gcd() method is used to calculate and display the GCD of the two numbers.
- If either number is prime, a
- MyPrimeException is thrown, which is caught and handled by printing a custom message.
Stack Unwinding in C++
Stack unwinding refers to the process of cleaning up the function call stack when an exception is thrown. This involves removing function entries from the call stack at runtime and destroying local objects in the reverse order in which they were created. In C++, when an exception occurs, the runtime system searches the function call stack for an appropriate exception handler. If no handler is found in the current function, it continues searching the previous functions in the call stack until an appropriate handler is found. Stack unwinding occurs when the functions without handlers are removed from the stack, and destructors for local objects are called.
The concept of stack unwinding is closely tied to exception handling in C++. If an exception is thrown and not caught in the same function, the destructors for all automatic objects created since entering that function are invoked before moving to the next function in the call stack. This process continues until the exception is caught or the program terminates.
Example
Here’s a modified version of the code to demonstrate stack unwinding:
#include
using namespace std;
// Sample class with a constructor and destructor
class Sample {
public:
Sample(const string& name) : objName(name) {
cout << objName << " created." << endl;
}
~Sample() {
cout << objName << " destroyed." << endl;
}
private:
string objName;
};
// Function that throws an exception
void func1() throw(int) {
Sample obj1("Object in func1");
cout << "func1() Start" << endl;
throw 404;
cout << "func1() End" << endl; // This line will never execute
}
// Function that calls func1
void func2() throw(int) {
Sample obj2("Object in func2");
cout << "func2() Start" << endl;
func1();
cout << "func2() End" << endl; // This line will never execute
}
// Function that calls func2 and catches the exception
void func3() {
Sample obj3("Object in func3");
cout << "func3() Start" << endl;
try {
func2();
}
catch (int e) {
cout << "Caught Exception: " << e << endl;
}
cout << "func3() End" << endl;
}
// Driver code
int main() {
func3();
return 0;
}
Output:
Object in func3 created.
func3() Start
Object in func2 created.
func2() Start
Object in func1 created.
func1() Start
Object in func1 destroyed.
Object in func2 destroyed.
Caught Exception: 404
func3() End
Object in func3 destroyed.
Explanation:
1. func1 throws an exception (404), which causes the control to leave the function.
- Before exiting, the local object obj1 in func1 is destroyed as part of stack unwinding.
2. func2 calls func1, but since func1 throws an exception and func2 does not handle it, the control leaves func2 as well.
- The local object obj2 in func2 is destroyed before func2 exits.
3. Finally, func3 calls func2 and contains an exception handler. When func2 throws the exception, func3 catches it.
- The catch block prints the caught exception (404) and the execution continues.
- The local object obj3 in func3 is destroyed at the end of the function.
Important Points:
- In the process of stack unwinding, destructors for local objects are called as each function is removed from the call stack.
- Any code after the exception is thrown in func1 and func2 does not execute.
- Stack unwinding ensures that resources are properly cleaned up, even when exceptions occur, preventing memory leaks or dangling resources.
User-defined Custom Exception with class
In C++, exceptions can be thrown using user-defined class types, allowing for more structured and meaningful error handling. For example, a custom class can be created to represent a specific kind of exception, and objects of that class can be thrown within a try
block and caught in a catch
block.
Example 1: Exception Handling with a Single Class
In this example, we define a simple class and throw an object of this class type in the try
block. The object is caught in the corresponding catch
block.
#include
using namespace std;
class CustomException {
};
int main() {
try {
throw CustomException();
}
catch (CustomException ce) {
cout << "Caught an exception of CustomException class" << endl;
}
}
Output:
Caught an exception of CustomException class
Explanation:
Here, we define an empty class CustomException
. In the try
block, an object of this class is thrown using throw CustomException()
. The catch
block catches the exception and prints a message.
Example 2: Exception Handling with Multiple Classes
This example demonstrates how exceptions can be handled using multiple user-defined classes. Depending on the condition, different class objects are thrown and handled accordingly.
#include
using namespace std;
class ExceptionType1 {
};
class ExceptionType2 {
};
int main() {
for (int i = 1; i <= 2; i++) {
try {
if (i == 1)
throw ExceptionType1();
else if (i == 2)
throw ExceptionType2();
}
catch (ExceptionType1 et1) {
cout << "Caught an exception of ExceptionType1 class" << endl;
}
catch (ExceptionType2 et2) {
cout << "Caught an exception of ExceptionType2 class" << endl;
}
}
}
Output:
Caught an exception of ExceptionType1 class
Caught an exception of ExceptionType2 class
Example 3: Exception Handling with Inheritance
C++ exception handling can also involve inheritance. When an exception is thrown by a derived class, it can be caught by a catch
block that handles the base class, due to the nature of polymorphism in inheritance.
Example:
#include
using namespace std;
class BaseException {
};
class DerivedException : public BaseException {
};
int main() {
for (int i = 1; i <= 2; i++) {
try {
if (i == 1)
throw BaseException();
else if (i == 2)
throw DerivedException();
}
catch (BaseException be) {
cout << "Caught an exception of BaseException class" << endl;
}
catch (DerivedException de) {
cout << "Caught an exception of DerivedException class" << endl;
}
}
}
Output:
Caught an exception of BaseException class
Caught an exception of BaseException class
Example 4: Exception Handling in Constructors
Exception handling can also be implemented within constructors. Though constructors cannot return values, exceptions can be used to signal errors during object creation.
Example:
#include
using namespace std;
class Example {
int number;
public:
Example(int x) {
try {
if (x == 0)
throw "Zero is not allowed";
number = x;
display();
}
catch (const char* msg) {
cout << "Exception caught: " << msg << endl;
}
}
void display() {
cout << "Number = " << number << endl;
}
};
int main() {
// Constructor is called and exception is handled
Example obj1(0);
cout << "Creating another object..." << endl;
Example obj2(5);
}
Output:
Exception caught: Zero is not allowed
Creating another object...
Number = 5