Custom Exceptions
Create custom exception classes to provide domain-specific error information
Learn how to create custom exception classes that provide meaningful, domain-specific error information for your applications.
A Simple Example
#include <iostream>
#include <exception>
#include <string>
class FileNotFoundException : public std::exception {
std::string filename;
std::string message;
public:
explicit FileNotFoundException(const std::string& file)
: filename{file}
, message{"File not found: " + file} {}
const char* what() const noexcept override {
return message.c_str();
}
const std::string& getFilename() const { return filename; }
};
void openFile(const std::string& filename) {
if (filename.empty()) {
throw FileNotFoundException{filename};
}
std::cout << "Opening: " << filename << "\n";
}
int main() {
try {
openFile("");
} catch (const FileNotFoundException& e) {
std::cerr << "Error: " << e.what() << "\n";
std::cerr << "Filename was: '" << e.getFilename() << "'\n";
}
return 0;
}
Breaking It Down
Inheriting from std::exception
- What it does: Makes your exception catchable by catch(const std::exception&)
- Must override: The what() method to return error message
- Best practice: Always inherit from std::exception or one of its derived classes
- Remember: This creates a unified exception hierarchy in your code
The what() method with noexcept
- Signature: const char* what() const noexcept override
- Why noexcept: If what() throws during exception handling, program terminates
- Return type: Must be const char*, typically from std::string::c_str()
- Remember: Store the message as a member to ensure lifetime
Storing exception context
- Add member variables: Store additional context like filename, error codes, etc.
- Provide accessors: Let callers extract specific information
- Use explicit constructors: Prevent accidental implicit conversions
- Remember: More context helps with debugging and error handling
Building exception hierarchies
- Create base classes: Define domain-specific base exceptions
- Derive specific errors: Make specialized exceptions inherit from your base
- Catch flexibility: Callers can catch specific or general exception types
- Remember: Hierarchy mirrors your domain model and error categories
Why This Matters
- Standard exceptions like std::runtime_error are generic and lack context about what went wrong in your specific application.
- Custom exceptions let you encode specific error conditions in your type system, making it easier to handle different failures differently.
- A banking app might have InsufficientFundsException vs AccountClosedException - each requiring different handling logic and user messages.
Critical Insight
Exception hierarchies mirror your domain model. Create a base exception class for your module (like ValidationException), then derive specific exceptions from it (like InvalidEmailException, PasswordTooShortException).
This lets callers catch either specific errors (to show targeted messages) or the whole category (for generic error handling) - giving them control over granularity. It is like organizing errors into folders and subfolders.
Best Practices
Always inherit from std::exception: This makes your exceptions compatible with standard exception handling and catchable by generic catch blocks.
Mark what() as noexcept: This prevents program termination if the method throws during exception handling.
Store the message as a member: Do not return pointers to temporary strings from what(). Store the message in a member variable.
Use explicit constructors: Single-argument constructors should be explicit to prevent implicit conversions that could create exceptions accidentally.
Provide context accessors: Add getter methods for error-specific information like filenames, error codes, or invalid values.
Common Mistakes
Forgetting noexcept on what(): The what() method must be noexcept or your program can terminate if it throws during exception handling.
Returning temporary string pointers: Never return c_str() from a temporary string. Store the message as a member.
Not using explicit constructors: Single-argument constructors should be explicit to prevent implicit conversions.
Catching by value: Always catch exceptions by const reference to avoid slicing and unnecessary copies.
Debug Challenge
This custom exception has a bug in the what() method signature. Click the highlighted line to fix it:
Quick Quiz
- Why should what() be marked noexcept?
- What is the benefit of exception hierarchies?
- Why use explicit on exception constructors?
Practice Playground
Time to try out what you just learned! Play with the example code below, experiment by making changes and running the code to deepen your understanding.
Output:
Error:
Lesson Progress
- Fix This Code
- Quick Quiz
- Practice Playground - run once