Refining a Class Hierarchy: Deepening the Object-Oriented Foundation in DTLib

All classes in DTLib must belong to a single inheritance tree. This design ensures consistent behavior when objects are allocated on the heap. The InvalidOperationException class is a newly added derived exception type, designed to be thrown when a member function is called in an incorrect state.

Key Improvements

  • The Exception class now inherits from Object. This guarantees that when an exception object is allocated on the heap, the custom operator new (which returns NULL on failure) is used.
  • A new exception type InvalidOperationException is introduced. It should be thrown when a member function is invoked while the object is in an invalid state.
  • The SmartPointer class also inherits from Object, so heap allocation of smart pointer objects uses the same operator new that returns NULL on failure.

InvalidOperationException Definition

class InvalidOperationException : public Exception
{
public:
    InvalidOperationException() : Exception(0) {}
    InvalidOperationException(const char* message) : Exception(message) {}
    InvalidOperationException(const char* file, int line) : Exception(file, line) {}
    InvalidOperationException(const char* message, const char* file, int line) 
        : Exception(message, file, line) {}
    InvalidOperationException(const InvalidOperationException& e) : Exception(e) {}
    
    InvalidOperationException& operator=(const InvalidOperationException& e)
    {
        Exception::operator=(e);
        return *this;
    }
};

Refining the init() Function in Exception.cpp

The init() function must check whether memory allocation succeeded. However, caution is needed in the else branch: throwing an NoEnoughMemoryException would be problematic because:

  • From a high-level perspective, the exception class itself inherits from the abstract base Exception. The base object has not yet been fully constructed when an exception is thrown, which is illegal.
  • From a code perspective, constructing an exception object would invoke the base class constructor, which would again call init(), leading to infinite recursion.

Therefore, the best practice is to simply set m_location = NULL in the else branch without throwing any exception.

void Exception::init(const char* message, const char* file, int line)
{
    m_message = strdup(message);
    if (file != nullptr)
    {
        char lineStr[16] = {0};
        itoa(line, lineStr, 10);
        m_location = static_cast<char>(malloc(strlen(file) + strlen(lineStr) + 2));
        if (m_location != nullptr)
        {
            strcat(m_location, file);
            strcat(m_location, ":");
            strcat(m_location, lineStr);
        }
    }
    else
    {
        m_location = nullptr;
    }
}
</char>

Complete First-Stage Code

Object.h

#ifndef OBJECT_H
#define OBJECT_H

namespace DTLib
{
class Object
{
public:
    void* operator new(unsigned int size) throw();
    void operator delete(void* p);
    void* operator new[](unsigned int size) throw();
    void operator delete[](void* p);
    virtual ~Object() = 0;
};
}
#endif

Object.cpp

#include "Object.h"
#include <cstdlib>
#include <iostream>

using namespace std;

namespace DTLib
{
void* Object::operator new(unsigned int size) throw()
{
    cout << "Object::operator new: " << size << endl;
    return malloc(size);
}

void Object::operator delete(void* p)
{
    cout << "Object::operator delete: " << p << endl;
    free(p);
}

void* Object::operator new[](unsigned int size) throw()
{
    cout << "Object::operator new[]" << endl;
    return malloc(size);
}

void Object::operator delete[](void* p)
{
    cout << "Object::operator delete[]" << endl;
    free(p);
}

Object::~Object() {}
}
</iostream></cstdlib>

SmartPointer.h

#ifndef SMARTPOINTER_H
#define SMARTPOINTER_H
#include "Object.h"

namespace DTLib
{
template <typename T>
class SmartPointer : public Object
{
protected:
    T* m_ptr;
public:
    SmartPointer(T* p = nullptr) : m_ptr(p) {}
    
    SmartPointer(const SmartPointer<T>& other)
    {
        m_ptr = other.m_ptr;
        const_cast<SmartPointer<T>&(other).m_ptr = nullptr;
    }
    
    SmartPointer<T>& operator=(const SmartPointer<T>& other)
    {
        if (this != &other)
        {
            delete m_ptr;
            m_ptr = other.m_ptr;
            const_cast<SmartPointer<T>&(other).m_ptr = nullptr;
        }
        return *this;
    }
    
    T* operator->() { return m_ptr; }
    T& operator*()  { return *m_ptr; }
    
    bool isNull() const { return m_ptr == nullptr; }
    T* get() const       { return m_ptr; }
    
    ~SmartPointer() { delete m_ptr; }
};
}
#endif

Exception.h

#ifndef EXCEPTION_H
#define EXCEPTION_H
#include "Object.h"

namespace DTLib
{
#define THROW_EXCEPTION(e, m) (throw e(m, __FILE__, __LINE__))

class Exception : public Object
{
protected:
    char* m_message;
    char* m_location;
    void init(const char* message, const char* file, int line);
public:
    Exception(const char* message);
    Exception(const char* file, int line);
    Exception(const char* message, const char* file, int line);
    Exception(const Exception& e);
    Exception& operator=(const Exception& e);
    virtual const char* message() const;
    virtual const char* location() const;
    virtual ~Exception() = 0;
};

// ... (ArithmeticException, NullPointerException, etc. unchanged)
// Only InvalidOperationException shown for brevity
class InvalidOperationException : public Exception
{
    // same as above
};

}
#endif

Exception.cpp

#include "Exception.h"
#include <cstring>
#include <cstdlib>

using namespace std;

namespace DTLib
{
void Exception::init(const char* message, const char* file, int line)
{
    m_message = strdup(message);
    if (file != nullptr)
    {
        char buf[16] = {0};
        itoa(line, buf, 10);
        m_location = static_cast<char*>(malloc(strlen(file) + strlen(buf) + 2));
        if (m_location != nullptr)
        {
            strcat(m_location, file);
            strcat(m_location, ":");
            strcat(m_location, buf);
        }
    }
    else
    {
        m_location = nullptr;
    }
}

// Other constructor and method implementations omitted for brevity
}

main.cpp (Test)

#include <iostream>
#include "Object.h"
#include "Exception.h"
#include "SmartPointer.h"

using namespace std;
using namespace DTLib;

int main()
{
    SmartPointer<int>* sp = new SmartPointer<int>();
    delete sp;
    
    InvalidOperationException* ex = new InvalidOperationException();
    delete ex;
    
    return 0;
}

Development Approach and Caveats

  • Iterative development: Complete one small goal at a time, continuously refining toward a reusable library.
  • Single inheritance tree: All classes inherit from Object, standardizing heap allocation behavior.
  • Only throw, never catch: Use the THROW_EXCEPTION macro to keep the code portable; exception handling is left to the user.
  • Low coupling: Avoid using standard library components as much as possible to enhance portability.

If the target compiler does not support exception handling, the macro can be modified:

#define THROW_EXCEPTION(e, m) (throw e(m, __FILE__, __LINE__))      // original
#define THROW_EXCEPTION(e, m) // (throw e(m, __FILE__, __LINE__))    // disabled

Phase 1 Summary

  1. Relationship between data structures and algorithms.
  2. Methods for measuring algorithm efficiency.
  3. Basic infrastructure of DTLib:
    • Top-level parent class Object.
    • Smart pointer class.
    • Exception class hierarchy.

Tags: DTLib Exception SmartPointer C++ object-oriented

Posted on Thu, 06 Aug 2026 16:19:28 +0000 by steamPunk