Handle Exceptions in C++

C++C++Beginner
Practice Now

Introduction

In this lab, you will learn the fundamental concepts of exception handling in C++. You will start by understanding how to throw and catch basic exceptions, which are a way to handle runtime errors or unexpected situations in your program. You will then explore the use of the try, catch, and throw keywords, as well as standard exception classes such as std::exception. Additionally, you will learn how to define custom exception classes and implement multiple catch blocks for different exception types. Finally, you will explore the use of nested try-catch blocks to handle exceptions at different levels of your program.


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL cpp(("`C++`")) -.-> cpp/OOPGroup(["`OOP`"]) cpp(("`C++`")) -.-> cpp/AdvancedConceptsGroup(["`Advanced Concepts`"]) cpp(("`C++`")) -.-> cpp/StandardLibraryGroup(["`Standard Library`"]) cpp/OOPGroup -.-> cpp/classes_objects("`Classes/Objects`") cpp/OOPGroup -.-> cpp/class_methods("`Class Methods`") cpp/OOPGroup -.-> cpp/constructors("`Constructors`") cpp/OOPGroup -.-> cpp/inheritance("`Inheritance`") cpp/AdvancedConceptsGroup -.-> cpp/exceptions("`Exceptions`") cpp/StandardLibraryGroup -.-> cpp/standard_containers("`Standard Containers`") subgraph Lab Skills cpp/classes_objects -.-> lab-446082{{"`Handle Exceptions in C++`"}} cpp/class_methods -.-> lab-446082{{"`Handle Exceptions in C++`"}} cpp/constructors -.-> lab-446082{{"`Handle Exceptions in C++`"}} cpp/inheritance -.-> lab-446082{{"`Handle Exceptions in C++`"}} cpp/exceptions -.-> lab-446082{{"`Handle Exceptions in C++`"}} cpp/standard_containers -.-> lab-446082{{"`Handle Exceptions in C++`"}} end

Throw and Catch Basic Exceptions

In this step, you'll learn the fundamental concepts of exception handling in C++, focusing on how to throw and catch basic exceptions. Exceptions are a way to handle runtime errors or unexpected situations in your program.

Open the WebIDE and create a new file called basic_exceptions.cpp in the ~/project directory:

touch ~/project/basic_exceptions.cpp

Add the following code to basic_exceptions.cpp:

#include <iostream>
#include <stdexcept>

int divide(int numerator, int denominator) {
    // Throw an exception if denominator is zero
    if (denominator == 0) {
        throw std::runtime_error("Division by zero is not allowed!");
    }
    return numerator / denominator;
}

int main() {
    try {
        // Attempt a normal division
        int result1 = divide(10, 2);
        std::cout << "10 / 2 = " << result1 << std::endl;

        // Attempt division by zero
        int result2 = divide(10, 0);
        std::cout << "This line will not be executed" << std::endl;
    }
    catch (const std::exception& e) {
        // Catch and handle the exception
        std::cout << "Error: " << e.what() << std::endl;
    }

    return 0;
}

Let's break down the key components:

  1. throw keyword:

    • Used to generate an exception when an error occurs
    • In this example, we throw a std::runtime_error when division by zero is attempted
  2. try block:

    • Contains code that might generate an exception
    • Allows you to attempt risky operations
    • If an exception occurs, program control moves to the catch block
  3. catch block:

    • Handles the exception thrown in the try block
    • Catches exceptions of a specific type (here, std::exception)
    • Uses e.what() to get the error message

Compile and run the program:

g++ basic_exceptions.cpp -o basic_exceptions
./basic_exceptions

Example output:

10 / 2 = 5
Error: Division by zero is not allowed!

Key points about basic exception handling:

  • Exceptions provide a way to handle runtime errors gracefully
  • throw generates an exception
  • try and catch work together to manage exceptional situations
  • Prevents program crashes by providing controlled error handling

Understand try, catch, and throw Keywords

In this step, you'll dive deeper into the three key keywords of exception handling in C++: try, catch, and throw. These keywords work together to create a robust error-handling mechanism in your programs.

Open the WebIDE and create a new file called exception_keywords.cpp in the ~/project directory:

touch ~/project/exception_keywords.cpp

Add the following code to exception_keywords.cpp:

#include <iostream>
#include <string>

// Function that might throw an exception
int processAge(int age) {
    // Throw an exception for invalid age
    if (age < 0) {
        throw std::string("Age cannot be negative");
    }
    if (age > 120) {
        throw std::string("Age is unrealistically high");
    }
    return age;
}

