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