summaryrefslogtreecommitdiffstats
path: root/mfbt/InitializedOnce.h
blob: aac152df35a0f1d3bb8de453d110b843bd0f4c33 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// Class template for objects that can only be initialized once.

#ifndef mozilla_mfbt_initializedonce_h__
#define mozilla_mfbt_initializedonce_h__

#include "mozilla/Assertions.h"
#include "mozilla/Maybe.h"

#include <type_traits>

namespace mozilla {

namespace detail {

enum struct InitWhen { InConstructorOnly, LazyAllowed };
enum struct DestroyWhen { EarlyAllowed, InDestructorOnly };

namespace ValueCheckPolicies {
template <typename T>
struct AllowAnyValue {
  constexpr static bool Check(const T& /*aValue*/) { return true; }
};

template <typename T>
struct ConvertsToTrue {
  constexpr static bool Check(const T& aValue) {
    return static_cast<bool>(aValue);
  }
};
}  // namespace ValueCheckPolicies

// A kind of mozilla::Maybe that can only be initialized and cleared once. It
// cannot be re-initialized. This is a more stateful than a const Maybe<T> in
// that it can be cleared, but much less stateful than a non-const Maybe<T>
// which could be reinitialized multiple times. Can only be used with const T
// to ensure that the contents cannot be modified either.
// TODO: Make constructors constexpr when Maybe's constructors are constexpr
// (Bug 1601336).
template <typename T, InitWhen InitWhenVal, DestroyWhen DestroyWhenVal,
          template <typename> class ValueCheckPolicy =
              ValueCheckPolicies::AllowAnyValue>
class InitializedOnce final {
  static_assert(std::is_const_v<T>);
  using MaybeType = Maybe<std::remove_const_t<T>>;

 public:
  using ValueType = T;

  template <typename Dummy = void>
  explicit constexpr InitializedOnce(
      std::enable_if_t<InitWhenVal == InitWhen::LazyAllowed, Dummy>* =
          nullptr) {}

  // note: aArg0 is named separately here to disallow calling this with no
  // arguments. The default constructor should only be available conditionally
  // and is declared above.
  template <typename Arg0, typename... Args>
  explicit constexpr InitializedOnce(Arg0&& aArg0, Args&&... aArgs)
      : mMaybe{Some(std::remove_const_t<T>{std::forward<Arg0>(aArg0),
                                           std::forward<Args>(aArgs)...})} {
    MOZ_ASSERT(ValueCheckPolicy<T>::Check(*mMaybe));
  }

  InitializedOnce(const InitializedOnce&) = delete;
  InitializedOnce(InitializedOnce&& aOther) : mMaybe{std::move(aOther.mMaybe)} {
    static_assert(DestroyWhenVal == DestroyWhen::EarlyAllowed);
#ifdef DEBUG
    aOther.mWasReset = true;
#endif
  }
  InitializedOnce& operator=(const InitializedOnce&) = delete;
  InitializedOnce& operator=(InitializedOnce&& aOther) {
    static_assert(InitWhenVal == InitWhen::LazyAllowed &&
                  DestroyWhenVal == DestroyWhen::EarlyAllowed);
    MOZ_ASSERT(!mWasReset);
    MOZ_ASSERT(!mMaybe);
    mMaybe.~MaybeType();
    new (&mMaybe) MaybeType{std::move(aOther.mMaybe)};
#ifdef DEBUG
    aOther.mWasReset = true;
#endif
    return *this;
  }

  template <typename... Args, typename Dummy = void>
  constexpr std::enable_if_t<InitWhenVal == InitWhen::LazyAllowed, Dummy> init(
      Args&&... aArgs) {
    MOZ_ASSERT(mMaybe.isNothing());
    MOZ_ASSERT(!mWasReset);
    mMaybe.emplace(std::remove_const_t<T>{std::forward<Args>(aArgs)...});
    MOZ_ASSERT(ValueCheckPolicy<T>::Check(*mMaybe));
  }

  constexpr explicit operator bool() const { return isSome(); }
  constexpr bool isSome() const { return mMaybe.isSome(); }
  constexpr bool isNothing() const { return mMaybe.isNothing(); }

  constexpr T& operator*() const { return *mMaybe; }
  constexpr T* operator->() const { return mMaybe.operator->(); }

  constexpr T& ref() const { return mMaybe.ref(); }

  template <typename Dummy = void>
  std::enable_if_t<DestroyWhenVal == DestroyWhen::EarlyAllowed, Dummy>
  destroy() {
    MOZ_ASSERT(mMaybe.isSome());
    maybeDestroy();
  }

  template <typename Dummy = void>
  std::enable_if_t<DestroyWhenVal == DestroyWhen::EarlyAllowed, Dummy>
  maybeDestroy() {
    mMaybe.reset();
#ifdef DEBUG
    mWasReset = true;
#endif
  }

