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.

Cheat sheet

Custom exception classes inherit from std::exception or its derived classes and override 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(); }
};

Create exception hierarchies by inheriting from custom base exceptions:

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

Handle exceptions at different levels of specificity by ordering catch blocks from most specific to most general:

try {
    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() guarantees the method won't throw, which is required by the base class interface.

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