sql_utils/base/simple_reference_counted.h (34 lines of code) (raw):

/* * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef THIRD_PARTY_PY_BIGQUERY_ML_UTILS_SQL_UTILS_BASE_SIMPLE_REFERENCE_COUNTED_H_ #define THIRD_PARTY_PY_BIGQUERY_ML_UTILS_SQL_UTILS_BASE_SIMPLE_REFERENCE_COUNTED_H_ #include <stddef.h> #include <atomic> namespace bigquery_ml_utils_base { // Classes that wish to have thread-safe reference counting can simply inherit // from this SimpleReferenceCounted class. // // SimpleReferenceCounted additionally provides some functions that are more // 'Subtle' than 'Simple': RefCountIsOne() and OnRefCountIsZero(). // These functions should be used with great care and only by classes that have // very strict reference count contracts. // See the documentation on these functions for usage and details. class SimpleReferenceCounted { public: // It is important that the ref_count_ is initialized to 1, so the caller // owns a reference. The caller must eventually call Unref() to release it. SimpleReferenceCounted() : ref_count_(1) {} // We delete both the move constructor and copy constructor. // A move constructor or any function accepting an rvalue reference to a // reference counted object does not add much value. Reference counted objects // are always reference objects; it is illegal for any function to return a // reference counted object by value. So there is not really such a thing as // an rvalue reference counted object. // Lacking any obvious benefits, and given the subtleties of move constructors // and defining a reference counted object's invariants with move semantics, // we disable the default move constructor. // A copy constructor has similar (although less subtle) considerations, and // as we do not provide a move constructor, we do not provide a copy // constructor either. // Derived classes can still create their own copy constructors if they desire // one. Such a custom constructor should simply invoke the default constructor // of SimpleReferenceCounted which will initialize the ref count of the new // instance to 1. Creating a move constructor is strongly discouraged. SimpleReferenceCounted(const SimpleReferenceCounted&) = delete; SimpleReferenceCounted(SimpleReferenceCounted&&) = delete; // Like ctors, we have no use for default assignment operators. SimpleReferenceCounted& operator=(const SimpleReferenceCounted&) = delete; SimpleReferenceCounted& operator=(SimpleReferenceCounted&&) = delete; // Take possession of a reference on this, which must eventually be released // with Unref(). void Ref() const { ref_count_.fetch_add(1, std::memory_order_relaxed); } // Drop a reference on this, which ought to have been owned by the caller. // WARNING: Unref() may delete the object and it should not be touched once // a reference is no longer held. void Unref() const { if (ref_count_.fetch_sub(1, std::memory_order_acq_rel) - 1 == 0) { OnRefCountIsZero(); } } // Returns true if the reference count is exactly 1 // // Applications with a strong reference counting contract can use this value // to determine they are the sole logical owner holding a reference on a // given instance. // // Such applications typically don't hold raw pointers to instances without // also holding a reference, manage all references through ptr classes such as // refcount::reffed_ptr, or use other means to ensure that code can not refer // to an instance without also holding a reference on that instance. // // One example how RefCountIsOne() can be used is to implement copy-on-write // logic with an optimization that avoids a copy if the reference count is // one; i.e., the caller is the sole owner of the instance. bool RefCountIsOne() const { return ref_count_.load(std::memory_order_acquire) == 1; } protected: // This is protected to prevent its direct invocation from outside of a // SimpleReferenceCounted object. Unfortunately, there is still a risk that // a subclass will invoke its destructor directly, rather than through // Unref(). Apparently, gcc doesn't permit inheritance from a class with a // private destructor, so this has to be protected. virtual ~SimpleReferenceCounted() {} // The OnRefCountIsZero() function is invoked by Unref() once the reference // count reaches 0, i.e., when the final reference on this instance has been // removed and the object can be deleted. // // The default implementation will simply delete the instance, i.e., call // 'delete this'. Derived classes can override this function if they require // custom finalization / deallocation functions, or if they otherwise want to // control the life cycle of their instances. // // There is no requirement that the OnRefCountIsZero() method directly // destroys the object. For example, a derived class which has a substantial // construction and allocation cost may choose to override the // OnRefCountIsZero() method and freelist instances for re-use rather then // delete them directly. // // Classes that implement the OnRefCountIsZero() function to freelist objects // must make sure to call Ref() on the object before re-using it. // The preferred pattern is to call Ref() (and perform other essential class // re-initialization) directly inside the OnRefCountIsZero() function before // freelisting or otherwise re-using the object. // // While we explictly allow/require the reference count to be incremented // again from the OnRefCountIsZero handler extending the instance's life // cycle, calling Unref() directly or indirectly from OnRefCountIsZero() // should be avoided as it may trigger the reference count to drop to zero, // and cause a recursive loop. // Applications with custom OnRefCountIsZero handlers must make sure that the // reference count does not reach zero again inside the handler function. // // Example: (assuming an imaginary MyFreeList class with push / pop semantics) // // class MyHeavyBlob : public SimpleReferenceCounted { // public: // static MyHeavyBlob* New() { // MyHeavyBlob* blob = m_freelist.Pop(); // return (blob != nullptr) ? blob : new MyHeavyBlob; // } // // private: // MyHeavyBlob() = default; // // void FreeList() { // // Reset class to a 'fresh state': // // - Call Ref() to snatch 'this' away from the claws of death. // // - Clear items_ (std::vector's large allocated memory is retained) // Ref(); // items_.clear(); // if (!m_freelist.Add(this)) delete this; // } // // void OnRefCountIsZero() const override { // const_cast<MyHeavyBlob*>(this)->Freelist(); // } // // std::vector<BigStruct> items_; // }; virtual void OnRefCountIsZero() const { delete this; } // Protected accessor for testing/debugging purposes in base classes. int32_t ref_count() const { return ref_count_.load(std::memory_order_acquire); } private: mutable std::atomic<int32_t> ref_count_; }; } // namespace bigquery_ml_utils_base #endif // THIRD_PARTY_PY_BIGQUERY_ML_UTILS_SQL_UTILS_BASE_SIMPLE_REFERENCE_COUNTED_H_