Exception handling is a crucial feature in C++ that allows you to gracefully handle and recover from exceptional situations or errors that may occur during the execution of a program. Exception handling provides a mechanism to catch and handle exceptions, preventing abnormal program termination and enabling robust error handling.
The process of exception handling involves three key components: try block, throw statement, and catch block.
1. Try Block:
A try block is used to enclose the code that may potentially throw an exception. It identifies the portion of code where an exception can occur. If an exception is thrown within the try block, the program execution jumps to the nearest matching catch block.
Syntax:
try
{
// Code that may throw an exception
}
catch (ExceptionType1 ex1)
{
// Exception handling for ExceptionType1
}
catch (ExceptionType2 ex2)
{
// Exception handling for ExceptionType2
}
The try block consists of a sequence of statements where an exception might occur. If an exception is thrown within the try block, the program execution is transferred to the nearest catch block that can handle the thrown exception. If no appropriate catch block is found, the program terminates abruptly.
2. Throw Statement:
The throw statement is used to explicitly throw an exception. It is typically used when an error condition or exceptional situation is encountered. When a throw statement is encountered, the program control transfers to the nearest catch block that can handle the thrown exception.
Syntax:
throw exception; // Throws an exception of a specified type
throw; // Re-throws the currently caught exception
3. Catch Block:
A catch block is used to handle a specific type of exception that can be thrown within the try block. It specifies the exception type it can catch and provides the necessary code to handle the exception.
Syntax:
catch (ExceptionType ex)
{
// Exception handling code
}
The catch block is placed immediately after the try block and consists of the catch keyword followed by the exception type in parentheses. When an exception is thrown within the try block, the program control jumps to the catch block that matches the thrown exception's type.
Exception handling can also include a catch block without any specified exception type, which acts as a catch-all block to handle any type of exception that is not handled by the preceding catch blocks.
Syntax:
catch (...)
{
// Exception handling code for any unhandled exception
}
Here's an example that demonstrates the use of exception handling in C++:
#include <iostream>
using namespace std;
int divide(int dividend, int divisor)
{
if (divisor == 0)
string str = "Division by zero";
throw str;
return dividend / divisor;
}
int main()
{
try
{
int result = divide(10, 0);
cout << "Result: " << result << endl;
}
catch (string& ex)
{
cout << "Exception caught: " << ex << endl;
}
catch (...)
{
cout << "Unknown exception caught" << endl;
}
return 0;
}
In this example, the `divide` function attempts to perform division between `dividend` and `divisor`. If the divisor is `0`, it throws a exception with the message "Division by zero".
Rules and Conditions for Exception Handling:
1. Exceptions should be used for exceptional cases:
Exceptions are designed to handle exceptional situations that occur at runtime, such as errors or exceptional conditions that prevent normal program execution. It is recommended to use exceptions sparingly and only for cases where an error or exceptional condition has occurred.
2. Exceptions should be thrown when an error or exceptional condition occurs:
When an error or exceptional condition is encountered in the code, an exception should be thrown. This is done using the `throw` statement, which allows you to raise an exception with a specific type.
3. Exceptions should be caught with appropriate catch blocks:
Exceptions are caught using catch blocks. Catch blocks handle specific types of exceptions and provide the necessary code to handle or respond to the exception. Catch blocks are placed after the try block and can be chained to handle different types of exceptions.
4. Catch blocks should be ordered from the most specific to the most general:
When multiple catch blocks are used, they should be ordered from the most specific exception type to the most general. This ensures that exceptions are caught and handled appropriately. If catch blocks are ordered incorrectly, more general catch blocks may catch exceptions that should be handled by more specific catch blocks.
5. Catch blocks should specify the exception type:
Each catch block should specify the type of exception it can handle. This is done by specifying the exception type in the catch block's parentheses. By specifying the exception type, the catch block will only handle exceptions of that type or its derived types.
6. Catch blocks can catch exceptions by reference or by value:
Catch blocks can catch exceptions either by reference or by value. Catching by reference allows direct access to the original exception object and is generally preferred to avoid unnecessary object copying.
7. Catch blocks can re-throw exceptions:
Catch blocks have the option to re-throw the caught exception using the `throw` statement. This allows exceptions to be caught at a higher level and re-thrown for further handling or reporting.
8. Catch blocks can have a catch-all block:
A catch-all block can be used to catch any unhandled exceptions. It is defined using the ellipsis (`...`) syntax in the catch block. The catch-all block should be placed at the end, after all other catch blocks, as a last resort.
9. Uncaught exceptions terminate the program:
If an exception is thrown and not caught by any catch block, the program terminates abruptly. Therefore, it is essential to handle exceptions appropriately to prevent unexpected program termination.
By following these rules and conditions, you can effectively work with exception handling in C++, enabling you to handle exceptional situations gracefully and providing robust error handling and recovery mechanisms in your programs.
Here is an other example to understand the concept of exception handling more clearly:
#include <iostream>
#include <string>
using namespace std;
void Test1(int x);
class A
{
string myWhat;
public:
A(string str = "Error Occurred.")
{
myWhat = str;
}
virtual string what()
{
return myWhat;
}
};
class B : public A
{
public:
B(string str = "Error.") : A(str) {}
};
int Test2(int x)
{
if (x == 50)
throw B("Exception 50");
else if (x == 100)
throw A("Exception 100");
else if (x == 150)
{
string str = "Exception 150";
throw str;
}
else
return 0;
}
void SomeFunction(int x)
{
try
{
Test1(x);
cout << "Back in SomeFunction.\n";
}
catch (A& ex)
{
cout << "SomeFunction A: " << ex.what() << endl;
}
catch (B& ex)
{
cout << "SomeFunction B: " << ex.what() << endl;
}
catch (string& ex)
{
cout << "SomeFunction Exception: " << ex << endl;
throw ex;
}
cout << "- - - -\n";
}
void Test1(int x)
{
if (x == 50)
{
try
{
Test2(x);
}
catch (string& ex)
{
cout << "50: Test1 Exception ...\n" << ex << endl;
}
}
else if (x == 100)
{
try
{
Test2(x);
}
catch (B& ex)
{
cout << "100: Test1 B ...\n" << ex.what() << endl;
}
catch (string& ex)
{
cout << "100: Test1 Exception ...\n" << ex << endl;
}
catch (A& ex)
{
cout << "100: Test1 AAA ...\n" << ex.what() << endl;
}
}
else if (x == 150)
{
int* ptr = 0;
try
{
ptr = new int[100];
}
catch (B& ex)
{
cout << "150: Test1 B ...\n" << ex.what() << endl;
}
catch (string& ex)
{
cout << "150: Test1 Exception ...\n" << ex << endl;
throw ex;
}
catch (A& ex)
{
cout << "150: Test1 AAA ...\n" << ex.what() << endl;
}
Test2(x);
if (ptr) delete[] ptr;
}
else
{
try
{
cout << "Else ...\n";
}
catch (A& abc)
{
cout << "Else: Caught A...\n" << abc.what() << endl;
}
catch (B& abc)
{
cout << "Else: Caught B...\n" << abc.what() << endl;
}
}
cout << "Test Printing.\n";
}
int main()
{
SomeFunction(50);
SomeFunction(100);
SomeFunction(150);
return 0;
}
- The execution starts in `SomeFunction`.
- It calls `Test1(50)`.
- In `Test1`, `x` is equal to 50, so it enters the first if block.
- Inside the first if block, it encounters a `try` block and calls `Test2(50)`.
- In `Test2`, it encounters another `throw` statement for `B("Exception 50")`.
- Since `B` is derived from `A`, it matches the first catch block in `SomeFunction`, `catch (A& ex)`.
- The catch block is executed, printing "SomeFunction A: Exception 50".
- The execution continues in `SomeFunction` after the catch blocks, printing "- - - -".
- The execution starts in `SomeFunction`.
- It calls `Test1(100)`.
- In `Test1`, `x` is equal to 100, so it enters the second if block.
- Inside the second if block, it encounters a `try` block and calls `Test2(100)`.
- In `Test2`, it encounters a `throw` statement for `A("Exception 100")`.
- Since A is the base class of B, it matches the third catch block in Test1, catch (A& ex).
- The catch block is executed, printing "100: Test1 AAA ..." followed by "Exception 100".
- The execution continues in Test1 after the catch blocks, printing "Test Printing".
- Finally, the execution returns to SomeFunction, printing "Back in SomeFunction" and "- - - -".
- The execution starts in `SomeFunction`.
- It calls `Test1(150)`.
- In `Test1`, `x` is equal to 150, so it enters the third if block.
- Inside the third if block, it encounters a `try` block and attempts to allocate memory using `new`.
- However, since memory allocation fails, it throws a `bad_alloc` exception.
- The exception is not caught by any catch block within `Test1`, so it propagates up the call stack to the nearest catch block, which is in `SomeFunction`.
- It matches the second catch block in `SomeFunction`, `catch (string & ex)`.
- The catch block is executed, printing "SomeFunction Exception: Exception 150".
- The exception is then re-thrown using `throw ex`.
- Since there is no catch block to handle this exception in main, the program terminates abruptly with an unhandled exception.