int main() {
    // First try block: handling age validation
    try {
        // Successful case
        int validAge = processAge(25);
        std::cout << "Valid age: " << validAge << std::endl;

        // This will throw an exception
        int invalidAge1 = processAge(-5);
        std::cout << "This line will not be executed" << std::endl;
    }
    catch (const std::string& errorMessage) {
        // Catch block to handle the thrown exception
        std::cout << "Error: " << errorMessage << std::endl;
    }

    // Second try block: another example
    try {
        // This will throw another exception
        int invalidAge2 = processAge(150);
        std::cout << "This line will also not be executed" << std::endl;
    }
    catch (const std::string& errorMessage) {
        std::cout << "Error: " << errorMessage << std::endl;
    }

    return 0;
}

Let's break down the key components:

  1. throw keyword:

    • Used to generate an exception when a specific condition is met
    • Can throw different types of objects (strings, integers, custom objects)
    • Immediately stops the current function's execution
  2. try block:

    • Contains code that might generate an exception
    • Allows you to attempt risky operations
    • If an exception occurs, program control moves to the matching catch block
  3. catch block:

    • Catches and handles specific types of exceptions
    • Can have multiple catch blocks for different exception types
    • Prevents program from crashing by handling errors gracefully

Compile and run the program:

g++ exception_keywords.cpp -o exception_keywords
./exception_keywords

Example output:

Valid age: 25
Error: Age cannot be negative
Error: Age is unrealistically high

Key points about exception keywords:

  • throw signals an error condition
  • try defines a block of code that might generate an exception
  • catch handles the exception and prevents program termination
  • Exceptions provide a structured way to manage runtime errors

Use Standard Exception Classes (std::exception)

In this step, you'll learn about standard exception classes in C++ and how to use the std::exception hierarchy to handle different types of runtime errors. The C++ Standard Library provides a set of predefined exception classes that cover various error scenarios.

Open the WebIDE and create a new file called standard_exceptions.cpp in the ~/project directory:

touch ~/project/standard_exceptions.cpp

Add the following code to standard_exceptions.cpp:

#include <iostream>
#include <stdexcept>
#include <limits>

double divideNumbers(double numerator, double denominator) {
    // Check for division by zero using std::runtime_error
    if (denominator == 0) {
        throw std::runtime_error("Division by zero is not allowed!");
    }
    return numerator / denominator;
}

void checkArrayIndex(int* arr, int size, int index) {
    // Check for out-of-range access using std::out_of_range
    if (index < 0 || index >= size) {
        throw std::out_of_range("Array index is out of bounds!");
    }
    std::cout << "Value at index " << index << ": " << arr[index] << std::endl;
}

int main() {
    try {
        // Demonstrate division by zero exception
        std::cout << "Attempting division:" << std::endl;
        double result = divideNumbers(10, 0);
    }
    catch (const std::runtime_error& e) {
        std::cout << "Runtime Error: " << e.what() << std::endl;
    }

    try {
        // Demonstrate array index out of range exception
        int numbers[] = {1, 2, 3, 4, 5};
        int arraySize = 5;

        std::cout << "\nAccessing array elements:" << std::endl;
        checkArrayIndex(numbers, arraySize, 2);  // Valid index
        checkArrayIndex(numbers, arraySize, 10); // Invalid index
    }
    catch (const std::out_of_range& e) {
        std::cout << "Out of Range Error: " << e.what() << std::endl;
    }

    return 0;
}

Let's explore the standard exception classes:

  1. std::exception:

    • Base class for all standard exceptions
    • Provides a virtual what() method to get error description
  2. Common derived exception classes:

    • std::runtime_error: For runtime errors that can only be detected during program execution
    • std::out_of_range: When an index or iterator is out of valid range
    • Other common classes include std::logic_error, std::invalid_argument, etc.

Compile and run the program:

g++ standard_exceptions.cpp -o standard_exceptions
./standard_exceptions

Example output:

Attempting division:
Runtime Error: Division by zero is not allowed!

Accessing array elements:
Value at index 2: 3
Out of Range Error: Array index is out of bounds!

Key points about standard exception classes:

  • Provide a structured way to handle different types of errors
  • Each exception class serves a specific purpose
  • what() method returns a descriptive error message
  • Helps in creating more robust and informative error handling

Define Custom Exception Classes

In this step, you'll learn how to create your own custom exception classes in C++. Custom exceptions allow you to define specific error types tailored to your application's unique requirements.

Open the WebIDE and create a new file called custom_exceptions.cpp in the ~/project directory:

touch ~/project/custom_exceptions.cpp

