#ifndef SAFE_REF_H
#define SAFE_REF_H
#include <cassert>
#include <vector>
template<typename T> class SafeReference;
/**
* Registration object of references to some object that might disappear at any time.
* @tparam T Type of the object that may disappear.
* @note It's useful to connect lifetime of this class with the object being protected,
* for example by embedding the registration into the object to protect.
*/
template<typename T>
class ReferencedObject {
friend class SafeReference<T>;
public:
ReferencedObject() = delete;
ReferencedObject(ReferencedObject &) = delete;
ReferencedObject(ReferencedObject &&) = delete;
ReferencedObject(T &object) : object(object) { }
~ReferencedObject()
{
unsigned int index = 0;
for (SafeReference<T> *sref : this->references) {
sref->Disconnect(index);
index++;
}
}
/**
* Construct a new reference object to access the protected object.
* @return New reference object.
*/
SafeReference<T> MakeReference()
{
return SafeReference<T>(this);
}
#ifdef DBG_SAFE_REF
/**
* For debugging, dump the reference objects that currently exist for this referenced object.
* @param indent If specified, the indentation text to prepend to each line.
*/
void DumpReferences(const char *indent = "")
{
printf("%sReferencedObject points at %p (%lu references)\n", indent, &this->object, this->references.size());
for (SafeReference<T> *ref : this->references) {
printf("%s- Index %2u: Reference at %p\n", indent, ref->ref_index, ref);
}
printf("\n");
}
#endif
private:
/**
* Register a new reference pointing to this object.
* @param reference New reference to register.
* @return Index of the reference.
*/
unsigned int Register(SafeReference<T> *reference)
{
assert(this->references.size() < UINT_MAX);
unsigned int new_index = this->references.size();
this->references.push_back(reference);
return new_index;
}
/**
* Change the registrered reference at a given index.
* @param ref_index Index to change.
* @param old_ref Old reference at the given index.
* @param new_ref New reference to use instead.
*/
void ChangeRegistered(unsigned int ref_index, SafeReference<T> *old_ref, SafeReference<T> *new_ref)
{
assert(ref_index < this->references.size());
assert(this->references[ref_index] == old_ref);
this->references[ref_index] = new_ref;
}
/**
* Drop a reference to the object.
* @param ref_index Index of the reference to remove.
* @param reference Address of the reference, for verification.
*/
void Unregister(unsigned int ref_index, SafeReference<T> *reference)
{
size_t count = this->references.size();
assert(ref_index < count);
assert(this->references[ref_index] == reference);
SafeReference<T> *last_ref = this->references.back();
if (reference != last_ref) { // Reference is not the last element.
// Move last_ref into 'ref_index' to fill the hole.
last_ref->MoveIndex(count - 1, ref_index);
this->references[ref_index] = last_ref;
}
this->references.pop_back();
}
std::vector<SafeReference<T> *> references; ///< References to the object. Vector doesn't
///< have holes, last entry gets moved as needed.
T& object; ///< The object pointed at.
};
/**
* Reference object to refer to another object safely.
* @tparam T Type of the referenced object.
*/
template<typename T>
class SafeReference {
friend class ReferencedObject<T>;
public:
SafeReference() : ref_object(nullptr)
{
}
SafeReference(ReferencedObject<T> *ref_object) : ref_object(ref_object)
{
assert(this->ref_object != nullptr);
this->ref_index = this->ref_object->Register(this);
}
SafeReference(SafeReference<T> &sref)
{
if (sref.IsValid()) {
this->ref_object = sref.ref_object;
this->ref_index = this->ref_object->Register(this);
} else {
this->ref_object = nullptr;
}
}
SafeReference(SafeReference<T> &&sref)
{
if (sref.IsValid()) {
this->ref_object = sref.ref_object;
this->ref_index = sref.ref_index;
this->ref_object->ChangeRegistered(this->ref_index, &sref, this);
sref.ref_object = nullptr;
} else {
this->ref_object = nullptr;
}
}
SafeReference &operator=(SafeReference<T> &sref)
{
if (this == &sref) return *this;
if (this->ref_object != nullptr) this->ref_object->Unregister(this->ref_index, this);
if (sref.IsValid()) {
this->ref_object = sref.ref_object;
this->ref_index = this->ref_object->Register(this);
} else {
this->ref_object = nullptr;
}
return *this;
}
SafeReference &operator=(SafeReference<T> &&sref)
{
if (this == &sref) return *this;
if (this->ref_object != nullptr) this->ref_object->Unregister(this->ref_index, this);
if (sref.IsValid()) {
this->ref_object = sref.ref_object;
this->ref_index = sref.ref_index;
this->ref_object->ChangeRegistered(this->ref_index, &sref, this);
sref.ref_object = nullptr;
} else {
this->ref_object = nullptr;
}
return *this;
}
~SafeReference()
{
if (this->ref_object != nullptr) this->ref_object->Unregister(this->ref_index, this);
}
/**
* Return whether the referenced object is still available.
* @return Whether the referenced object still exists.
*/
bool IsValid() const
{
return this->ref_object != nullptr;
}
/**
* Get a pointer to the referenced object.
* @return Pointer to the referenced object, or \c nullptr if the referenced object does not
* exist any more.
* @note The pointer is not protected, and may become dangling. To avoid that, don't keep the
* bare pointer around, instead, use #IsValid and #GetObject for every access.
*/
T* GetObject()
{
return (this->ref_object == nullptr) ? nullptr : &this->ref_object->object;
}
private:
/**
* Move the index of this reference from \a old_index to \a new_index.
* @param old_index Index of the reference object that it currently uses.
* @param new_index New index to use instead.
*/
void MoveIndex(unsigned int old_index, unsigned int new_index)
{
assert(ref_object != nullptr);
assert(old_index == this->ref_index);
this->ref_index = new_index;
}
/**
* The referenced object disappeared, do not access the referenced object any more.
* @param old_index Index of the reference object that it currently uses.
*/
void Disconnect(unsigned int old_index)
{
assert(ref_object != nullptr);
assert(old_index == this->ref_index);
this->ref_object = nullptr;
}
ReferencedObject<T> *ref_object; ///< Referenced object pointing to, if not \c nullptr.
unsigned int ref_index; ///< Index number of this reference in the referenced object vector,
};
#endif