// BlockyFroggy
// Copyright © 2017 John Ryland.
// All rights reserved.
#include "RefCounting.h"
#include <atomic>
class ReferenceCount
{
public:
operator size_t() const { return (m_count == nullptr) ? 1 : (*m_count).load(); }
void operator++()
{
if (m_count == nullptr)
{
m_count = new std::atomic<size_t>;
*m_count = 2;
}
else
{
(*m_count)++;
}
}
// returns true if need to delete the object
bool operator--()
{
if (operator size_t() == 1) // We are the sole owner left
{
if (m_count)
delete m_count;
m_count = nullptr;
return true;
}
else
{
(*m_count)--;
}
return false;
}
void reset()
{
m_count = nullptr;
}
private:
std::atomic<size_t>* m_count = nullptr; // a nullptr refCount is semantically equivolent to a refCount of one
};
template <typename T>
class ReferenceCountedObject
{
public:
// Default construct an object
ReferenceCountedObject()
{
}
// Copy constructor
explicit ReferenceCountedObject(const ReferenceCountedObject& a_other)
{
m_data = a_other.m_data;
a_other.addRef();
m_refCount = a_other.m_refCount;
}
// Assignment operator
ReferenceCountedObject& operator=(const ReferenceCountedObject& a_other)
{
if (&a_other != this) // Self assignment does nothing
{
a_other.addRef();
removeRef();
m_data = a_other.m_data;
m_refCount = a_other.m_refCount;
}
return *this;
}
~ReferenceCountedObject()
{
removeRef();
}
private:
void addRef() const
{
++m_refCount;
}
void removeRef()
{
if (--m_refCount)
{
delete[] m_data;
m_data = nullptr;
}
}
void modify()
{
if (m_refCount >= 2)
{
T* newData = m_data->clone();
removeRef();
m_data = newData;
m_refCount.reset(); // detach from the original ref count
}
}
T* m_data = nullptr;
mutable ReferenceCount m_refCount;
};
// ReferenceCount objects need to be pointers, so there is an allocation there
// an alternative idea is to essentially make a doubly linked list of the shared objects
// When the linked list becomes empty, the item can be deleted.
template <typename T>
class ReferencedObject
{
public:
// Default construct an object
ReferencedObject()
{
}
// Copy constructor
explicit ReferencedObject(const ReferencedObject& a_other)
{
addRef(&a_other.m_refs);
m_data = a_other.m_data;
}
// Assignment operator
ReferencedObject& operator=(const ReferencedObject& a_other)
{
//! @todo what about m_data == a_other.m_data ??? Could it be conceptually a different thing and can we optimize this case?
if (&a_other != this) // Self assignment does nothing
{
//! @todo not sure this is safe to remove before we have added the new reference
removeRef();
addRef(&a_other.m_refs);
m_data = a_other.m_data;
}
return *this;
}
~ReferencedObject()
{
removeRef();
}
private:
// There is this: https://en.wikipedia.org/wiki/XOR_linked_list
// But it is no good for this use case as we need to remove items without iterating the entire list
struct Pointers
{
Pointers* prev = nullptr;
Pointers* next = nullptr;
};
void addRef(Pointers* a_refs) const
{
// insert this m_refs in to the chain of other
m_refs.next = a_refs->next;
a_refs->next = &m_refs;
m_refs.prev = a_refs;
}
void removeRef()
{
Pointers* prev = m_refs.prev;
if (prev) {
assert(prev->next == &m_refs);
prev->next = m_refs.next;
}
Pointers* next = m_refs.next;
if (next) {
assert(next->prev == &m_refs);
next->prev = m_refs.prev;
}
if (next == nullptr && prev == nullptr)
{
delete[] m_data;
m_data = nullptr;
}
}
void modify()
{
if (m_refs.next != nullptr || m_refs.prev != nullptr)
{
T* newData = m_data->clone();
removeRef();
m_data = newData;
m_refs.next = nullptr;
m_refs.prev = nullptr;
//m_refCount.reset(); // detach from the original ref count
}
}
T* m_data = nullptr;
mutable Pointers m_refs;
};
// Yet another idea is to allocate the reference count along with the object being reference counted
// Possible downside is that it may limit ability to have a polymorphic pointer
// Upcasts of T that has multiple inheritance
template <typename T>
class SmartObject
{
public:
// Some kind of initial creation constructor that takes the args needed to construct a T
template <typename ...Ts>
explicit SmartObject(Ts ...args)
{
m_ptr = new InternalObject(args...);
}
// Copy constructor
explicit SmartObject(const SmartObject<T>& a_other)
{
a_other.addRef();
m_ptr = a_other.m_ptr;
assert(m_ptr);
}
// Assignment operator
SmartObject<T>& operator=(const SmartObject<T>& a_other)
{
// Self assignment should be safe
a_other.addRef();
removeRef(a_other.m_ptr);
return *this;
}
~SmartObject()
{
removeRef(nullptr);
}
private:
struct InternalObject
{
template <typename ...Ts>
explicit InternalObject(Ts ...args) : m_data(args...), m_refCount(1) {}
T m_data;
size_t m_refCount;
};
void addRef()
{
assert(m_ptr);
m_ptr->m_refCount++;
}
void removeRef(InternalObject *a_replace)
{
assert(m_ptr);
if (!--m_ptr->m_refCount)
delete m_ptr;
m_ptr = a_replace;
}
InternalObject* m_ptr = nullptr;
};