Section 18: Exception Handling
3 Types of Errors
- Syntax Error: Removed using the help of the compiler.
- Logical Error: Program runs successfully, but the result is different/incorrect. The debugger helps us run the program line-by-line or statement-by-statement to find these.
- Runtime Error: Caused by bad input, unavailability, or problems with resources.
Runtime Error
- In the case of a runtime error, the program crashes, stops abruptly, or terminates abnormally without completing its execution. Suddenly, it will stop.
- That is why runtime errors are dangerous for the user.
- Responsibility: The user is often responsible because, assuming the developer did their job perfectly, the user might not be providing proper resources or proper input, causing the program to fail. Thus, the failure of the program often goes to the user.
Exception
- These runtime errors are called Exceptions. We use the term "Exception" because they occur in exceptional cases.
- Objective of Exception Handling:
- Handling the error gracefully.
- Giving a proper message to the user informing them about the exact problem.
- Providing guidance to solve that problem.
Exception Handling Structure
Construct/Structure
The basic structure involves try and catch blocks.
graph TD
Start((Start)) --> Try[Try Block]
Try --> Check{Error?}
Check -- Yes --> Throw[Throw Exception]
Throw --> Catch[Catch Block]
Check -- No --> Execute[Continue Execution]
Catch --> End((End))
Execute --> End
- Try Block (
try { ... }): - If there is any error on any line inside the
tryblock, it will stop execution at that line and jump to thecatchblock. -
If there is no error in the
tryblock, thecatchblock will not execute. -
Catch Block (
catch() { ... }): Executes statements to handle the error. - It is similar logic to
if-else.
Throwing Mechanism
- In the line
throw 101;, we are throwing an exception manually. - We have to throw exceptions in C++ explicitly for logic errors (like division by zero) because the C++ compiler/runtime doesn't have a built-in mechanism to automatically throw exceptions for math errors (unlike Java).
throw 101will be caught by the catch block argument, e.g.,catch(int e), whereebecomes101.
Code Example: Basic Try-Catch
#include <iostream>
using namespace std;
int main() {
int a = 10, b = 0, c;
try {
if (b == 0) {
throw 101; // Throwing an integer exception
}
c = a / b; // This line won't execute if b is 0
cout << c;
}
catch (int e) {
// e receives the value 101
cout << "Exception occurred: Division by zero. Error Code: " << e;
}
}
Throw and Catch between Functions
- Exception handling is primarily used to communicate errors between functions (e.g., a worker function telling the main function something went wrong).
- If everything is in one function, simple
if-elselogic often suffices. Exception handling is more useful for separating error detection from error handling.
Code Example: Function Communication
#include <iostream>
using namespace std;
// Function performing the division
int division(int a, int b) {
if (b == 0) {
throw 1; // Throw exception to the caller
}
return a / b;
}
int main() {
int x = 10, y = 0, z;
try {
z = division(x, y); // Calls function
cout << z << endl;
}
catch (int e) {
cout << "Division by zero! Error: " << e << endl;
}
cout << "Bye" << endl; // Program continues gracefully
return 0;
}
More about Throw
- We can throw anything:
intvalue,float,double,char,string, or even our own class object. - Exception Specification (Legacy):
- We can mention in the function signature that a function throws an exception:
int division(int x, int y) throw(MyException) { ... } - Note: This signals to other programmers via the signature that this function might throw specific exceptions, so they are supposed to catch them.
Code Example: Throwing Class Objects
class MyException : public exception {
// Custom exception class details
};
int division(int x, int y) {
if (y == 0) {
throw MyException(); // Throwing an object
}
return x / y;
}
More about Catch
Multiple Catch Blocks
- We can have multiple
catchblocks to handle different types of data thrown. catch(...)(The Ellipsis / "Catch-All"):- It can catch any type of exception.
- If we use this, it means we aren't interested in (or able to) give a specific message based on the data type.
- It is recommended to have multiple specific
catchblocks for different data types first. - Important: The
catch(...)block must be defined as the last catch block.
graph TD
Try[Try Block] --> Error{Exception Type?}
Error -- int --> CatchInt["catch(int e)"]
Error -- float --> CatchFloat["catch(float f)"]
Error -- unknown --> CatchAll["catch(...)"]
Code Example: Multiple Catches
try {
// Some code that might throw different types
if (error_type == 1) throw 10; // int
if (error_type == 2) throw 1.5; // double
if (error_type == 3) throw "Error"; // string
}
catch (int e) {
cout << "Caught Integer: " << e;
}
catch (double d) {
cout << "Caught Double: " << d;
}
catch (...) {
cout << "Catch-All Executed: Unknown Exception";
}
Important Points
- We can have a
tryblock inside anothertryblock (Nesting of try-catch). - Inheritance in Catch Blocks:
-
If exception classes are in an inheritance hierarchy, you must write the
catchfor the Child class first, and then the Parent class. -
void fun() throw()means the function guarantees it throws no exception. - Exceptions are raised by the program.
- A
tryblock can have multiplecatchblocks.