So I realised while looking at the GameObject container and the GameObjectInstance class that basically the container is unnecessary as long as the GameObjectInstance has its operators overridden to mimic the functionality of the container. I took a crack at it with a ‘SelfObject’ class I originally wrote on my first attempt, refactored it and tested it.
SelfObject.h
#pragma once
#include <string>
#include <vector>
class SelfObject
{
public:
SelfObject(SelfObject& org);
static SelfObject& Instantiate();
static void Destroy(SelfObject& object);
int ID;
std::string name;
void printAllReferences();
std::string toString();
SelfObject& operator *();
SelfObject* operator ->();
void operator =(SelfObject& org);
bool operator ==(const SelfObject* ptr);
bool operator ==(const SelfObject& ptr);
static void CleanupSelfObjectContainers();
~SelfObject();
private:
SelfObject();
void operator delete(void* ptr);
void clearCopies(SelfObject* original);
std::vector<SelfObject*> copies;
static std::vector<SelfObject*> instances;
SelfObject* _instance;
static int ID_COUNTER;
};
SelfObject.cpp
#include "pch.h"
#include "SelfObject.h"
int SelfObject::ID_COUNTER = 0;
std::vector<SelfObject*> SelfObject::instances;
SelfObject::SelfObject(SelfObject & org)
{
printf("SelfObject copy constructor called.\n");
this->_instance = org._instance;
org.copies.push_back(this);
this->copies.push_back(&org);
this->ID = org.ID;
this->name = org.name;
}
SelfObject & SelfObject::Instantiate()
{
instances.push_back(new SelfObject());
instances[instances.size() - 1]->_instance = instances[instances.size() - 1];
return instances[instances.size()-1][0];
}
void SelfObject::Destroy(SelfObject & object)
{
printf("Destroying instance of %s\n", object->name.c_str());
delete object._instance;
object._instance = nullptr;
//clear its copies
object.clearCopies(&object);
}
void SelfObject::printAllReferences()
{
if (copies.empty())
{
printf("%s has not been copied.\n", _instance->toString().c_str());
}
else
{
printf("[%s]GameObject container(ID:%i) has %i copies.\n", _instance->name.c_str(), ID, copies.size());
for (int i = 0; i < copies.size(); ++i)
{
printf("Copy(#%i): (%s) {Container ID: %i}\n", i + 1, copies[i]->_instance->toString().c_str(), ID);
}
}
}
std::string SelfObject::toString()
{
return name + " | ID: " + std::to_string(ID);
}
SelfObject& SelfObject::operator * ()
{
return *_instance;
}
SelfObject * SelfObject::operator->()
{
return _instance;
}
void SelfObject::operator=(SelfObject & org)
{
printf("SelfObject assignment operator called.\n");
this->_instance = org._instance;
org.copies.push_back(this);
this->copies.push_back(&org);
this->ID = org.ID;
this->name = org.name;
}
bool SelfObject::operator==(const SelfObject * ptr)
{
if (ptr == nullptr)
return _instance == (void*)ptr;
return _instance == ptr->_instance;
}
bool SelfObject::operator==(const SelfObject & ptr)
{
return _instance == ptr._instance;
}
SelfObject::SelfObject()
{
//self = this;
ID = ID_COUNTER++;
name = "Self Object(" + std::to_string(ID) + ")";
}
void SelfObject::operator delete(void * ptr)
{
::delete (SelfObject*)ptr; //call the global delete
}
void SelfObject::clearCopies(SelfObject * original)
{
if (original->copies.empty())
{
original->_instance = nullptr;
return;
}
else
{
while (!original->copies.empty())
{
SelfObject* cp = original->copies[original->copies.size() - 1];
original->copies.pop_back();
cp->_instance = nullptr;
clearCopies(cp);
}
}
}
void SelfObject::CleanupSelfObjectContainers()
{
while (!instances.empty())
{
SelfObject* current = instances[instances.size() - 1];
if (current != nullptr)
{
//check if its instance object is destroyed, destroy otherwise
if (current->_instance != nullptr)
Destroy(*current);
//now destroy the container
delete current;
current = nullptr;
}
instances.pop_back();
}
}
SelfObject::~SelfObject()
{
const char* outputtext = _instance == nullptr ? "" : _instance->name.c_str();
printf("[%s]SelfObject being deleted.\n", outputtext);
if (_instance != nullptr && copies.size() > 1)
Destroy(*this);
}
It’s basically the same thing, just has a pointer to itself. Works like the last implementation I made. This will make it easier to integrate into my current entity architecture, since I won’t have to do much more refactoring. Just operator overloading and monitor the instances from the factory.
main.cpp
SelfObject* test;
SelfObject so1 = SelfObject::Instantiate();
test = &so1;
SelfObject so1Copy = so1;
so1.printAllReferences();
{
SelfObject so1Copy1 = so1Copy;
SelfObject so1Copy2 = so1Copy;
so1Copy.printAllReferences();
}
console output
SelfObject copy constructor called.
SelfObject copy constructor called.
[Self Object(0)]GameObject container(ID:0) has 2 copies.
Copy(#1): (Self Object(0) | ID: 0) {Container ID: 0}
Copy(#2): (Self Object(0) | ID: 0) {Container ID: 0}
SelfObject copy constructor called.
SelfObject copy constructor called.
[Self Object(0)]GameObject container(ID:0) has 3 copies.
Copy(#1): (Self Object(0) | ID: 0) {Container ID: 0}
Copy(#2): (Self Object(0) | ID: 0) {Container ID: 0}
Copy(#3): (Self Object(0) | ID: 0) {Container ID: 0}
[Self Object(0)]SelfObject being deleted.
[Self Object(0)]SelfObject being deleted.
end of main reached.
[Self Object(0)]SelfObject being deleted.
Destroying unique instance of Self Object(0)
[Self Object(0)]SelfObject being deleted.
[]SelfObject being deleted.
[]SelfObject being deleted.