Elective Object Oriented Programming in C++

Object Oriented Programming in C++Unit 810 min read

Exception Handling in C++: Mechanisms, Syntax, and Real-World Applications

Unit 8 of Object Oriented Programming in C++ covers exception handling—how to detect, raise, and manage runtime errors gracefully using try, catch, and throw. Learn syntax, hierarchy, and best practices with code examples, real-world ties (e.g., bank transactions, file I/O), and exam-focused tips.

TAKEAWAYS:

  • Exception handling isolates errors to prevent program crashes, using try blocks to test code and catch blocks to handle exceptions.
  • C++ supports multiple catch blocks for different exception types, checked in order of inheritance (derived → base).
  • Built-in exceptions (std::exception) and custom exceptions (derived classes) enable structured error reporting.
  • throw can rethrow exceptions or propagate them up the call stack.
  • Best practices include avoiding catch(...) for debugging, using RAII for resource cleanup, and documenting thrown exceptions.

Core Concepts and Syntax

1. Why Exception Handling?

Errors in programs (e.g., division by zero, file not found) disrupt normal flow. Exception handling:

  • Separates error-handling code from business logic.
  • Centralizes error management (e.g., logging, user notifications).
  • Prevents crashes by gracefully recovering or exiting.

Example Scenario: A bank app (like Ncell Money) processes transactions. If a user’s account balance is insufficient, the app should not crash but instead show a message and roll back the transaction.


2. Key Components

flowchart TD
    A["try block"] -->|"throws exception"| B["catch block"]
    B --> C["Exception object"]
    C --> D["Handled or rethrown"]
    A -->|"no exception"| E["Continue execution"]
  • try block: Code that might throw an exception.
  • throw: Explicitly raises an exception (e.g., throw runtime_error("Insufficient funds");).
  • catch block: Handles exceptions of a specific type.
  • Exception object: Contains error details (e.g., message, error code).

3. Syntax Rules

#include <iostream>
#include <stdexcept> // For std::exception
using namespace std;

int main() {
    try {
        // Risky code (e.g., division, file I/O)
        int balance = 100;
        int withdrawal = 150;
        if (withdrawal > balance) {
            throw runtime_error("Insufficient balance");
        }
        balance -= withdrawal;
    }
    catch (const runtime_error& e) {
        // Handle the exception
        cerr << "Error: " << e.what() << endl;
    }
    catch (const exception& e) {
        // Catch base-class exceptions (e.g., logic_error, runtime_error)
        cerr << "Generic error: " << e.what() << endl;
    }
    return 0;
}

Key Notes:

  • Order matters: Catch derived classes before base classes (e.g., runtime_error before exception).
  • const&: Avoid copying the exception object; pass by reference.
  • Multiple catch: Handle specific exceptions first, then generic ones.

Visual: Exception Hierarchy in C++

classDiagram
    class Exception {
        <<abstract>>
        +virtual void what() const = 0
    }
    class runtime_error {
        +what() const
    }
    class logic_error {
        +what() const
    }
    class bad_alloc {
        +what() const
    }
    Exception <|-- runtime_error
    Exception <|-- logic_error
    Exception <|-- bad_alloc

Built-in Exceptions:

Exception Class When Thrown Example
runtime_error Logic errors (e.g., invalid args) throw runtime_error("File not found");
logic_error Programmer errors (e.g., bad logic) throw logic_error("Index out of bounds");
bad_alloc Memory allocation fails new throws this if memory is exhausted.
out_of_range Container access violations vector::at() throws this.

Worked Example: File Handling with Exceptions

Scenario: A program reads a file (e.g., eSewa’s transaction log). If the file is missing, it should notify the user instead of crashing.

#include <fstream>
#include <iostream>
using namespace std;

void readFile(const string& filename) {
    ifstream file(filename);
    if (!file) {
        throw runtime_error("Failed to open file: " + filename);
    }
    string line;
    while (getline(file, line)) {
        cout << line << endl;
    }
}

int main() {
    try {
        readFile("transactions.txt");
    }
    catch (const runtime_error& e) {
        cerr << "Error: " << e.what() << endl;
        // Alternative: Log to a backup file or notify admin.
    }
    return 0;
}

Trace Table:

Step Action Exception Thrown? Output
1 readFile("transactions.txt") Yes (file missing) cerr prints error message.
2 catch block executes No Program continues gracefully.

Custom Exceptions

Why? Built-in exceptions may not fit your needs (e.g., a Daraz order system needs custom errors like OutOfStockException).

#include <stdexcept>
#include <string>

class OutOfStockException : public runtime_error {
public:
    OutOfStockException(const string& product)
        : runtime_error("Product " + product + " is out of stock") {}
};

