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
98: The Exception Hierarchy in the Standard Library
When you start writing larger C++ applications, you'll realize that not all errors are created equal. Some things go wrong because the programmer messed up the logic (like passing a negative value to a function that expects a size), while other things go wrong because the environment failed (like a network cable being unplugged). The C++ Standard Library provides a hierarchy of exception classes to help us distinguish between these two categories.
Defining a basic configuration loader
Let's build a small configuration loader. I want this tool to read a value from a simulated file. If the file is missing, that's a runtime issue. If the value read is logically impossible for our application, that's a logic error. I'll start by using the most common base classes: std::runtime_error and std::logic_error.
#include <iostream>
#include <std;string>
#include <stdexcept>
class ConfigLoader {
public:
int loadSetting(const std::string& key) {
if (key == "missing") {
// This is an environmental failure
throw std::runtime_error("Configuration file not found on disk");
}
if (key == "timeout" && 0 == 0) { // Simulating a value check
// Let's say a timeout of -1 is logically invalid
throw std::logic_error("Timeout value cannot be negative");
}
return 42;
}
};
The danger of "Island" exceptions
Here is where I usually trip up when I'm rushing a project. I often decide I want a more "specific" exception name to make the code more readable, so I create a custom class. But if I'm not careful, I create an "island"—an exception that doesn't belong to the standard hierarchy.
// My mistake: creating a standalone class
class ConfigParseError : public std::exception {
const char* what() const noexcept override {
return "Failed to parse the config format";
}
};
At first glance, this looks fine. I inherited from std::exception. But wait—look at how I'm catching errors in my main loop:
int main() {
ConfigLoader loader;
try {
loader.loadSetting("missing");
} catch (const std::runtime_error& e) {
std::cout << "Runtime issue: " << e.what() << "\n";
} catch (const std::logic_error& e) {
std::cout << "Logic issue: " << e.what() << "\n";
} catch (const std::exception& e) {
std::cout << "General issue: " << e.what() << "\n";
}
}
If I throw my ConfigParseError, it *will* be caught by std::exception. However, std::exception is a very thin interface. It doesn't have the convenient string constructor that std::runtime_error has. I'm finding myself overriding what() manually for every single single custom exception, which is a tedious waste of time.
Integrating into the runtime hierarchy
The professional way to handle this is to realize that std::runtime_error and std::logic_error are themselves derived from std::exception. Instead of inheriting from the root, I should inherit from the category that fits my error. Since a parsing error is something that happens at runtime due to bad input, I'll pivot my custom exception to inherit from std::runtime_error.
// The corrected approach
class ConfigParseError : public std::runtime_error {
public:
// I can now just pass the message up to the runtime_error constructor
ConfigParseError(const std::string& msg) : std::runtime_error(msg) {}
};
Now, my exception hierarchy looks like this: ConfigParseError → std::runtime_error → std::exception. This means my catch (const std::runtime_error& e) block will now catch my custom ConfigParseError, allowing me to group similar types of failures together while still maintaining the ability to catch them specifically if I need to.
Ordering the catch blocks
One last thing you need to remember: the order of your catch blocks matters immensely. C++ checks them from top to bottom. If you put catch (const std::exception& e) at the top, it will swallow every single exception in the hierarchy, and your more specific std::runtime_error blocks will never execute. Always move from the most specific child to the most general parent.
📋 Practical Task
Implementing a Multi-Tiered Database Connection Validator
Create a program that simulates a database connection process. You need to implement the following exception hierarchy and logic:
- Create a custom exception class
ConnectionTimeoutErrorthat inherits fromstd::runtime_error. - Create a custom exception class
InvalidQueryErrorthat inherits fromstd::logic_error. - Write a function
connectAndQuery(int statusCode)that:- Throws
ConnectionTimeoutErrorifstatusCodeis 408. - Throws
InvalidQueryErrorifstatusCodeis 400. - Throws a generic
std::runtime_errorifstatusCodeis 500.
- Throws
- In
main, wrap the function call in a try-block with three separate catch blocks:- One specifically for
ConnectionTimeoutError. - One for the broader
std::logic_error. - One for the base
std::exceptionto catch everything else.
- One specifically for
Test your code by calling the function with different status codes to ensure the correct catch block is triggered.
There are no comments for now.