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 <type_traits> 17 #include <utility> 18 19 #include "pw_polyfill/language_feature_macros.h" 20 21 namespace pw { 22 23 /// Helper type to create a global or function-local static variable of type `T` 24 /// when `T` has a non-trivial destructor. Storing a `T` in a 25 /// `pw::NoDestructor<T>` will prevent `~T()` from running, even when the 26 /// variable goes out of scope. 27 /// 28 /// This class is useful when a variable has static storage duration but its 29 /// type has a non-trivial destructor. Destructor ordering is not defined and 30 /// can cause issues in multithreaded environments. Additionally, removing 31 /// destructor calls can save code size. 32 /// 33 /// Except in generic code, do not use `pw::NoDestructor<T>` with trivially 34 /// destructible types. Use the type directly instead. If the variable can be 35 /// `constexpr`, make it `constexpr`. 36 /// 37 /// `pw::NoDestructor<T>` provides a similar API to `std::optional`. Use `*` or 38 /// `->` to access the wrapped type. 39 /// 40 /// `NoDestructor` instances can be `constinit` if `T` has a `constexpr` 41 /// constructor. In C++20, `NoDestructor` instances may be `constexpr` if `T` 42 /// has a `constexpr` destructor. `NoDestructor` is unnecessary for literal 43 /// types. 44 /// 45 /// @note `NoDestructor<T>` instances may be constant initialized, whether they 46 /// are `constinit` or not. This may be undesirable for large objects, since 47 /// moving them from `.bss` to `.data` increases binary size. To prevent this, 48 /// use `pw::RuntimeInitGlobal`, which prevents constant initialization and 49 /// removes the destructor. 50 /// 51 /// Example usage: 52 /// @code{.cpp} 53 /// 54 /// pw::sync::Mutex& GetMutex() { 55 /// // Use NoDestructor to avoid running the mutex destructor when exit-time 56 /// // destructors run. 57 /// static const pw::NoDestructor<pw::sync::Mutex> global_mutex; 58 /// return *global_mutex; 59 /// } 60 /// 61 /// @endcode 62 /// 63 /// In Clang, `pw::NoDestructor` can be replaced with the 64 /// <a href="https://clang.llvm.org/docs/AttributeReference.html#no-destroy"> 65 /// [[clang::no_destroy]]</a> attribute. `pw::NoDestructor<T>` is similar to 66 /// Chromium’s `base::NoDestructor<T>` in <a 67 /// href="https://chromium.googlesource.com/chromium/src/base/+/5ea6e31f927aa335bfceb799a2007c7f9007e680/no_destructor.h"> 68 /// src/base/no_destructor.h</a>. 69 /// 70 /// @warning Misuse of `NoDestructor` can cause memory leaks and other problems. 71 /// Only skip destructors when you know it is safe to do so. 72 template <typename T> 73 class NoDestructor { 74 public: 75 using value_type = T; 76 77 // Initializes a T in place. 78 // 79 // This overload is disabled when it might collide with copy/move. 80 template <typename... Args, 81 typename std::enable_if<!std::is_same<void(std::decay_t<Args>&...), 82 void(NoDestructor&)>::value, 83 int>::type = 0> NoDestructor(Args &&...args)84 explicit constexpr NoDestructor(Args&&... args) 85 : storage_(std::forward<Args>(args)...) {} 86 87 // Move or copy from the contained type. This allows for construction from an 88 // initializer list, e.g. for std::vector. NoDestructor(const T & x)89 explicit constexpr NoDestructor(const T& x) : storage_(x) {} NoDestructor(T && x)90 explicit constexpr NoDestructor(T&& x) : storage_(std::move(x)) {} 91 92 NoDestructor(const NoDestructor&) = delete; 93 NoDestructor& operator=(const NoDestructor&) = delete; 94 95 ~NoDestructor() = default; 96 97 constexpr const T& operator*() const { return get(); } 98 constexpr T& operator*() { return get(); } 99 100 constexpr const T* operator->() const { return &get(); } 101 constexpr T* operator->() { return &get(); } 102 103 private: get()104 constexpr T& get() { 105 if constexpr (std::is_trivially_destructible_v<T>) { 106 return storage_; 107 } else { 108 return storage_.value; 109 } 110 } 111 get()112 constexpr const T& get() const { 113 if constexpr (std::is_trivially_destructible_v<T>) { 114 return storage_; 115 } else { 116 return storage_.value; 117 } 118 } 119 120 union NonTrivialStorage { 121 template <typename... Args> NonTrivialStorage(Args &&...args)122 constexpr NonTrivialStorage(Args&&... args) 123 : value(std::forward<Args>(args)...) {} 124 125 // Unfortunately, this cannot be trivially destructible because having a 126 // union member of non-trivially destructible T implicitly deletes the 127 // destructor. Trivial destruction may be possible in future C++ standards. ~NonTrivialStorage()128 PW_CONSTEXPR_CPP20 ~NonTrivialStorage() {} 129 130 T value; 131 }; 132 133 std::conditional_t<std::is_trivially_destructible_v<T>, T, NonTrivialStorage> 134 storage_; 135 }; 136 137 } // namespace pw 138