xref: /aosp_15_r20/external/pigweed/pw_intrusive_ptr/public/pw_intrusive_ptr/intrusive_ptr.h (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1 // Copyright 2022 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14 #pragma once
15 
16 #include <cstddef>
17 #include <type_traits>
18 #include <utility>
19 
20 #include "pw_intrusive_ptr/internal/ref_counted_base.h"
21 #include "pw_intrusive_ptr/recyclable.h"
22 
23 namespace pw {
24 
25 // Shared pointer that relies on the stored object for the refcounting.
26 //
27 // T should be either a subclass of `RefCounted` (preferred way) or
28 // implement AddRef()/ReleaseRef() by itself.
29 //
30 // IntrusivePtr API follows the std::shared_ptr API, but doesn't provide weak
31 // pointers and some of the functionality such as reset(), owner_before(),
32 // operator[] or unique().
33 //
34 // Similar to the std::make_shared for the std::shared_ptr, IntrusivePtr
35 // provides the MakeRefCounted() helper.
36 //
37 // IntrusivePtr by itself doesn't provide any thread-safety guarantees but if T
38 // is a subclass from `RefCounted` - it is guaranteed to have atomic reference
39 // counter operations.
40 template <typename T>
41 class IntrusivePtr final {
42  public:
43   using element_type = T;
44 
45   // Constructs an empty IntrusivePtr.
IntrusivePtr()46   constexpr IntrusivePtr() : ptr_(nullptr) {}
47 
48   // Constructs an empty IntrusivePtr.
49   //
50   // NOLINTNEXTLINE(google-explicit-constructor)
IntrusivePtr(std::nullptr_t)51   constexpr IntrusivePtr(std::nullptr_t) : IntrusivePtr() {}
52 
53   // Constructs an IntrusivePtr from already allocated pointer.
54   //
55   // IntrusivePtr owns this pointer after this wrapping. All operations with the
56   // pointer should be done through IntrusivePtr after the wrapping or while at
57   // least one IntrusivePtr object owning it is in scope.
58   //
59   // IntrusivePtr can be used with either heap-allocated pointers or
60   // stack/static allocated objects if T is Recyclable. An attempt to wrap a
61   // stack-allocated object with a non-Recyclable IntrusivePtr will result in a
62   // crash on destruction.
IntrusivePtr(T * p)63   explicit IntrusivePtr(T* p) : ptr_(p) {
64     if (ptr_) {
65       ptr_->AddRef();
66     }
67   }
68 
IntrusivePtr(const IntrusivePtr & other)69   IntrusivePtr(const IntrusivePtr& other) : IntrusivePtr(other.ptr_) {}
70 
IntrusivePtr(IntrusivePtr && other)71   IntrusivePtr(IntrusivePtr&& other) noexcept
72       : ptr_(std::exchange(other.ptr_, nullptr)) {}
73 
74   template <typename U>
75   // NOLINTNEXTLINE(google-explicit-constructor)
IntrusivePtr(const IntrusivePtr<U> & other)76   IntrusivePtr(const IntrusivePtr<U>& other) : IntrusivePtr(other.ptr_) {
77     CheckConversionAllowed<U>();
78   }
79 
80   template <typename U>
81   // NOLINTNEXTLINE(google-explicit-constructor)
IntrusivePtr(IntrusivePtr<U> && other)82   IntrusivePtr(IntrusivePtr<U>&& other)
83       : ptr_(std::exchange(other.ptr_, nullptr)) {
84     CheckConversionAllowed<U>();
85   }
86 
87   IntrusivePtr& operator=(const IntrusivePtr& other) {
88     if (&other == this) {
89       return *this;
90     }
91     IntrusivePtr(other).swap(*this);
92     return *this;
93   }
94 
95   IntrusivePtr& operator=(IntrusivePtr&& other) noexcept {
96     if (&other == this) {
97       return *this;
98     }
99     IntrusivePtr(std::move(other)).swap(*this);
100     return *this;
101   }
102 
~IntrusivePtr()103   ~IntrusivePtr() {
104     T* ptr = ptr_;
105     // Clear ptr_ to help detect re-entrancy in ~T.
106     ptr_ = nullptr;
107     if (ptr && ptr->ReleaseRef()) {
108       recycle_or_delete(ptr);
109     }
110   }
111 
swap(IntrusivePtr & other)112   void swap(IntrusivePtr& other) { std::swap(ptr_, other.ptr_); }
113 
get()114   T* get() const { return ptr_; }
115 
use_count()116   int32_t use_count() const { return ptr_ ? ptr_->ref_count() : 0; }
117 
118   T& operator*() const { return *ptr_; }
119 
120   T* operator->() const { return ptr_; }
121 
122   explicit operator bool() const { return ptr_; }
123 
124  private:
125   template <typename U>
126   friend class IntrusivePtr;
127 
128   // Compilation-time verification that we can convert from IntrusivePtr<U> to
129   // IntrusivePtr<T>.
130   template <typename U>
CheckConversionAllowed()131   constexpr void CheckConversionAllowed() {
132     static_assert(
133         std::is_convertible_v<U*, T*> &&
134             (std::has_virtual_destructor_v<T> || std::is_same_v<T, const U>),
135         "Cannot convert IntrusivePtr<U> to IntrusivePtr<T> unless T has a "
136         "virtual destructor or T == const U.");
137   }
138 
139   // Support Ts that inherit from the Recyclable mixin.
recycle_or_delete(T * ptr)140   static void recycle_or_delete(T* ptr) {
141     if constexpr (::pw::internal::has_pw_recycle_v<T>) {
142       ::pw::internal::recycle<T>(ptr);
143     } else {
144       delete ptr;
145     }
146   }
147 
148   T* ptr_;
149 };
150 
151 // Base class to be used with the IntrusivePtr. Doesn't provide any public
152 // methods.
153 //
154 // Provides an atomic-based reference counting. Atomics are used irrespective of
155 // the settings, which makes it different from the std::shared_ptr (that relies
156 // on the threading support settings to determine if atomics should be used for
157 // the control block or not).
158 //
159 // RefCounted MUST never be used as a pointer type to store derived objects -
160 // it doesn't provide a virtual destructor.
161 template <typename T>
162 class RefCounted : private internal::RefCountedBase {
163  public:
164   // Type alias for the IntrusivePtr of ref-counted type.
165   using Ptr = IntrusivePtr<T>;
166 
167  private:
168   template <typename U>
169   friend class IntrusivePtr;
170 };
171 
172 template <typename T, typename U>
173 inline bool operator==(const IntrusivePtr<T>& lhs, const IntrusivePtr<U>& rhs) {
174   return lhs.get() == rhs.get();
175 }
176 
177 template <typename T, typename U>
178 inline bool operator!=(const IntrusivePtr<T>& lhs, const IntrusivePtr<U>& rhs) {
179   return !(lhs == rhs);
180 }
181 
182 template <typename T>
183 inline bool operator==(const IntrusivePtr<T>& ptr, std::nullptr_t) {
184   return ptr.get() == nullptr;
185 }
186 
187 template <typename T>
188 inline bool operator!=(const IntrusivePtr<T>& ptr, std::nullptr_t) {
189   return ptr.get() != nullptr;
190 }
191 
192 template <typename T>
193 inline bool operator==(std::nullptr_t, const IntrusivePtr<T>& ptr) {
194   return ptr.get() == nullptr;
195 }
196 
197 template <typename T>
198 inline bool operator!=(std::nullptr_t, const IntrusivePtr<T>& ptr) {
199   return ptr.get() != nullptr;
200 }
201 
202 // Constructs an IntrusivePtr<T> with a given set of arguments for the T
203 // constructor.
204 template <typename T, typename... Args>
MakeRefCounted(Args &&...args)205 IntrusivePtr<T> MakeRefCounted(Args&&... args) {
206   return IntrusivePtr(new T(std::forward<Args>(args)...));
207 }
208 
209 }  // namespace pw
210