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
EasyLet'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
FileExceptionclass that inherits fromstd::exception. It should store an error message and override thewhat()method with thenoexceptspecifier.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 aFileProcessorclass that simulates file operations and throws your custom exceptions.Your
FileProcessorshould have a methodopenFile(const std::string& filename)that simulates opening a file:- If the filename contains
missing, throw aFileNotFoundException - If the filename contains
locked, throw aPermissionDeniedException - Otherwise, return the string
Successfully opened: [filename]
- If the filename contains
main.cpp: Read a single filename from input.Create a
FileProcessorand attempt to open the file. Use a try-catch block with handlers ordered from most specific to most general:- Catch
FileNotFoundExceptionand print:File Error: [message] - Catch
PermissionDeniedExceptionand print:Access Error: [message] - Catch
FileExceptionand print:General File Error: [message]
If no exception is thrown, print the success message returned by
openFile().- Catch
For example, with input report.txt:
Successfully opened: report.txtWith input missing_data.csv:
File Error: File not found: missing_data.csvWith input locked_config.ini:
Access Error: Permission denied: locked_config.iniThis 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;
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Fundamentals of OOP
External FilesC++ Build & CompilationHeader Files & Source FilesNamespaces & ScopeIntroduction to OOP in C++Classes vs ObjectsThe 'this' PointerMethods (Member Functions)Attributes (Data Members)Ctors & Dtors BasicsRecap - Simple Calculator4Class Properties
Instance vs Static MembersGetters and SettersConst Member FunctionsMutable KeywordStatic Methods and VariablesFriend Functions & ClassesRecap - Bank Account Manager7Inheritance
Basic InheritanceInheritance Access LevelsCtor & Dtor Call OrderMethod OverridingVirtual Functions & VTableMultiple InheritanceVirtual InheritanceRecap - Employee Hierarchy2Memory Management
Stack vs Heap MemoryPointers and ReferencesDynamic Memory (new/delete)Smart Pointers in C++RAII in C++Recap - Dynamic Array Manager5Encapsulation
Access Specifiers in C++Access Specifiers In DepthInformation HidingStruct vs ClassNested & Inner ClassesRecap - Student Records System8Polymorphism
Compile vs Runtime PolymorphFunction OverloadingVirtual Functions RevisitedPure Virtual FunctionsAbstract ClassesInterface Design in C++Dynamic Casting & RTTIRecap - Shape Calculator11Advanced OOP Concepts
Composition vs InheritanceMixins via CRTPPimpl IdiomType ErasureEnum Classes & Strong TypingException Handling in OOPCustom Exception Hierarchies3Constructors & Destructors
Default ConstructorParameterized ConstructorCopy ConstructorMove ConstructorConstructor Init ListsDelegating ConstructorsDestructor Deep DiveRule of Three / Five / ZeroRecap - String Class6Operator Overloading
Intro to Operator OverloadArithmetic Operator OverloadComparison Operator OverloadStream OperatorsAssignment Operator Overload[] and () Operator OverloadType Conversion OperatorsRecap - Matrix Class9Templates
Function TemplatesClass TemplatesTemplate SpecializationVariadic TemplatesSFINAE & Type Traits BasicsRecap - Generic Container