C++
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C++
-
Section 4: Memory Management
-
Section 5: Templates and Generic Programming
-
Section 6: The Standard Template Library
-
Section 7: Modern C++ Features
-
Section 8: Error Handling
-
Section 9: Multithreading
-
Section 10: Operator Overloading and Type Conversion
-
Section 11: Advanced Topics
-
Section 12: Tooling and Build Systems
-
Section 13: Design Patterns in C++
-
Section 14: Interfacing with C and Systems Programming
-
Section 15: Networking and IPC Basics
-
Section 16: Graphics and Game Programming Basics
-
Section 17: The Boost Libraries
-
Section 18: Data Structures and Algorithms in C++
-
Section 19: Practical Projects
-
Section 20: More Concurrency Patterns
-
Section 21: More OOP and Design Practice
-
Section 22: File I/O and Streams
-
Section 23: More Standard Library
-
Section 24: Practice Exercises
-
Section 25: Interview and Algorithm Practice
-
Section 26: Compiler and Language Internals
-
Section 27: GUI and Application Frameworks Overview
-
Section 28: Testing and Quality Practices
-
Section 29: Numerics Library
-
Section 30: Concepts Library (C++20)
-
Section 31: Ranges Library (C++20) In Depth
-
Section 32: More Utility Library
99: Custom Exception Hierarchies
I often see developers approach custom exceptions by creating a flat list of classes. They’ll make a FileNotFoundException, a DiskFullException, and a PermissionDeniedException, all inheriting directly from std::exception. On the surface, it looks organized. You have a specific type for every specific problem. But in a real-world production system, this is a maintenance nightmare.
The Flat-List Trap: Thinking every error needs a standalone class
When you create a flat list of exceptions, you force the person using your code to either write a massive block of catch statements for every single possibility, or give up and catch std::exception. Catching std::exception is too broad—you might accidentally swallow a std::bad_alloc or some other system error you weren't prepared to handle.
// The "Flat" approach - Don't do this for large systems
try {
myFileSystem.save(data);
} catch (const FileNotFoundException& e) {
/* handle it */
} catch (const DiskFullException& e) {
/* handle it */
} catch (const PermissionDeniedException& e) {
/* handle it */
}
// What if I just want to handle ANY file system error?
// I have to list them all here.
This approach fails because it ignores the most powerful tool in C++: polymorphism. If you have twenty different types of IO errors, you don't want to write twenty catch blocks every time you call a function.
Using Polymorphism to Categorize Failures
The right way to do this is to build a hierarchy. I usually suggest creating a base exception class for your specific module or library that inherits from std::runtime_error. Why std::runtime_error? Because it already handles the string message storage for you, saving you from writing a custom constructor just to pass a message up to std::exception.
By grouping your exceptions, you give the caller a choice: they can be surgical and catch a specific error, or they can be general and catch the category base class.
#include <stdexcept>
#include <string>
#include <iostream>
// The base for all our database-related errors
class DatabaseException : public std::runtime_error {
public:
explicit DatabaseException(const std::string& msg)
: std::runtime_error(msg) {}
};
// Specialized errors inherit from the category base
class ConnectionTimeoutException : public DatabaseException {
public:
explicit ConnectionTimeoutException()
: DatabaseException("Database connection timed out.") {}
};
class QuerySyntaxException : public DatabaseException {
public:
explicit QuerySyntaxException(const std::string& query)
: DatabaseException("Syntax error in query: " + query) {}
};
void executeQuery(const std::string& query) {
if (query.empty()) throw QuerySyntaxException(query);
if (/* network fails */ true) throw ConnectionTimeoutException();
}
int main() {
try {
executeQuery("");
} catch (const ConnectionTimeoutException& e) {
std::cout << "Retrying connection..." << std::endl;
} catch (const DatabaseException& e) {
// This catches QuerySyntaxException AND any other DatabaseException
// that isn't a timeout.
std::cerr << "Database error occurred: " < e.what() << std::endl;
}
}
Notice how the catch (const DatabaseException& e) block acts as a "catch-all" for that specific domain. I find this is the sweet spot for API design. You provide enough granularity for the power user to handle specific edge cases, but you don't force every user to write a novel of catch blocks just to keep the app from crashing.
One quick tip: always inherit publicly and always catch by reference. If you catch by value, you'll run into "slicing," where the derived part of your exception is stripped away, and you lose the specific type information you worked so hard to build.
📋 Practical Task
Implement a Hierarchical Network Protocol Error System
You are building a network library. Instead of using generic exceptions, implement a custom hierarchy that allows a user to distinguish between different types of network failures while still being able to handle all "Network Errors" collectively.
Requirements:
- Create a base class
NetworkExceptionthat inherits fromstd::runtime_error. - Create two derived classes:
PacketLossExceptionandInvalidHeaderException, both inheriting fromNetworkException. - Write a function
processPacket(int packetId)that:- Throws
PacketLossExceptionifpacketIdis negative. - Throws
InvalidHeaderExceptionifpacketIdis greater than 1000.
- Throws
- In your
mainfunction, wrap the call toprocessPacketin a try-catch block. Implement twocatchhandlers:- One specifically for
PacketLossExceptionto print "Requesting retransmission...". - One for the base
NetworkExceptionto catch any other network-related errors (includingInvalidHeaderException) and print the error message using.what().
- One specifically for
There are no comments for now.