1 // Copyright 2025 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 <memory> 18 #include <type_traits> 19 #include <utility> 20 21 #include "pw_allocator/capability.h" 22 #include "pw_allocator/hardening.h" 23 24 namespace pw { 25 26 // Forward declarations. 27 class Allocator; 28 class Deallocator; 29 30 namespace allocator::internal { 31 32 // Empty struct used in place of the `size_` field when the pointer type is not 33 // an array type. 34 struct Empty {}; 35 36 /// This class simply provides type-erased static methods to check capabilities 37 /// and manage memory in a managed pointer. This allows `ManagedPtr<T>` to 38 /// be declared without a complete declaration of `Allocator` or 39 /// `Deallocator`, breaking the dependency cycle between `ManagedPtr<T>` and 40 /// `Allocator`methods including `MakeUnique<T>()` and `MakeShared<T>()`. 41 class BaseManagedPtr { 42 protected: 43 static bool HasCapability(Deallocator* deallocator, Capability capability); 44 static void Deallocate(Deallocator* deallocator, void* ptr); 45 static bool Resize(Allocator* deallocator, void* ptr, size_t new_size); 46 }; 47 48 /// This class extends `BaseManagerPtr` to provide type checking for methods 49 /// including the assignment operators. It has no concept of ownership of the 50 /// object or its memory and is thus "weak". 51 template <typename T> 52 class WeakManagedPtr : public BaseManagedPtr { 53 protected: 54 using element_type = std::conditional_t<std::is_array_v<T>, 55 typename std::remove_extent<T>::type, 56 T>; 57 58 template <typename U> CheckAssignable()59 constexpr void CheckAssignable() { 60 static_assert( 61 std::is_assignable_v<element_type*&, 62 typename WeakManagedPtr<U>::element_type*>, 63 "Attempted to construct a WeakManagedPtr<T> from a WeakManagedPtr<U> " 64 "where U* is not assignable to T*."); 65 } 66 67 private: 68 // Allow WeakManagedPtr<T> to access WeakManagedPtr<U> and vice versa. 69 template <typename> 70 friend class WeakManagedPtr; 71 }; 72 73 /// Smart pointer to an object in memory provided by a `Deallocator`. 74 /// 75 /// This type provides methods for accessing and destroying allocated objects 76 /// wrapped by RAII-style smart pointers. It is not designed to be used 77 /// directly, and instead should be extend to create shart pointers that call 78 /// the base methods at the appropriate time, e.g. `UniquePtr` calls 79 /// `Destroy` as part of `Reset`. 80 template <typename T> 81 class ManagedPtr : public WeakManagedPtr<T> { 82 protected: 83 using Base = WeakManagedPtr<T>; 84 using element_type = typename Base::element_type; 85 86 public: 87 // Derived classes must explicitly provide any copy- and/or move- constructors 88 // and assignment operators they intend to support. 89 ManagedPtr(const ManagedPtr&) = delete; 90 ManagedPtr& operator=(const ManagedPtr&) = delete; 91 92 /// `operator bool` is not provided in order to ensure that there is no 93 /// confusion surrounding `if (foo)` vs. `if (*foo)`. 94 /// 95 /// `nullptr` checking should instead use `if (foo == nullptr)`. 96 explicit operator bool() const = delete; 97 98 /// Returns whether this `ManagedPtr` is in an "empty" (`nullptr`) state. 99 bool operator==(std::nullptr_t) const { return value_ == nullptr; } 100 101 /// Returns whether this `ManagedPtr` is not in an "empty" (`nullptr`) 102 /// state. 103 bool operator!=(std::nullptr_t) const { return value_ != nullptr; } 104 105 /// Returns the underlying (possibly null) pointer. get()106 constexpr element_type* get() const noexcept { return value_; } 107 108 /// Permits accesses to members of `T` via `ptr->Member`. 109 /// 110 /// The behavior of this operation is undefined if this `ManagedPtr` is in 111 /// an "empty" (`nullptr`) state. 112 constexpr element_type* operator->() const noexcept { 113 if constexpr (Hardening::kIncludesRobustChecks) { 114 PW_ASSERT(value_ != nullptr); 115 } 116 return value_; 117 } 118 119 /// Returns a reference to any underlying value. 120 /// 121 /// The behavior of this operation is undefined if this `ManagedPtr` is in 122 /// an "empty" (`nullptr`) state. 123 constexpr element_type& operator*() const { 124 if constexpr (Hardening::kIncludesRobustChecks) { 125 PW_ASSERT(value_ != nullptr); 126 } 127 return *value_; 128 } 129 130 /// Returns a reference to the element at the given index. 131 /// 132 /// The behavior of this operation is undefined if this `ManagedPtr` is in 133 /// an "empty" (`nullptr`) state. 134 constexpr element_type& operator[](size_t index) const { 135 static_assert(std::is_array_v<T>, 136 "operator[] cannot be called with non-array types"); 137 if constexpr (Hardening::kIncludesRobustChecks) { 138 PW_ASSERT(value_ != nullptr); 139 } 140 return value_[index]; 141 } 142 143 protected: 144 constexpr ManagedPtr() = default; 145 146 /// Constructs a `ManagedPtr` from an already-allocated value and size. ManagedPtr(element_type * value)147 constexpr explicit ManagedPtr(element_type* value) : value_(value) {} 148 149 /// Copies details from another object without releasing it. 150 template <typename U> CopyFrom(const ManagedPtr<U> & other)151 void CopyFrom(const ManagedPtr<U>& other) { 152 Base::template CheckAssignable<U>(); 153 value_ = other.value_; 154 } 155 156 /// Releases a value from the `ManagedPtr`. 157 /// 158 /// After this call, the object will have an "empty" (`nullptr`) value. Release()159 element_type* Release() { 160 element_type* value = value_; 161 value_ = nullptr; 162 return value; 163 } 164 165 /// Swaps the managed pointer and deallocator of this and another object. Swap(ManagedPtr & other)166 void Swap(ManagedPtr& other) noexcept { std::swap(value_, other.value_); } 167 168 /// Destroys the objects in this object's memory without deallocating it. 169 /// 170 /// This will fail to compile if it is called with an array type. Destroy()171 void Destroy() { 172 static_assert(!std::is_array_v<T>, 173 "Destroy() cannot be called with array types"); 174 std::destroy_at(value_); 175 } 176 177 /// Destroys the objects in this object's memory without deallocating it. 178 /// 179 /// This will fail to compile if it is called with a non-array type. Destroy(size_t size)180 void Destroy(size_t size) { 181 static_assert(std::is_array_v<T>, 182 "Destrot(size_t) cannot be called with non-array types"); 183 std::destroy_n(value_, size); 184 } 185 186 private: 187 // Allow ManagedPtr<T> to access ManagedPtr<U> and vice versa. 188 template <typename> 189 friend class ManagedPtr; 190 191 /// A pointer to the contained value. 192 element_type* value_ = nullptr; 193 }; 194 195 } // namespace allocator::internal 196 } // namespace pw 197 198 /// Returns whether this `ManagedPtr` is in an "empty" (`nullptr`) state. 199 template <typename T> 200 bool operator==(std::nullptr_t, 201 const pw::allocator::internal::ManagedPtr<T>& ptr) { 202 return ptr == nullptr; 203 } 204 205 /// Returns whether this `ManagedPtr` is not in an "empty" (`nullptr`) 206 /// state. 207 template <typename T> 208 bool operator!=(std::nullptr_t, 209 const pw::allocator::internal::ManagedPtr<T>& ptr) { 210 return ptr != nullptr; 211 } 212