Menu
Coddy logo textTech

Custom Exception Hierarchies

Part of the Object Oriented Programming section of Coddy's C++ journey. Lesson 82 of 104.

While the standard library provides useful exception classes like std::runtime_error, real-world applications often benefit from creating custom exception hierarchies. By defining your own exception classes that inherit from standard exceptions, you can create domain-specific error types that carry additional context and enable more precise error handling.

A custom exception class typically inherits from std::exception or one of its derived classes and overrides the what() method:

#include <exception>
#include <string>

class DatabaseException : public std::exception {
    std::string message;
public:
    DatabaseException(const std::string& msg) : message(msg) {}
    const char* what() const noexcept override { return message.c_str(); }
};

class ConnectionException : public DatabaseException {
public:
    ConnectionException(const std::string& host) 
        : DatabaseException("Failed to connect to: " + host) {}
};

class QueryException : public DatabaseException {
public:
    QueryException(const std::string& query) 
        : DatabaseException("Invalid query: " + query) {}
};

This hierarchy allows catch blocks to handle exceptions at different levels of specificity:

try {
    // database operations...
    throw ConnectionException("localhost");
} catch (const ConnectionException& e) {
    std::cout << "Connection issue: " << e.what() << "\n";
} catch (const DatabaseException& e) {
    std::cout << "Database error: " << e.what() << "\n";
} catch (const std::exception& e) {
    std::cout << "General error: " << e.what() << "\n";
}

The noexcept specifier on what() is important - it guarantees the method won't throw, which is required by the base class interface. Order your catch blocks from most specific to most general, as C++ uses the first matching handler.

challenge icon

Challenge

Easy

Let's build a file processing system with a custom exception hierarchy that handles different types of file-related errors. You'll create domain-specific exceptions that inherit from a base exception class, allowing precise error handling at different levels of specificity.

You'll organize your code across three files:

  • FileExceptions.h: Define your custom exception hierarchy for file operations.

    Create a base FileException class that inherits from std::exception. It should store an error message and override the what() method with the noexcept specifier.

    Then create two derived exception classes:

    • FileNotFoundException: takes a filename and creates a message: File not found: [filename]
    • PermissionDeniedException: takes a filename and creates a message: Permission denied: [filename]

    Each derived class should call its parent constructor with the appropriate message.

  • FileProcessor.h: Define a FileProcessor class that simulates file operations and throws your custom exceptions.

    Your FileProcessor should have a method openFile(const std::string& filename) that simulates opening a file:

    • If the filename contains missing, throw a FileNotFoundException
    • If the filename contains locked, throw a PermissionDeniedException
    • Otherwise, return the string Successfully opened: [filename]
  • main.cpp: Read a single filename from input.

    Create a FileProcessor and attempt to open the file. Use a try-catch block with handlers ordered from most specific to most general:

    1. Catch FileNotFoundException and print: File Error: [message]
    2. Catch PermissionDeniedException and print: Access Error: [message]
    3. Catch FileException and print: General File Error: [message]

    If no exception is thrown, print the success message returned by openFile().

For example, with input report.txt:

Successfully opened: report.txt

With input missing_data.csv:

File Error: File not found: missing_data.csv

With input locked_config.ini:

Access Error: Permission denied: locked_config.ini

This challenge demonstrates how custom exception hierarchies let you handle errors at the appropriate level of detail: catching specific exceptions when you need precise handling, or catching the base exception when you want to handle all file errors uniformly.

Try it yourself

#include <iostream>
#include <string>
#include "FileExceptions.h"
#include "FileProcessor.h"

using namespace std;

int main() {
    string filename;
    cin >> filename;
    
    FileProcessor processor;
    
    // TODO: Use a try-catch block to handle exceptions
    // Order handlers from most specific to most general:
    // 1. Catch FileNotFoundException - print: "File Error: [message]"
    // 2. Catch PermissionDeniedException - print: "Access Error: [message]"
    // 3. Catch FileException - print: "General File Error: [message]"
    // If no exception, print the success message from openFile()
    
    try {
        // TODO: Call processor.openFile() and handle the result
        
    }
    // TODO: Add catch blocks in order from most specific to most general
    
    return 0;
}
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming

Practice on your own: Online C++ compiler