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 ErrorHandling 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 <iostream>
#include <stdexcept>
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 <iostream>
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 <iostream>
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 <iostream>
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 <iostream>
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 <iostream>
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 <iostream>
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 <iostream>
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 <iostream>
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 <iostream>
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 <iostream>
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