• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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