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
Exceptionclass now inherits fromObject. This guarantees that when an exception object is allocated on the heap, the customoperator new(which returnsNULLon failure) is used. - A new exception type
InvalidOperationExceptionis introduced. It should be thrown when a member function is invoked while the object is in an invalid state. - The
SmartPointerclass also inherits fromObject, so heap allocation of smart pointer objects uses the sameoperator newthat returnsNULLon 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_EXCEPTIONmacro 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
- Relationship between data structures and algorithms.
- Methods for measuring algorithm efficiency.
- Basic infrastructure of DTLib:
- Top-level parent class
Object. - Smart pointer class.
- Exception class hierarchy.
- Top-level parent class