void processOrder(const string& product) {
    if (product == "iPhone 15") {
        throw OutOfStockException(product);
    }
    // Proceed with order.
}

int main() {
    try {
        processOrder("iPhone 15");
    }
    catch (const OutOfStockException& e) {
        cerr << "Order failed: " << e.what() << endl;
        // Suggest alternatives or notify customer.
    }
    return 0;
}

Visual: Custom Exception Hierarchy

classDiagram
    class OutOfStockException {
        +OutOfStockException(string)
    }
    class runtime_error {
        +what() const
    }
    OutOfStockException --|> runtime_error

## In the Real World

  1. eSewa/Khalti Transactions

    • Idea Used: Exception handling for insufficient balance or failed payments.
    • How: If a user’s account lacks funds, the app throws a InsufficientFundsException and shows a message instead of crashing. The transaction is rolled back.
  2. Daraz Order Processing

    • Idea Used: Custom exceptions for out-of-stock items or shipping failures.
    • How: When a product is unavailable, Daraz throws an OutOfStockException and suggests alternatives. The order queue skips the item automatically.
  3. Bank Loan Calculations (e.g., NMB Bank)

    • Idea Used: Handling invalid interest rates or division by zero (e.g., 0% rate).
    • How: A function like calculateEMI() throws a InvalidRateException if the rate is negative, ensuring the program doesn’t proceed with incorrect data.
  4. WhatsApp Message Delivery

    • Idea Used: Network exceptions (e.g., ConnectionFailedException).
    • How: If WhatsApp can’t connect to a server, it retries or shows an offline message, using exceptions to separate retry logic from core messaging.

Advanced Topics

1. Exception Specifications (Deprecated in C++11)

Old syntax (avoid in modern C++):

void riskyOperation() throw (runtime_error, bad_alloc) {
    // May throw only runtime_error or bad_alloc.
}

Replacement: Use noexcept (C++11+):

void safeOperation() noexcept {
    // Guaranteed not to throw.
}

2. Stack Unwinding

When an exception is thrown:

  1. The try block exits.
  2. Destructors of local objects are called (RAII ensures cleanup).
  3. The exception propagates up the call stack until caught.

Example:

void openFile() {
    FileHandler fh; // RAII: destructor closes file.
    if (!fh.open()) throw runtime_error("File open failed");
}

void process() {
    try {
        openFile();
    }
    catch (...) {
        // fh's destructor runs here, closing the file.
    }
}

3. Rethrowing Exceptions

Use throw; to propagate an exception up the stack (add context without losing the original error).

void readData() {
    try {
        // Code that throws.
    }
    catch (const exception& e) {
        cerr << "Read error: " << e.what() << endl;
        throw; // Rethrow to let caller handle it.
    }
}

Common Pitfalls and Best Practices

Pitfall Solution
Catching by value (catch (exception e)) Use catch (const exception& e) to avoid copies.
Empty catch blocks Log the error or rethrow.
Catching ... for debugging Use specific exceptions; catch(...) hides errors.
Not documenting thrown exceptions Add comments like // Throws: runtime_error if file missing.
Ignoring exceptions in destructors Destructors should not throw (use noexcept).

## Exam Tip

  1. Define Exception Handling:

    "Exception handling in C++ is a mechanism to handle runtime errors gracefully using try, throw, and catch blocks, separating error-handling code from normal logic."

  2. Programming Questions:

    • Must-do: Write a program with:
      • A try block containing risky code (e.g., division, file I/O).
      • At least two catch blocks (one specific, one generic).
      • A throw statement with a custom or built-in exception.
    • Example Starter:
      #include <iostream>
      using namespace std;
      
      int divide(int a, int b) {
          if (b == 0) throw runtime_error("Division by zero");
          return a / b;
      }
      
      int main() {
          try {
              cout << divide(10, 0) << endl;
          }
          catch (const runtime_error& e) {
              cout << "Error: " << e.what() << endl;
          }
          return 0;
      }
      
  3. Theory Questions:

    • Explain multiple catch blocks and their order.
    • Differentiate between throw and catch.
    • Describe stack unwinding and RAII’s role.
  4. Real-World Tie:

    • In exams, relate exceptions to systems you know (e.g., "How would you handle a failed login in Khalti?").
    • Use bank loan calculations or file operations as examples.
  5. Avoid:

    • Using catch(...) without logging (marks deducted).
    • Throwing exceptions in destructors.
    • Not handling base-class exceptions after derived ones.

Key Formula to Remember:

try {
    // Risky code
}
catch (derived_exception& e) {
    // Handle derived first
}
catch (base_exception& e) {
    // Handle base
}
catch (...) {
    // Fallback (use sparingly)
}

Based on the PU BE Computer (PU) syllabus for Object Oriented Programming in C++, unit 8.

Discussion

Loading…