  template <typename Dummy = T>
  std::enable_if_t<DestroyWhenVal == DestroyWhen::EarlyAllowed, Dummy>
  release() {
    MOZ_ASSERT(mMaybe.isSome());
    auto res = std::move(mMaybe.ref());
    destroy();
    return res;
  }

 private:
  MaybeType mMaybe;
#ifdef DEBUG
  bool mWasReset = false;
#endif
};

template <typename T, InitWhen InitWhenVal, DestroyWhen DestroyWhenVal,
          template <typename> class ValueCheckPolicy>
class LazyInitializer {
 public:
  explicit LazyInitializer(InitializedOnce<T, InitWhenVal, DestroyWhenVal,
                                           ValueCheckPolicy>& aLazyInitialized)
      : mLazyInitialized{aLazyInitialized} {}

  template <typename U>
  LazyInitializer& operator=(U&& aValue) {
    mLazyInitialized.init(std::forward<U>(aValue));
    return *this;
  }

  LazyInitializer(const LazyInitializer&) = delete;
  LazyInitializer& operator=(const LazyInitializer&) = delete;

 private:
  InitializedOnce<T, InitWhenVal, DestroyWhenVal, ValueCheckPolicy>&
      mLazyInitialized;
};

}  // namespace detail

// The following *InitializedOnce* template aliases allow to declare class
// member variables that can only be initialized once, but maybe destroyed
// earlier explicitly than in the containing classes destructor.
// The intention is to restrict the possible state transitions for member
// variables that can almost be const, but not quite. This may be particularly
// useful for classes with a lot of members. Uses in other contexts, e.g. as
// local variables, are possible, but probably seldom useful. They can only be
// instantiated with a const element type. Any misuses that cannot be detected
// at compile time trigger a MOZ_ASSERT at runtime. Individually spelled out
// assertions for these aspects are not necessary, which may improve the
// readability of the code without impairing safety.
//
// The base variant InitializedOnce requires initialization in the constructor,
// but allows early destruction using destroy(), and allow move construction. It
// is similar to Maybe<const T> in some sense, but a Maybe<const T> could be
// reinitialized arbitrarily. InitializedOnce expresses the intent not to do
// this, and prohibits reinitialization.
//
// The Lazy* variants allow default construction, and can be initialized lazily
// using init() in that case, but it cannot be reinitialized either. They do not
// allow early destruction.
//
// The Lazy*EarlyDestructible variants allow lazy initialization, early
// destruction, move construction and move assignment. This should be used only
// when really required.
//
// The *NotNull variants only allow initialization with values that convert to
// bool as true. They are named NotNull because the typical use case is with
// (smart) pointer types, but any other type convertible to bool will also work
// analogously.
//
// There is no variant combining detail::DestroyWhen::InConstructorOnly with
// detail::DestroyWhen::InDestructorOnly because this would be equivalent to a
// const member.
//
// For special cases, e.g. requiring custom value check policies,
// detail::InitializedOnce might be instantiated directly, but be mindful when
// doing this.

template <typename T>
using InitializedOnce =
    detail::InitializedOnce<T, detail::InitWhen::InConstructorOnly,
                            detail::DestroyWhen::EarlyAllowed>;

template <typename T>
using InitializedOnceNotNull =
    detail::InitializedOnce<T, detail::InitWhen::InConstructorOnly,
                            detail::DestroyWhen::EarlyAllowed,
                            detail::ValueCheckPolicies::ConvertsToTrue>;

template <typename T>
using LazyInitializedOnce =
    detail::InitializedOnce<T, detail::InitWhen::LazyAllowed,
                            detail::DestroyWhen::InDestructorOnly>;

template <typename T>
using LazyInitializedOnceNotNull =
    detail::InitializedOnce<T, detail::InitWhen::LazyAllowed,
                            detail::DestroyWhen::InDestructorOnly,
                            detail::ValueCheckPolicies::ConvertsToTrue>;

template <typename T>
using LazyInitializedOnceEarlyDestructible =
    detail::InitializedOnce<T, detail::InitWhen::LazyAllowed,
                            detail::DestroyWhen::EarlyAllowed>;

template <typename T>
using LazyInitializedOnceNotNullEarlyDestructible =
    detail::InitializedOnce<T, detail::InitWhen::LazyAllowed,
                            detail::DestroyWhen::EarlyAllowed,
                            detail::ValueCheckPolicies::ConvertsToTrue>;

template <typename T, detail::InitWhen InitWhenVal,
          detail::DestroyWhen DestroyWhenVal,
          template <typename> class ValueCheckPolicy>
auto do_Init(detail::InitializedOnce<T, InitWhenVal, DestroyWhenVal,
                                     ValueCheckPolicy>& aLazyInitialized) {
  return detail::LazyInitializer(aLazyInitialized);
}

}  // namespace mozilla

#endif