Designing a Custom C++ Exception Hierarchy for Library Infrastructure

Principles of Custom Exception Types

C++ allows exception types to be user-defined classes. When an exception is thrown, the runtime attempts to match the type in a top-down manner using strict type checikng. However, the assignment compatibility rule applies: a base class handler can catch exceptions of derived classes.

To ensure correct behavior:

  • Place catch blocks for derived (child) exceptions before those for base (parent) exceptions.
  • A well-designed exception class hierarchy is fundamental infrastructure for modern C++ libraries.

Base Exception Interface

Define an abstract base class BaseError with a pure virtual destructor. Common initialization logic is extracted into a helper function.

Header definition:

class BaseError
{
protected:
    char* m_desc;
    char* m_origin;
    void prepare(const char* desc, const char* srcFile, int srcLine);

public:
    BaseError(const char* desc);
    BaseError(const char* srcFile, int srcLine);
    BaseError(const char* desc, const char* srcFile, int srcLine);

    BaseError(const BaseError& rhs);
    BaseError& operator=(const BaseError& rhs);

    virtual const char* description() const;
    virtual const char* origin() const;

    virtual ~BaseError() = 0;
};

Implementation:

void BaseError::prepare(const char* desc, const char* srcFile, int srcLine)
{
    m_desc = strdup(desc ? desc : "");

    if (srcFile)
    {
        char lineBuf[16] = {0};
        itoa(srcLine, lineBuf, 10);

        size_t len = strlen(srcFile) + strlen(lineBuf) + 3;
        m_origin = static_cast<char*>(malloc(len));
        m_origin[0] = '\0';
        strcat(m_origin, srcFile);
        strcat(m_origin, ":");
        strcat(m_origin, lineBuf);
    }
    else
    {
        m_origin = nullptr;
    }
}

BaseError::BaseError(const char* desc)
{
    prepare(desc, nullptr, 0);
}

BaseError::BaseError(const char* srcFile, int srcLine)
{
    prepare(nullptr, srcFile, srcLine);
}

BaseError::BaseError(const char* desc, const char* srcFile, int srcLine)
{
    prepare(desc, srcFile, srcLine);
}

BaseError::BaseError(const BaseError& rhs)
{
    m_desc = strdup(rhs.m_desc);
    m_origin = rhs.m_origin ? strdup(rhs.m_origin) : nullptr;
}

BaseError& BaseError::operator=(const BaseError& rhs)
{
    if (this != &rhs)
    {
        free(m_desc);
        free(m_origin);
        m_desc = strdup(rhs.m_desc);
        m_origin = rhs.m_origin ? strdup(rhs.m_origin) : nullptr;
    }
    return *this;
}

const char* BaseError::description() const
{
    return m_desc;
}

const char* BaseError::origin() const
{
    return m_origin;
}

BaseError::~BaseError()
{
    free(m_desc);
    free(m_origin);
}

The description() and origin() methods are const to allow inspection via const BaseError& references in catch blocks without risk of modification.

Derived Exception Classes

Specific exceptions inherit from BaseError and forward constructor arguments.

class MathError : public BaseError
{
public:
    MathError() : BaseError("") {}
    MathError(const char* desc) : BaseError(desc) {}
    MathError(const char* file, int line) : BaseError(file, line) {}
    MathError(const char* desc, const char* file, int line) : BaseError(desc, file, line) {}
    MathError(const MathError& rhs) : BaseError(rhs) {}
    MathError& operator=(const MathError& rhs)
    {
        BaseError::operator=(rhs);
        return *this;
    }
};

class NullPtrError : public BaseError
{
public:
    NullPtrError() : BaseError("") {}
    NullPtrError(const char* desc) : BaseError(desc) {}
    NullPtrError(const char* file, int line) : BaseError(file, line) {}
    NullPtrError(const char* desc, const char* file, int line) : BaseError(desc, file, line) {}
    NullPtrError(const NullPtrError& rhs) : BaseError(rhs) {}
    NullPtrError& operator=(const NullPtrError& rhs)
    {
        BaseError::operator=(rhs);
        return *this;
    }
};

class BoundsError : public BaseError
{
public:
    BoundsError() : BaseError("") {}
    BoundsError(const char* desc) : BaseError(desc) {}
    BoundsError(const char* file, int line) : BaseError(file, line) {}
    BoundsError(const char* desc, const char* file, int line) : BaseError(desc, file, line) {}
    BoundsError(const BoundsError& rhs) : BaseError(rhs) {}
    BoundsError& operator=(const BoundsError& rhs)
    {
        BaseError::operator=(rhs);
        return *this;
    }
};

class MemoryError : public BaseError
{
public:
    MemoryError() : BaseError("") {}
    MemoryError(const char* desc) : BaseError(desc) {}
    MemoryError(const char* file, int line) : BaseError(file, line) {}
    MemoryError(const char* desc, const char* file, int line) : BaseError(desc, file, line) {}
    MemoryError(const MemoryError& rhs) : BaseError(rhs) {}
    MemoryError& operator=(const MemoryError& rhs)
    {
        BaseError::operator=(rhs);
        return *this;
    }
};

class ParamError : public BaseError
{
public:
    ParamError() : BaseError("") {}
    ParamError(const char* desc) : BaseError(desc) {}
    ParamError(const char* file, int line) : BaseError(file, line) {}
    ParamError(const char* desc, const char* file, int line) : BaseError(desc, file, line) {}
    ParamError(const ParamError& rhs) : BaseError(rhs) {}
    ParamError& operator=(const ParamError& rhs)
    {
        BaseError::operator=(rhs);
        return *this;
    }
};

Utility Macro and Usage

A macro simplifies throwing exceptions with file and line information:

#define RAISE_ERROR(cls, msg) throw cls(msg, __FILE__, __LINE__)

Example test:

#include <iostream>
#include "BaseError.h"

int main()
{
    try
    {
        RAISE_ERROR(BoundsError, "Index out of range");
    }
    catch (const BoundsError& e)
    {
        std::cout << "Caught BoundsError\n";
        std::cout << e.description() << std::endl;
        std::cout << e.origin() << std::endl;
    }
    catch (const BaseError& e)
    {
        std::cout << "Caught BaseError\n";
        std::cout << e.description() << std::endl;
        std::cout << e.origin() << std::endl;
    }
    return 0;
}

Always order catch blocks from most derived to most base to ensure correct matching.

Tags: C++ Exception Handling Object-Oriented Design Error Management Software Infrastructure

Posted on Fri, 08 May 2026 20:47:44 +0000 by adrianuk29