Modern C++ Architecture Principles
Effective software architecture follows several key practices:
- Prefer single inheritance combined with interfaces over multiple inheritance
- Maintain a single inheritance hierarchy through a top-level abstract base class
- Favor composition over inheritance where possible
The flexibility of C++ allows multiple inheritance trees, but this can lead to portability issues. Compiler differences may cause identical code to behave differently across platforms. For instance, older compilers returned null pointers on failed memory allocation, while modern compilers typically throw exceptions, creating inconsistency.
Creating a foundational Object class addresses these concerns by:
- Following established design patterns where all data structures inherit from a common base
- Standardizing dynamic memory allocation behavior to improve code portability
Implementation
Header Definition
#ifndef BASE_OBJECT_H
#define BASE_OBJECT_H
namespace Framework
{
class Object
{
public:
void* operator new (unsigned int bytes) throw();
void operator delete(void* ptr);
void* operator new[] (unsigned int bytes) throw();
void operator delete[] (void* ptr);
virtual ~Object() = 0;
};
}
#endif // BASE_OBJECT_H
Note that the throw() specification ensures no exceptions are thrown on allocation failure. The pure virtual destructor enables runtime type identification for all derived classes.
Implementation Source
#include "Object.h"
#include <cstdlib>
#include <iostream>
using namespace std;
namespace Framework
{
void* Object::operator new (unsigned int bytes) throw()
{
cout << "Allocating: " << bytes << " bytes" << endl;
return malloc(bytes);
}
void Object::operator delete(void* ptr)
{
cout << "Deallocating pointer: " << ptr << endl;
free(ptr);
}
void* Object::operator new[] (unsigned int bytes) throw()
{
cout << "Array allocation: " << bytes << " bytes" << endl;
return malloc(bytes);
}
void Object::operator delete[] (void* ptr)
{
cout << "Array deallocation" << endl;
free(ptr);
}
Object::~Object()
{
// Pure virtual destructor requires definition
}
}
The pure virtual destructor must be defined. Debug output statements are included for demonstration purposes.
Usage Example
#include <iostream>
#include "Object.h"
using namespace std;
using namespace Framework;
class DataContainer : public Object
{
public:
int x;
int y;
};
class ExtendedContainer : public DataContainer
{
public:
int z;
};
int main()
{
Object* item1 = new DataContainer();
Object* item2 = new ExtendedContainer();
cout << "Address 1: " << item1 << endl;
cout << "Address 2: " << item2 << endl;
delete item1;
delete item2;
return 0;
}
Execution demonstrates custom memory management operators being invoked and proper vtable initialization.
Key Benefits
- Serves as the root class for all library data structures
- Provides consistant heap memory operations
- Returns nullptr on allocation failure rather than throwing exceptions
- Enables runtime type checking through virtual function tables