Add the following code to custom_exceptions.cpp:

#include <iostream>
#include <string>
#include <stdexcept>

// Custom exception class for bank account errors
class InsufficientFundsException : public std::runtime_error {
public:
    // Constructor that takes account balance and withdrawal amount
    InsufficientFundsException(double balance, double amount)
        : std::runtime_error("Insufficient funds"),
          currentBalance(balance),
          withdrawalAmount(amount) {}

    // Method to get detailed error information
    double getCurrentBalance() const { return currentBalance; }
    double getWithdrawalAmount() const { return withdrawalAmount; }

private:
    double currentBalance;
    double withdrawalAmount;
};

class BankAccount {
private:
    double balance;

public:
    BankAccount(double initialBalance) : balance(initialBalance) {}

    void withdraw(double amount) {
        // Check if withdrawal amount exceeds current balance
        if (amount > balance) {
            throw InsufficientFundsException(balance, amount);
        }
        balance -= amount;
        std::cout << "Withdrawal successful. Remaining balance: $"
                  << balance << std::endl;
    }

    double getBalance() const { return balance; }
};

int main() {
    try {
        // Create a bank account with initial balance
        BankAccount account(100.0);

        // Attempt a valid withdrawal
        account.withdraw(50.0);

        // Attempt an invalid withdrawal
        account.withdraw(75.0);
    }
    catch (const InsufficientFundsException& e) {
        std::cout << "Error: " << e.what() << std::endl;
        std::cout << "Current Balance: $" << e.getCurrentBalance() << std::endl;
        std::cout << "Attempted Withdrawal: $" << e.getWithdrawalAmount() << std::endl;
    }

    return 0;
}

Let's break down the custom exception class:

  1. Inherit from std::runtime_error:

    • Provides a base for custom exception classes
    • Allows using what() method to get error description
  2. Custom Exception Features:

    • Constructor with additional error context
    • Methods to retrieve specific error details
    • Provides more informative error handling

Compile and run the program:

g++ custom_exceptions.cpp -o custom_exceptions
./custom_exceptions

Example output:

Withdrawal successful. Remaining balance: $50
Error: Insufficient funds
Current Balance: $50
Attempted Withdrawal: $75

Key points about custom exception classes:

  • Inherit from standard exception classes
  • Add specific error context and methods
  • Provide more detailed error information
  • Improve error handling and debugging

Implement Multiple catch Blocks for Different Exception Types

In this step, you'll learn how to handle multiple exception types using different catch blocks. This approach allows you to provide specific error handling for various types of exceptions that might occur in your program.

Open the WebIDE and create a new file called multiple_catch.cpp in the ~/project directory:

touch ~/project/multiple_catch.cpp

Add the following code to multiple_catch.cpp:

#include <iostream>
#include <stdexcept>
#include <string>

class InvalidAgeException : public std::runtime_error {
public:
    InvalidAgeException(int age)
        : std::runtime_error("Invalid age"), invalidAge(age) {}

    int getInvalidAge() const { return invalidAge; }

private:
    int invalidAge;
};

class Student {
private:
    std::string name;
    int age;

public:
    void setStudent(const std::string& studentName, int studentAge) {
        // Validate name length
        if (studentName.length() < 2) {
            throw std::length_error("Name is too short");
        }

        // Validate age range
        if (studentAge < 0 || studentAge > 120) {
            throw InvalidAgeException(studentAge);
        }

        name = studentName;
        age = studentAge;
    }

    void displayInfo() const {
        std::cout << "Name: " << name << ", Age: " << age << std::endl;
    }
};

int main() {
    Student student;

    // First attempt: Short name
    try {
        student.setStudent("A", 25);
    }
    catch (const std::length_error& e) {
        std::cout << "Length Error: " << e.what() << std::endl;
    }
    catch (const InvalidAgeException& e) {
        std::cout << "Age Error: " << e.what()
                  << " (" << e.getInvalidAge() << ")" << std::endl;
    }

    // Second attempt: Invalid age
    try {
        student.setStudent("John Doe", 150);
    }
    catch (const std::length_error& e) {
        std::cout << "Length Error: " << e.what() << std::endl;
    }
    catch (const InvalidAgeException& e) {
        std::cout << "Age Error: " << e.what()
                  << " (" << e.getInvalidAge() << ")" << std::endl;
    }

    // Successful attempt
    try {
        student.setStudent("Alice", 20);
        student.displayInfo();
    }
    catch (const std::length_error& e) {
        std::cout << "Length Error: " << e.what() << std::endl;
    }
    catch (const InvalidAgeException& e) {
        std::cout << "Age Error: " << e.what()
                  << " (" << e.getInvalidAge() << ")" << std::endl;
    }

    return 0;
}

