Exception Handling
Custom Exceptions
Design and implement custom exception classes by inheriting from std::exception, overriding virtual what() safely.
Interview: Designing robust custom error hierarchies, managing internal state (e.g. error codes) safely, and declaring what() as noexcept.
In complex C++ systems, standard exception classes may not provide enough context. Creating Custom Exceptions by inheriting from std::exception or std::runtime_error allows you to carry domain-specific error details (like database status codes) and override what() to return specific error messages.
Inheritance
Derive from std::exception or std::runtime_error to integrate with standard catch blocks.
what() Override
Override the virtual function: const char* what() const noexcept to provide the error string.
State Safety
Store error codes or details internally. Avoid memory allocation during exception construction to prevent throwing bad_alloc.
The noexcept Constraint on what()
The signature of the overridden what() function must match the base definition exactly:
It is marked noexcept (or throw() in older standards) to guarantee that querying the exception's message will never throw another exception, which would cause an immediate crash (terminate).
Code Walkthrough
An implementation of a custom database exception carrying an internal error code.
#include <iostream> #include <exception> #include <string>class DatabaseException : public std::exception { private: int m_errorCode; std::string m_errorMessage;
public: DatabaseException(int code, const std::string& msg) : m_errorCode(code), m_errorMessage(msg) {}
// Overriding the virtual what() function safely virtual const char* what() const noexcept override { return m_errorMessage.c_str(); // Returns pointer to internal buffer }
int getErrorCode() const noexcept { return m_errorCode; } };
void queryDatabase() { throw DatabaseException(500, "Database connection timeout occurred"); }
int main() { try { queryDatabase(); } catch (const DatabaseException& e) { std::cerr << "Database Error [" << e.getErrorCode() << "]: " << e.what() << std::endl; } catch (const std::exception& e) { std::cerr << "Generic Error: " << e.what() << std::endl; } return 0; }
Interview-Relevant Information
Q: Why does standard exception::what() return const char instead of std::string?
Answer: std::string can dynamically allocate memory. If dynamic allocation fails during exception handling (e.g. during a low-memory std::bad_alloc state), constructing std::string would throw another exception, causing an abort. Returning a raw const char* avoids any new allocations.
Q: Why should custom exception classes derive from std::exception?
Answer: Deriving from std::exception ensures compatibility with existing try/catch blocks in standard codebases. It allows generic handlers to catch your exception as a const std::exception& while still retrieving your custom message via polymorphism.
Quick Checklist
Did you mark what() as noexcept? Does it return a pointer to a persistent member variable? If yes, your custom exception design is safe.
Use Cases
Defining domain-specific exceptions (e.g. ConfigException, ParserException) within library boundaries.
Carrying detailed structured diagnostic information (like severity levels or database error codes) to higher-level handlers.
Common Mistakes
Returning a pointer to a temporary string constructed inside what() (which immediately goes out of scope, causing undefined behavior).
Omitting the const or noexcept qualifiers, leading to compilation errors due to signature mismatch with std::exception::what().