Module 13: Exception Handling
Chapter 13 • Advanced
Exception Handling
Exception handling allows programs to deal with errors gracefully instead of crashing. It's essential for writing robust, production-quality C++ code.
What are Exceptions?
Exceptions are runtime errors or unusual conditions that disrupt normal program flow. Instead of crashing, exceptions can be caught and handled.
Benefits:
- Error Recovery: Handle errors without crashing
- Clean Code: Separate error handling from business logic
- Resource Safety: Ensure cleanup even when errors occur
- Information Propagation: Pass error information up call stack
Exception Handling Syntax
Basic try-catch Block
try {
// Code that might throw exception
riskyFunction();
} catch (exception_type e) {
// Handle exception
cout << "Error: " << e.what() << endl;
}
Example
#include <iostream>
#include <stdexcept>
using namespace std;
int divide(int a, int b) {
if (b == 0) {
throw runtime_error("Division by zero!");
}
return a / b;
}
int main() {
try {
int result = divide(10, 0);
cout << "Result: " << result << endl;
} catch (runtime_error& e) {
cout << "Caught: " << e.what() << endl;
}
return 0;
}
Standard Exception Classes
C++ provides standard exception hierarchy:
exception
├── logic_error
│ ├── invalid_argument
│ ├── domain_error
│ ├── length_error
│ └── out_of_range
├── runtime_error
│ ├── range_error
│ ├── overflow_error
│ ├── underflow_error
│ └── system_error
└── bad_alloc (from new)
Using Standard Exceptions
#include <stdexcept>
// Invalid argument
if (value < 0) {
throw invalid_argument("Value must be non-negative");
}
// Out of range
if (index >= size) {
throw out_of_range("Index out of bounds");
}
// Runtime error
if (fileNotFound) {
throw runtime_error("File not found");
}
Multiple catch Blocks
Handle different exception types:
try {
// Code that might throw
} catch (invalid_argument& e) {
// Handle invalid argument
} catch (out_of_range& e) {
// Handle out of range
} catch (exception& e) {
// Handle any other exception
}
Order Matters: More specific exceptions first, general last.
Exception Propagation
Exceptions propagate up the call stack until caught:
void function3() {
throw runtime_error("Error in function3");
}
void function2() {
function3(); // Exception propagates
}
void function1() {
try {
function2();
} catch (runtime_error& e) {
cout << "Caught: " << e.what() << endl;
}
}
Creating Custom Exceptions
Derive from standard exception classes:
class MyException : public runtime_error {
public:
MyException(const string& msg) : runtime_error(msg) {}
};
// Usage
throw MyException("Custom error message");
Exception Specifications (Deprecated)
Old Style (C++98):
void function() throw(runtime_error); // Deprecated!
Modern Style (C++11):
void function() noexcept; // No exceptions
void function() noexcept(false); // May throw
RAII and Exception Safety
RAII (Resource Acquisition Is Initialization) ensures resources are cleaned up even when exceptions occur.
Without RAII (Problem)
void badFunction() {
int* ptr = new int(10);
riskyOperation(); // If this throws, memory leak!
delete ptr;
}
With RAII (Solution)
void goodFunction() {
unique_ptr<int> ptr = make_unique<int>(10);
riskyOperation(); // If this throws, ptr automatically deleted!
}
Exception Safety Guarantees
- Basic Guarantee: No resource leaks, valid state
- Strong Guarantee: Operation succeeds or state unchanged
- No-throw Guarantee: Never throws exceptions
noexcept Specifier
Mark functions that don't throw:
void safeFunction() noexcept {
// This function promises not to throw
}
// Conditional noexcept
template <typename T>
void swap(T& a, T& b) noexcept(noexcept(a.swap(b))) {
a.swap(b);
}
Best Practices
- ✅ Use exceptions for exceptional conditions
- ✅ Catch by reference (not by value)
- ✅ Use specific exception types when possible
- ✅ Don't throw from destructors (undefined behavior)
- ✅ Use RAII for resource management
- ✅ Document exceptions in function comments
- ✅ Catch specific exceptions first, general last
- ✅ Re-throw with `throw;` to preserve stack trace
Common Patterns
Pattern 1: Resource Guard
class FileGuard {
FILE* file;
public:
FileGuard(const char* name) : file(fopen(name, "r")) {
if (!file) throw runtime_error("Cannot open file");
}
~FileGuard() { if (file) fclose(file); }
FILE* get() { return file; }
};
Pattern 2: Exception Wrapper
template <typename Func>
auto safeCall(Func f) {
try {
return f();
} catch (exception& e) {
cout << "Error: " << e.what() << endl;
throw; // Re-throw
}
}
Common Mistakes
- ❌ Catching by value instead of reference
- ❌ Catching
exceptionbefore specific types - ❌ Throwing from destructors
- ❌ Swallowing exceptions silently
- ❌ Using exceptions for control flow
- ❌ Not cleaning up resources in catch blocks
- ❌ Forgetting to re-throw when needed
Exception vs Error Codes
| Aspect | Exceptions | Error Codes |
|---|---|---|
| **Performance** | Slower (when thrown) | Fast |
| **Visibility** | Must be handled | Easy to ignore |
| **Information** | Rich (what(), type) | Limited |
| **Control Flow** | Automatic propagation | Manual checking |
| **Use Case** | Exceptional conditions | Expected errors |
Next Module
In Module 14, we'll learn about File I/O - reading and writing files in C++!
Hands-on Examples
Basic Exception Handling
#include <iostream>
#include <stdexcept>
#include <string>
using namespace std;
int divide(int a, int b) {
if (b == 0) {
throw runtime_error("Division by zero is not allowed!");
}
return a / b;
}
int getElement(int arr[], int size, int index) {
if (index < 0 || index >= size) {
throw out_of_range("Index out of bounds!");
}
return arr[index];
}
int main() {
// Example 1: Division by zero
cout << "=== Example 1: Division ===" << endl;
try {
int result = divide(10, 0);
cout << "Result: " << result << endl;
} catch (runtime_error& e) {
cout << "Caught exception: " << e.what() << endl;
}
// Example 2: Array bounds
cout << "\n=== Example 2: Array Access ===" << endl;
int arr[] = {10, 20, 30, 40, 50};
try {
int value = getElement(arr, 5, 10); // Invalid index
cout << "Value: " << value << endl;
} catch (out_of_range& e) {
cout << "Caught exception: " << e.what() << endl;
}
// Example 3: Successful operation
cout << "\n=== Example 3: Success ===" << endl;
try {
int result = divide(20, 4);
cout << "Result: " << result << endl;
int value = getElement(arr, 5, 2);
cout << "Array value: " << value << endl;
} catch (exception& e) {
cout << "Caught: " << e.what() << endl;
}
return 0;
}Basic exception handling uses try-catch blocks. Code that might throw is in try block. catch blocks handle specific exception types. Exceptions propagate up call stack until caught. Use standard exception types from <stdexcept>. Always catch by reference, not by value.
Practice with Programs
Reinforce your learning with hands-on practice programs
Related Program Topics
Recommended Programs
Hello World
BeginnerThe classic first program that prints "Hello World" to the console
Display Your Name
BeginnerProgram to display your name on the screen
User Input
BeginnerProgram to take input from user and display it
Check for Even Odd
BeginnerProgram to check if a number is even or odd
Largest Among 3
BeginnerProgram to find the largest of three numbers