Chapter 16: Exception Handling
Exception Handling in C++: Making Your Code Robust & Reliable
Hello my wonderful student! π Welcome to Lesson 16 β Exception Handling β one of the most important topics for writing production-quality C++ code!
Exceptions are C++’s way of saying: βSomething went wrong (file not found, division by zero, out of memory, invalid inputβ¦), and Iβm going to throw the problem up the call stack until someone catches it and handles it properly.β
Think of exceptions like a fire alarm:
- When a problem occurs β the alarm goes off (throw)
- Someone (the nearest handler) hears it and takes action (catch)
- If no one handles it β the program terminates (like calling emergency services)
Today weβll cover everything in great detail:
- try, catch, throw
- Multiple catch blocks & catch-all
- Custom exceptions (your own error classes)
- noexcept specifier (modern C++ safety & performance)
Letβs go step-by-step with real-life analogies and complete examples!
1. Basic try-catch-throw β The Core Mechanism
Syntax:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<span class="line">try {</span> <span class="line"> // code that might throw an exception</span> <span class="line">}</span> <span class="line">catch (ExceptionType1& e) {</span> <span class="line"> // handle this type</span> <span class="line">}</span> <span class="line">catch (ExceptionType2& e) {</span> <span class="line"> // handle another type</span> <span class="line">}</span> <span class="line">catch (...) { // catch-all (rarely used)</span> <span class="line"> // handle anything else</span> <span class="line">}</span> |
Important rules:
- Code inside try is normal code
- When an exception is thrown β execution jumps to the matching catch
- If no catch matches β program calls std::terminate() (crashes)
Simple example β Division by zero
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
<span class="line">#include <iostream></span> <span class="line">#include <stdexcept> // for std::runtime_error</span> <span class="line">int divide(int a, int b) {</span> <span class="line"> if (b == 0) {</span> <span class="line"> throw std::runtime_error("Division by zero!"); // throw exception</span> <span class="line"> }</span> <span class="line"> return a / b;</span> <span class="line">}</span> <span class="line">int main() {</span> <span class="line"> try {</span> <span class="line"> int result = divide(10, 0);</span> <span class="line"> std::cout << "Result: " << result << "\n";</span> <span class="line"> }</span> <span class="line"> catch (const std::runtime_error& e) {</span> <span class="line"> std::cout << "Error: " << e.what() << "\n";</span> <span class="line"> }</span> <span class="line"> std::cout << "Program continues safely!\n";</span> <span class="line"> return 0;</span> <span class="line">}</span> |
Output:
|
0 1 2 3 4 5 6 7 |
<span class="line">Error: Division by zero!</span> <span class="line">Program continues safely!</span> |
- divide(10, 0) β throws std::runtime_error
- Control jumps to catch block β prints error message
- Program does NOT crash β it continues normally
2. Catching Multiple Exception Types
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
<span class="line">#include <iostream></span> <span class="line">#include <stdexcept></span> <span class="line">#include <string></span> <span class="line">void riskyOperation(int value) {</span> <span class="line"> if (value < 0) {</span> <span class="line"> throw std::invalid_argument("Negative value not allowed");</span> <span class="line"> }</span> <span class="line"> if (value == 0) {</span> <span class="line"> throw std::runtime_error("Zero is not allowed");</span> <span class="line"> }</span> <span class="line"> if (value > 100) {</span> <span class="line"> throw std::out_of_range("Value too large");</span> <span class="line"> }</span> <span class="line"> std::cout << "Value accepted: " << value << "\n";</span> <span class="line">}</span> <span class="line">int main() {</span> <span class="line"> try {</span> <span class="line"> riskyOperation(-5);</span> <span class="line"> // riskyOperation(0);</span> <span class="line"> // riskyOperation(150);</span> <span class="line"> }</span> <span class="line"> catch (const std::invalid_argument& e) {</span> <span class="line"> std::cout << "Invalid argument: " << e.what() << "\n";</span> <span class="line"> }</span> <span class="line"> catch (const std::runtime_error& e) {</span> <span class="line"> std::cout << "Runtime error: " << e.what() << "\n";</span> <span class="line"> }</span> <span class="line"> catch (const std::out_of_range& e) {</span> <span class="line"> std::cout << "Out of range: " << e.what() << "\n";</span> <span class="line"> }</span> <span class="line"> catch (...) {</span> <span class="line"> std::cout << "Unknown error occurred!\n";</span> <span class="line"> }</span> <span class="line"> std::cout << "Handled safely!\n";</span> <span class="line"> return 0;</span> <span class="line">}</span> |
Best practice:
- Catch specific exceptions first
- Catch base classes (like std::exception) last if needed
- Use catch by reference (&) β avoids copying
3. Custom Exceptions β Your Own Error Types
You can (and should!) create your own exception classes β it makes error handling much clearer.
Example β Custom exception hierarchy
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
<span class="line">#include <iostream></span> <span class="line">#include <stdexcept></span> <span class="line">#include <string></span> <span class="line">// Base custom exception</span> <span class="line">class BankException : public std::runtime_error {</span> <span class="line">public:</span> <span class="line"> explicit BankException(const std::string& msg)</span> <span class="line"> : std::runtime_error(msg) {}</span> <span class="line">};</span> <span class="line">// Specific exceptions</span> <span class="line">class InsufficientFunds : public BankException {</span> <span class="line">public:</span> <span class="line"> explicit InsufficientFunds(double balance, double amount)</span> <span class="line"> : BankException("Insufficient funds! Balance: " + std::to_string(balance) +</span> <span class="line"> ", Tried to withdraw: " + std::to_string(amount)) {}</span> <span class="line">};</span> <span class="line">class NegativeAmount : public BankException {</span> <span class="line">public:</span> <span class="line"> explicit NegativeAmount(double amount)</span> <span class="line"> : BankException("Negative amount not allowed: " + std::to_string(amount)) {}</span> <span class="line">};</span> <span class="line">class BankAccount {</span> <span class="line">private:</span> <span class="line"> double balance = 1000.0;</span> <span class="line">public:</span> <span class="line"> void withdraw(double amount) {</span> <span class="line"> if (amount <= 0) {</span> <span class="line"> throw NegativeAmount(amount);</span> <span class="line"> }</span> <span class="line"> if (amount > balance) {</span> <span class="line"> throw InsufficientFunds(balance, amount);</span> <span class="line"> }</span> <span class="line"> balance -= amount;</span> <span class="line"> std::cout << "Withdrew $" << amount << ". New balance: $" << balance << "\n";</span> <span class="line"> }</span> <span class="line">};</span> <span class="line">int main() {</span> <span class="line"> BankAccount acc;</span> <span class="line"> try {</span> <span class="line"> acc.withdraw(-50); // throws NegativeAmount</span> <span class="line"> // acc.withdraw(2000); // throws InsufficientFunds</span> <span class="line"> }</span> <span class="line"> catch (const InsufficientFunds& e) {</span> <span class="line"> std::cout << "Funds error: " << e.what() << "\n";</span> <span class="line"> }</span> <span class="line"> catch (const NegativeAmount& e) {</span> <span class="line"> std::cout << "Amount error: " << e.what() << "\n";</span> <span class="line"> }</span> <span class="line"> catch (const BankException& e) { // catches any BankException-derived</span> <span class="line"> std::cout << "Bank error: " << e.what() << "\n";</span> <span class="line"> }</span> <span class="line"> return 0;</span> <span class="line">}</span> |
Output (for negative amount):
|
0 1 2 3 4 5 6 |
<span class="line">Amount error: Negative amount not allowed: -50</span> |
Why custom exceptions?
- Clearer error messages
- Specific handling for different problems
- Easy to catch groups of related errors
4. noexcept Specifier β Modern C++ Safety & Performance
noexcept tells the compiler: βThis function promises never to throw an exception. If it does β call std::terminate() immediately.β
Why use it?
- Performance (compiler can optimize better)
- Safety (document that exceptions are not expected)
- Move semantics (required for some STL containers)
Examples:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 |
<span class="line">void safeFunction() noexcept { // promises no throw</span> <span class="line"> std::cout << "I never throw!\n";</span> <span class="line">}</span> <span class="line">void riskyButNoexcept() noexcept {</span> <span class="line"> // throw std::runtime_error("Oops"); // If this happens β std::terminate()</span> <span class="line">}</span> |
Best practice (2025+):
- Mark destructors, move constructors, move assignment operators as noexcept
- Use noexcept on small, performance-critical functions that really donβt throw
5. Full Practical Example β File Reading with Exception Handling
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
<span class="line">#include <iostream></span> <span class="line">#include <fstream></span> <span class="line">#include <stdexcept></span> <span class="line">#include <string></span> <span class="line">class FileNotFoundException : public std::runtime_error {</span> <span class="line">public:</span> <span class="line"> explicit FileNotFoundException(const std::string& filename)</span> <span class="line"> : std::runtime_error("File not found: " + filename) {}</span> <span class="line">};</span> <span class="line">std::string readFile(const std::string& filename) {</span> <span class="line"> std::ifstream file(filename);</span> <span class="line"> if (!file.is_open()) {</span> <span class="line"> throw FileNotFoundException(filename);</span> <span class="line"> }</span> <span class="line"> std::string content;</span> <span class="line"> std::string line;</span> <span class="line"> while (std::getline(file, line)) {</span> <span class="line"> content += line + "\n";</span> <span class="line"> }</span> <span class="line"> return content;</span> <span class="line">}</span> <span class="line">int main() {</span> <span class="line"> try {</span> <span class="line"> std::string data = readFile("important.txt");</span> <span class="line"> std::cout << "File contents:\n" << data << "\n";</span> <span class="line"> }</span> <span class="line"> catch (const FileNotFoundException& e) {</span> <span class="line"> std::cerr << "Error: " << e.what() << "\n";</span> <span class="line"> std::cerr << "Please check if the file exists.\n";</span> <span class="line"> }</span> <span class="line"> catch (const std::exception& e) {</span> <span class="line"> std::cerr << "Unexpected error: " << e.what() << "\n";</span> <span class="line"> }</span> <span class="line"> catch (...) {</span> <span class="line"> std::cerr << "Unknown error!\n";</span> <span class="line"> }</span> <span class="line"> std::cout << "Program continues...\n";</span> <span class="line"> return 0;</span> <span class="line">}</span> |
Tip: Always catch std::exception& as a fallback β it catches most standard exceptions.
Your Mini Homework (Try These!)
- Write a function safeDivide(double a, double b) that throws a custom DivisionByZeroException if b == 0.
- Create a SafeArray class that throws IndexOutOfRangeException when accessing invalid indices.
- Write a function marked noexcept that allocates memory β explain what happens if new throws.
Youβre doing absolutely incredible! Exception handling is what separates toy programs from real, reliable software β youβve just mastered one of the most critical topics!
Next lesson: File I/O & Streams β reading/writing files, binary data, serialization β super useful for real apps!
Any questions? Confused about custom exceptions or noexcept? Want more examples with stack unwinding or multiple catch blocks? Just ask β your friendly C++ teacher is right here for you! π