Let's break down multiple catch blocks:

  1. Multiple catch blocks:

    • Allow handling different types of exceptions
    • Executed in order from most specific to most general
    • Each block can handle a specific exception type
  2. Exception Handling Strategy:

    • First catch block handles std::length_error
    • Second catch block handles InvalidAgeException
    • Provides specific error messages for different scenarios

Compile and run the program:

g++ multiple_catch.cpp -o multiple_catch
./multiple_catch

Example output:

Length Error: Name is too short
Age Error: Invalid age (150)
Name: Alice, Age: 20

Key points about multiple catch blocks:

  • Handle different exception types separately
  • Provide specific error handling for each exception
  • Order matters when catching exceptions
  • Allows for more granular error management

Use Nested try-catch Blocks

In this step, you'll learn how to use nested try-catch blocks to handle complex error scenarios and provide more granular exception handling. Nested try-catch blocks allow you to handle exceptions at different levels of your code.

Open the WebIDE and create a new file called nested_exceptions.cpp in the ~/project directory:

touch ~/project/nested_exceptions.cpp

Add the following code to nested_exceptions.cpp:

#include <iostream>
#include <stdexcept>
#include <string>

class FileReadError : public std::runtime_error {
public:
    FileReadError(const std::string& filename)
        : std::runtime_error("Error reading file"), fileName(filename) {}

    std::string getFileName() const { return fileName; }

private:
    std::string fileName;
};

class DataProcessor {
public:
    void processFile(const std::string& filename) {
        try {
            // Simulate file reading
            if (filename.empty()) {
                throw FileReadError("Empty filename");
            }

            std::cout << "Reading file: " << filename << std::endl;

            try {
                // Simulate data processing
                validateData(filename);
            }
            catch (const std::runtime_error& e) {
                std::cout << "Inner try-catch: Data validation error" << std::endl;
                std::cout << "Error details: " << e.what() << std::endl;

                // Rethrow the exception to outer catch block
                throw;
            }
        }
        catch (const FileReadError& e) {
            std::cout << "Outer try-catch: File read error" << std::endl;
            std::cout << "Filename: " << e.getFileName() << std::endl;
        }
        catch (...) {
            std::cout << "Caught unknown exception" << std::endl;
        }
    }

private:
    void validateData(const std::string& filename) {
        // Simulate data validation
        if (filename == "corrupt.txt") {
            throw std::runtime_error("Corrupt file data");
        }
        std::cout << "Data validation successful" << std::endl;
    }
};

int main() {
    DataProcessor processor;

    // Scenario 1: Empty filename
    std::cout << "Scenario 1: Empty Filename" << std::endl;
    processor.processFile("");

    // Scenario 2: Corrupt file
    std::cout << "\nScenario 2: Corrupt File" << std::endl;
    processor.processFile("corrupt.txt");

    // Scenario 3: Valid file
    std::cout << "\nScenario 3: Valid File" << std::endl;
    processor.processFile("data.txt");

    return 0;
}

Let's break down nested try-catch blocks:

  1. Outer try-catch block:

    • Handles file-level exceptions
    • Catches FileReadError and other potential errors
  2. Inner try-catch block:

    • Handles data processing-specific exceptions
    • Can rethrow exceptions to the outer catch block
  3. Catch-all handler (catch (...)):

    • Catches any unexpected exceptions
    • Provides a final layer of error handling

Compile and run the program:

g++ nested_exceptions.cpp -o nested_exceptions
./nested_exceptions

Example output:

Scenario 1: Empty Filename
Outer try-catch: File read error
Filename: Empty filename

Scenario 2: Corrupt File
Reading file: corrupt.txt
Inner try-catch: Data validation error
Error details: Corrupt file data

Scenario 3: Valid File
Reading file: data.txt
Data validation successful

Key points about nested try-catch blocks:

  • Provide multiple levels of exception handling
  • Allow more detailed error management
  • Can rethrow exceptions to outer catch blocks
  • Useful for complex error scenarios

Summary

In this lab, you learned the fundamental concepts of exception handling in C++. You started by understanding how to throw and catch basic exceptions using the throw, try, and catch keywords. You explored the use of the std::runtime_error exception class to handle division by zero errors. Additionally, you learned how to access the error message using the e.what() function within the catch block. These techniques provide a way to handle runtime errors gracefully and prevent program crashes, allowing you to write more robust and reliable C++ applications.

Other C++ Tutorials you may like