#ifndef SAFE_REF_H #define SAFE_REF_H #include #include template 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 class ReferencedObject { friend class SafeReference; public: ReferencedObject() = delete; ReferencedObject(ReferencedObject &) = delete; ReferencedObject(ReferencedObject &&) = delete; ReferencedObject(T &object) : object(object) { } ~ReferencedObject() { unsigned int index = 0; for (SafeReference *sref : this->references) { sref->Disconnect(index); index++; } } /** * Construct a new reference object to access the protected object. * @return New reference object. */ SafeReference MakeReference() { return SafeReference(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 *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 *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 *old_ref, SafeReference *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 *reference) { size_t count = this->references.size(); assert(ref_index < count); assert(this->references[ref_index] == reference); SafeReference *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 *> 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 class SafeReference { friend class ReferencedObject; public: SafeReference() : ref_object(nullptr) { } SafeReference(ReferencedObject *ref_object) : ref_object(ref_object) { assert(this->ref_object != nullptr); this->ref_index = this->ref_object->Register(this); } SafeReference(SafeReference &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 &&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 &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 &&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 *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