InitializedOnce.h (9423B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 // Class template for objects that can only be initialized once. 8 9 #ifndef mozilla_mfbt_initializedonce_h__ 10 #define mozilla_mfbt_initializedonce_h__ 11 12 #include "mozilla/Assertions.h" 13 #include "mozilla/Maybe.h" 14 15 #include <type_traits> 16 17 namespace mozilla { 18 19 namespace detail { 20 21 enum struct InitWhen { InConstructorOnly, LazyAllowed }; 22 enum struct DestroyWhen { EarlyAllowed, InDestructorOnly }; 23 24 namespace ValueCheckPolicies { 25 template <typename T> 26 struct AllowAnyValue { 27 constexpr static bool Check(const T& /*aValue*/) { return true; } 28 }; 29 30 template <typename T> 31 struct ConvertsToTrue { 32 constexpr static bool Check(const T& aValue) { 33 return static_cast<bool>(aValue); 34 } 35 }; 36 } // namespace ValueCheckPolicies 37 38 // A kind of mozilla::Maybe that can only be initialized and cleared once. It 39 // cannot be re-initialized. This is a more stateful than a const Maybe<T> in 40 // that it can be cleared, but much less stateful than a non-const Maybe<T> 41 // which could be reinitialized multiple times. Can only be used with const T 42 // to ensure that the contents cannot be modified either. 43 // TODO: Make constructors constexpr when Maybe's constructors are constexpr 44 // (Bug 1601336). 45 template <typename T, InitWhen InitWhenVal, DestroyWhen DestroyWhenVal, 46 template <typename> class ValueCheckPolicy = 47 ValueCheckPolicies::AllowAnyValue> 48 class InitializedOnce final { 49 static_assert(std::is_const_v<T>); 50 using MaybeType = Maybe<std::remove_const_t<T>>; 51 52 template <typename Dummy> 53 using requires_lazy_init_allowed = 54 std::enable_if_t<InitWhenVal == InitWhen::LazyAllowed, Dummy>; 55 56 template <typename Dummy> 57 using requires_early_destroy_allowed = 58 std::enable_if_t<DestroyWhenVal == DestroyWhen::EarlyAllowed, Dummy>; 59 60 public: 61 using ValueType = T; 62 63 template <typename Dummy = void, typename = requires_lazy_init_allowed<Dummy>> 64 explicit constexpr InitializedOnce() {} 65 66 // note: aArg0 is named separately here to disallow calling this with no 67 // arguments. The default constructor should only be available conditionally 68 // and is declared above. 69 template <typename Arg0, typename... Args> 70 explicit constexpr InitializedOnce(Arg0&& aArg0, Args&&... aArgs) 71 : mMaybe{Some(std::remove_const_t<T>{std::forward<Arg0>(aArg0), 72 std::forward<Args>(aArgs)...})} { 73 MOZ_ASSERT(ValueCheckPolicy<T>::Check(*mMaybe)); 74 } 75 76 InitializedOnce(const InitializedOnce&) = delete; 77 InitializedOnce(InitializedOnce&& aOther) : mMaybe{std::move(aOther.mMaybe)} { 78 static_assert(DestroyWhenVal == DestroyWhen::EarlyAllowed); 79 #ifdef DEBUG 80 aOther.mWasReset = true; 81 #endif 82 } 83 InitializedOnce& operator=(const InitializedOnce&) = delete; 84 InitializedOnce& operator=(InitializedOnce&& aOther) { 85 static_assert(InitWhenVal == InitWhen::LazyAllowed && 86 DestroyWhenVal == DestroyWhen::EarlyAllowed); 87 MOZ_ASSERT(!mWasReset); 88 MOZ_ASSERT(!mMaybe); 89 mMaybe.~MaybeType(); 90 new (&mMaybe) MaybeType{std::move(aOther.mMaybe)}; 91 #ifdef DEBUG 92 aOther.mWasReset = true; 93 #endif 94 return *this; 95 } 96 97 template <typename... Args, typename Dummy = void, 98 typename = requires_lazy_init_allowed<Dummy>> 99 constexpr void init(Args&&... aArgs) { 100 MOZ_ASSERT(mMaybe.isNothing()); 101 MOZ_ASSERT(!mWasReset); 102 mMaybe.emplace(std::remove_const_t<T>{std::forward<Args>(aArgs)...}); 103 MOZ_ASSERT(ValueCheckPolicy<T>::Check(*mMaybe)); 104 } 105 106 constexpr explicit operator bool() const { return isSome(); } 107 constexpr bool isSome() const { return mMaybe.isSome(); } 108 constexpr bool isNothing() const { return mMaybe.isNothing(); } 109 110 constexpr T& operator*() const { return *mMaybe; } 111 constexpr T* operator->() const { return mMaybe.operator->(); } 112 113 constexpr T& ref() const { return mMaybe.ref(); } 114 115 template <typename Dummy = void, 116 typename = requires_early_destroy_allowed<Dummy>> 117 void destroy() { 118 MOZ_ASSERT(mMaybe.isSome()); 119 maybeDestroy(); 120 } 121 122 template <typename Dummy = void, 123 typename = requires_early_destroy_allowed<Dummy>> 124 void maybeDestroy() { 125 mMaybe.reset(); 126 #ifdef DEBUG 127 mWasReset = true; 128 #endif 129 } 130 131 template <typename Dummy = void, 132 typename = requires_early_destroy_allowed<Dummy>> 133 T release() { 134 MOZ_ASSERT(mMaybe.isSome()); 135 auto res = std::move(mMaybe.ref()); 136 destroy(); 137 return res; 138 } 139 140 private: 141 MaybeType mMaybe; 142 #ifdef DEBUG 143 bool mWasReset = false; 144 #endif 145 }; 146 147 template <typename T, InitWhen InitWhenVal, DestroyWhen DestroyWhenVal, 148 template <typename> class ValueCheckPolicy> 149 class LazyInitializer { 150 public: 151 explicit LazyInitializer(InitializedOnce<T, InitWhenVal, DestroyWhenVal, 152 ValueCheckPolicy>& aLazyInitialized) 153 : mLazyInitialized{aLazyInitialized} {} 154 155 template <typename U> 156 LazyInitializer& operator=(U&& aValue) { 157 mLazyInitialized.init(std::forward<U>(aValue)); 158 return *this; 159 } 160 161 LazyInitializer(const LazyInitializer&) = delete; 162 LazyInitializer& operator=(const LazyInitializer&) = delete; 163 164 private: 165 InitializedOnce<T, InitWhenVal, DestroyWhenVal, ValueCheckPolicy>& 166 mLazyInitialized; 167 }; 168 169 } // namespace detail 170 171 // The following *InitializedOnce* template aliases allow to declare class 172 // member variables that can only be initialized once, but maybe destroyed 173 // earlier explicitly than in the containing classes destructor. 174 // The intention is to restrict the possible state transitions for member 175 // variables that can almost be const, but not quite. This may be particularly 176 // useful for classes with a lot of members. Uses in other contexts, e.g. as 177 // local variables, are possible, but probably seldom useful. They can only be 178 // instantiated with a const element type. Any misuses that cannot be detected 179 // at compile time trigger a MOZ_ASSERT at runtime. Individually spelled out 180 // assertions for these aspects are not necessary, which may improve the 181 // readability of the code without impairing safety. 182 // 183 // The base variant InitializedOnce requires initialization in the constructor, 184 // but allows early destruction using destroy(), and allow move construction. It 185 // is similar to Maybe<const T> in some sense, but a Maybe<const T> could be 186 // reinitialized arbitrarily. InitializedOnce expresses the intent not to do 187 // this, and prohibits reinitialization. 188 // 189 // The Lazy* variants allow default construction, and can be initialized lazily 190 // using init() in that case, but it cannot be reinitialized either. They do not 191 // allow early destruction. 192 // 193 // The Lazy*EarlyDestructible variants allow lazy initialization, early 194 // destruction, move construction and move assignment. This should be used only 195 // when really required. 196 // 197 // The *NotNull variants only allow initialization with values that convert to 198 // bool as true. They are named NotNull because the typical use case is with 199 // (smart) pointer types, but any other type convertible to bool will also work 200 // analogously. 201 // 202 // There is no variant combining detail::DestroyWhen::InConstructorOnly with 203 // detail::DestroyWhen::InDestructorOnly because this would be equivalent to a 204 // const member. 205 // 206 // For special cases, e.g. requiring custom value check policies, 207 // detail::InitializedOnce might be instantiated directly, but be mindful when 208 // doing this. 209 210 template <typename T> 211 using InitializedOnce = 212 detail::InitializedOnce<T, detail::InitWhen::InConstructorOnly, 213 detail::DestroyWhen::EarlyAllowed>; 214 215 template <typename T> 216 using InitializedOnceNotNull = 217 detail::InitializedOnce<T, detail::InitWhen::InConstructorOnly, 218 detail::DestroyWhen::EarlyAllowed, 219 detail::ValueCheckPolicies::ConvertsToTrue>; 220 221 template <typename T> 222 using LazyInitializedOnce = 223 detail::InitializedOnce<T, detail::InitWhen::LazyAllowed, 224 detail::DestroyWhen::InDestructorOnly>; 225 226 template <typename T> 227 using LazyInitializedOnceNotNull = 228 detail::InitializedOnce<T, detail::InitWhen::LazyAllowed, 229 detail::DestroyWhen::InDestructorOnly, 230 detail::ValueCheckPolicies::ConvertsToTrue>; 231 232 template <typename T> 233 using LazyInitializedOnceEarlyDestructible = 234 detail::InitializedOnce<T, detail::InitWhen::LazyAllowed, 235 detail::DestroyWhen::EarlyAllowed>; 236 237 template <typename T> 238 using LazyInitializedOnceNotNullEarlyDestructible = 239 detail::InitializedOnce<T, detail::InitWhen::LazyAllowed, 240 detail::DestroyWhen::EarlyAllowed, 241 detail::ValueCheckPolicies::ConvertsToTrue>; 242 243 template <typename T, detail::InitWhen InitWhenVal, 244 detail::DestroyWhen DestroyWhenVal, 245 template <typename> class ValueCheckPolicy> 246 auto do_Init(detail::InitializedOnce<T, InitWhenVal, DestroyWhenVal, 247 ValueCheckPolicy>& aLazyInitialized) { 248 return detail::LazyInitializer(aLazyInitialized); 249 } 250 251 } // namespace mozilla 252 253 #endif