blob: 7e339b40c3926e448526c78920c31792c2ef5f91 [file]
// Copyright 2021 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 RIEGELI_BASE_INTRUSIVE_SHARED_PTR_H_
#define RIEGELI_BASE_INTRUSIVE_SHARED_PTR_H_
#include <stddef.h>
#include <cstddef>
#include <memory>
#include <type_traits>
#include <utility>
#include "absl/base/attributes.h"
#include "absl/base/nullability.h"
#include "riegeli/base/assert.h"
#include "riegeli/base/compare.h"
#include "riegeli/base/external_data.h"
#include "riegeli/base/initializer.h"
#include "riegeli/base/ownership.h"
namespace riegeli {
namespace intrusive_shared_ptr_internal {
template <typename T, typename Enable = void>
struct HasHasUniqueOwner : std::false_type {};
template <typename T>
struct HasHasUniqueOwner<
T, std::enable_if_t<std::is_convertible_v<
decltype(std::declval<const T&>().HasUniqueOwner()), bool>>>
: std::true_type {};
template <typename T, typename Enable = void>
struct HasGetCount : std::false_type {};
template <typename T>
struct HasGetCount<T,
std::enable_if_t<std::is_convertible_v<
decltype(std::declval<const T&>().GetCount()), size_t>>>
: std::true_type {};
} // namespace intrusive_shared_ptr_internal
// `IntrusiveSharedPtr<T>` implements shared ownership of an object of type `T`.
// It can also be empty, with the pointer being `nullptr`.
//
// The actual object can be of a subtype of `T`, as long as `T::Unref()`
// correctly deletes the object in such a case, which typically requires that
// `T` has a virtual destructor.
//
// `T` maintains its own reference count, e.g. as a member of type `RefCount`.
// `T` should support:
//
// ```
// // Increments the reference count of `*this`.
// void Ref() const;
//
// // Decrements the reference count of `*this`. Deletes `this` when the
// // reference count reaches 0.
// void Unref() const;
//
// // Returns `true` if there is only one owner of the object.
// //
// // This can be used to check if the object may be modified.
// //
// // Optional. Needed for `IntrusiveSharedPtr::IsUnique()`.
// bool HasUniqueOwner() const;
// ```
//
// Compared to `std::shared_ptr`, `IntrusiveSharedPtr` supports `IsUnique()`,
// and has a smaller memory overhead (the pointer has 1 word instead of 2, the
// object typically has 1 word of overhead instead of 3). OTOH it requires
// cooperation from `T`, and has fewer features, e.g. no aliasing constructor,
// no weak pointers.
//
// Compared to `SharedPtr`, `IntrusiveSharedPtr` is harder to use, because
// it requires the object to maintain its own reference count. OTOH
// `IntrusiveSharedPtr` supports custom allocation and deallocation, and
// conversion to an `IntrusiveSharedPtr` to a non-leftmost or virtual base
// class. Prefer `SharedPtr` unless `IntrusiveSharedPtr` is needed.
template <typename T>
class ABSL_ATTRIBUTE_TRIVIAL_ABI ABSL_NULLABILITY_COMPATIBLE IntrusiveSharedPtr
: public WithEqual<IntrusiveSharedPtr<T>> {
public:
// Creates an empty `IntrusiveSharedPtr`.
constexpr IntrusiveSharedPtr() = default;
/*implicit*/ constexpr IntrusiveSharedPtr(std::nullptr_t) noexcept {}
IntrusiveSharedPtr& operator=(std::nullptr_t) {
Reset();
return *this;
}
// Creates an `IntrusiveSharedPtr` holding `ptr`.
//
// Takes ownership of `ptr` unless the second parameter is `kShareOwnership`.
explicit IntrusiveSharedPtr(T* ptr ABSL_ATTRIBUTE_LIFETIME_BOUND,
PassOwnership = kPassOwnership) noexcept
: ptr_(ptr) {}
explicit IntrusiveSharedPtr(T* ptr ABSL_ATTRIBUTE_LIFETIME_BOUND,
ShareOwnership) noexcept
: ptr_(Ref(ptr)) {}
// Creates an `IntrusiveSharedPtr` holding a constructed value.
//
// The object is constructed with `new`, which means that `T::Unref()` should
// delete the object with `delete this`.
explicit IntrusiveSharedPtr(Initializer<T> value) : ptr_(std::move(value)) {}
// Creates an `IntrusiveSharedPtr` holding a constructed value of a compatible
// type.
//
// The object is constructed with `new`, which means that `T::Unref()` should
// delete the object with `delete this`.
template <typename SubInitializer,
std::enable_if_t<
std::is_convertible_v<TargetT<SubInitializer>*, T*>, int> = 0>
explicit IntrusiveSharedPtr(SubInitializer&& value)
: ptr_(Initializer<TargetT<SubInitializer>>(
std::forward<SubInitializer>(value))) {}
// Converts from an `IntrusiveSharedPtr` with a compatible type.
template <typename SubT,
std::enable_if_t<std::is_convertible_v<SubT*, T*>, int> = 0>
/*implicit*/ IntrusiveSharedPtr(const IntrusiveSharedPtr<SubT>& that) noexcept
: ptr_(Ref(that.ptr_.get())) {}
template <typename SubT,
std::enable_if_t<std::is_convertible_v<SubT*, T*>, int> = 0>
IntrusiveSharedPtr& operator=(const IntrusiveSharedPtr<SubT>& that) noexcept {
ptr_.reset(Ref(that.ptr_.get()));
return *this;
}
// Converts from an `IntrusiveSharedPtr` with a compatible type.
//
// The source `IntrusiveSharedPtr` is left empty.
template <typename SubT,
std::enable_if_t<std::is_convertible_v<SubT*, T*>, int> = 0>
/*implicit*/ IntrusiveSharedPtr(IntrusiveSharedPtr<SubT>&& that) noexcept
: ptr_(std::move(that).ptr_) {}
template <typename SubT,
std::enable_if_t<std::is_convertible_v<SubT*, T*>, int> = 0>
IntrusiveSharedPtr& operator=(IntrusiveSharedPtr<SubT>&& that) noexcept {
ptr_.reset(std::move(that).ptr_);
return *this;
}
IntrusiveSharedPtr(const IntrusiveSharedPtr& that) noexcept
: ptr_(Ref(that.ptr_.get())) {}
IntrusiveSharedPtr& operator=(const IntrusiveSharedPtr& that) noexcept {
ptr_.reset(Ref(that.ptr_.get()));
return *this;
}
// The source `IntrusiveSharedPtr` is left empty.
IntrusiveSharedPtr(IntrusiveSharedPtr&& that) = default;
IntrusiveSharedPtr& operator=(IntrusiveSharedPtr&& that) = default;
// Replaces the object, or makes `*this` empty if `ptr == nullptr`.
//
// Takes ownership of `ptr` unless the second parameter is `kShareOwnership`.
//
// The old object, if any, is destroyed afterwards.
ABSL_ATTRIBUTE_REINITIALIZES
void Reset(T* ptr = nullptr, PassOwnership = kPassOwnership) {
ptr_.reset(ptr);
}
ABSL_ATTRIBUTE_REINITIALIZES
void Reset(T* ptr, ShareOwnership) { ptr_.reset(Ref(ptr)); }
// Replaces the object with a constructed value.
//
// The old object, if any, is destroyed afterwards.
//
// The object is constructed with `new`, which means that `T::Unref()` should
// delete the object with `delete this`.
//
// If `T` supports `HasUniqueOwner()` and `*this` is the only owner of an
// object known to have the same move-assignable type, the existing object is
// assigned or reset instead of allocating and constructing a new object.
ABSL_ATTRIBUTE_REINITIALIZES
void Reset(Initializer<T> value) { ResetImpl(std::move(value)); }
// Replaces the object with a constructed value of a compatible type.
//
// The old object, if any, is destroyed afterwards.
//
// The object is constructed with `new`, which means that `T::Unref()` should
// delete the object with `delete this`.
template <typename SubInitializer,
std::enable_if_t<
std::is_convertible_v<TargetT<SubInitializer>*, T*>, int> = 0>
ABSL_ATTRIBUTE_REINITIALIZES void Reset(SubInitializer&& value) {
ptr_ = Initializer<TargetT<SubInitializer>>(
std::forward<SubInitializer>(value));
}
// Returns `true` if `*this` is the only owner of the object.
//
// This can be used to check if the object may be modified (in contrast to
// `std::shared_ptr::unique()`).
//
// If `*this` is empty, returns `false`.
//
// Supported if `T` supports `HasUniqueOwner()`.
template <typename DependentT = T,
std::enable_if_t<intrusive_shared_ptr_internal::HasHasUniqueOwner<
DependentT>::value,
int> = 0>
bool IsUnique() const {
return ptr_ != nullptr && ptr_->HasUniqueOwner();
}
// Returns the current reference count.
//
// If the `IntrusiveSharedPtr` is accessed by multiple threads, this is a
// snapshot of the count which may change asynchronously, hence usage of
// `GetRefCount()` should be limited to cases not important for correctness,
// like producing debugging output.
//
// The reference count can be reliably compared against 1 with `IsUnique()`.
//
// Supported if `T` supports `GetCount()`.
template <typename DependentT = T,
std::enable_if_t<
intrusive_shared_ptr_internal::HasGetCount<DependentT>::value,
int> = 0>
size_t GetRefCount() const {
if (ptr_ == nullptr) return 0;
return ptr_->GetRefCount();
}
// Returns the pointer.
T* get() const ABSL_ATTRIBUTE_LIFETIME_BOUND { return ptr_.get(); }
// Dereferences the pointer.
T& operator*() const ABSL_ATTRIBUTE_LIFETIME_BOUND {
RIEGELI_ASSERT_NE(ptr_, nullptr)
<< "Failed precondition of IntrusiveSharedPtr::operator*: null pointer";
return *ptr_;
}
T* operator->() const ABSL_ATTRIBUTE_LIFETIME_BOUND {
RIEGELI_ASSERT_NE(ptr_, nullptr)
<< "Failed precondition of IntrusiveSharedPtr::operator->: null "
"pointer";
return ptr_.get();
}
// Returns the pointer. This `IntrusiveSharedPtr` is left empty.
T* Release() { return ptr_.release(); }
template <typename OtherT>
friend bool operator==(const IntrusiveSharedPtr& a,
const IntrusiveSharedPtr<OtherT>& b) {
return a.get() == b.get();
}
friend bool operator==(const IntrusiveSharedPtr& a, std::nullptr_t) {
return a.get() == nullptr;
}
// Indicates support for:
// * `ExternalRef(const IntrusiveSharedPtr&, substr)`
// * `ExternalRef(IntrusiveSharedPtr&&, substr)`
friend void RiegeliSupportsExternalRef(const IntrusiveSharedPtr*) {}
// Supports `ExternalRef`.
friend ExternalStorage RiegeliToExternalStorage(IntrusiveSharedPtr* self) {
return ExternalStorage(const_cast<std::remove_cv_t<T>*>(self->Release()),
[](void* ptr) {
if (ptr != nullptr) static_cast<T*>(ptr)->Unref();
});
}
// Supports `riegeli::Debug()`.
template <typename DebugStream>
friend void RiegeliDebug(const IntrusiveSharedPtr& src, DebugStream& dest) {
dest.Debug(src.get());
}
// Supports `MemoryEstimator`.
template <typename MemoryEstimator>
friend void RiegeliRegisterSubobjects(const IntrusiveSharedPtr* self,
MemoryEstimator& memory_estimator) {
if (memory_estimator.RegisterNode(self->get())) {
memory_estimator.RegisterDynamicObject(self->get());
}
}
private:
// For converting from a `SharedPtr` with a compatible type.
template <typename SubT>
friend class IntrusiveSharedPtr;
using pointer = T*; // For `ABSL_NULLABILITY_COMPATIBLE`.
struct Unrefer {
void operator()(T* ptr) const { ptr->Unref(); }
};
template <typename SubT>
static SubT* Ref(SubT* ptr) {
if (ptr != nullptr) ptr->Ref();
return ptr;
}
template <typename DependentT>
struct IsAssignable
: std::conjunction<
intrusive_shared_ptr_internal::HasHasUniqueOwner<DependentT>,
std::disjunction<
std::negation<std::has_virtual_destructor<DependentT>>,
std::is_final<DependentT>>,
std::is_move_assignable<DependentT>> {};
void ResetImpl(Initializer<T> value) {
if constexpr (IsAssignable<T>::value) {
if (IsUnique()) {
*ptr_ = std::move(value);
return;
}
}
ptr_ = std::move(value);
}
std::unique_ptr<T, Unrefer> ptr_;
};
template <typename T>
explicit IntrusiveSharedPtr(T* ptr, PassOwnership = kPassOwnership)
-> IntrusiveSharedPtr<T>;
template <typename T>
explicit IntrusiveSharedPtr(T* ptr, ShareOwnership) -> IntrusiveSharedPtr<T>;
template <typename T, std::enable_if_t<!std::is_pointer_v<T>, int> = 0>
explicit IntrusiveSharedPtr(T&& value) -> IntrusiveSharedPtr<TargetT<T>>;
} // namespace riegeli
#endif // RIEGELI_BASE_INTRUSIVE_SHARED_PTR_H_