ryujin 2.1.1 revision 1c453cc82f1d29edf537280cd96267402ac73e60
lazy.h
Go to the documentation of this file.
1//
2// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
3// Copyright (C) 2023 - 2024 by Matthias Maier
4// Copyright (C) 2024 - 2024 by the ryujin authors
5//
6
7#pragma once
8
9#include <deal.II/base/config.h>
10
11#include <deal.II/base/exceptions.h>
12#include <deal.II/base/memory_consumption.h>
13
14#if DEAL_II_VERSION_GTE(9, 5, 0)
15#include <deal.II/base/mutex.h>
16#else
17#include <deal.II/base/thread_management.h>
18#endif
19
20#include <atomic>
21#include <mutex>
22#include <optional>
23
24
25namespace ryujin
26{
31 template <typename T>
32 class Lazy
33 {
34 public:
36 Lazy(const Lazy &other);
37 Lazy(Lazy &&other) noexcept;
38
39 Lazy &operator=(const Lazy &other);
40 Lazy &operator=(Lazy &&other) noexcept;
41
42 void reset() noexcept;
43
44 template <typename Callable>
45 void ensure_initialized(const Callable &creator) const;
46
47 bool has_value() const;
48
49 const T &value() const;
50 T &value();
51
52 private:
53 mutable std::optional<T> object;
54 mutable std::atomic<bool> object_is_initialized;
55 mutable dealii::Threads::Mutex initialization_mutex;
56 };
57
58
59 // ------------------------------- inline functions --------------------------
60
61
62 template <typename T>
63 inline Lazy<T>::Lazy()
64 : object_is_initialized(false)
65 {
66 }
67
68
69 template <typename T>
70 inline Lazy<T>::Lazy(const Lazy &other)
71 : object(other.object)
72 {
73 object_is_initialized.store(other.object_is_initialized.load());
74 }
75
76
77 template <typename T>
78 inline Lazy<T>::Lazy(Lazy &&other) noexcept
79 : object(std::move(other.object))
80 {
81 object_is_initialized.store(other.object_is_initialized.load());
82
83 other.object_is_initialized.store(false);
84 other.object.reset();
85 }
86
87
88 template <typename T>
89 inline Lazy<T> &Lazy<T>::operator=(const Lazy &other)
90 {
91 object = other.object;
92 object_is_initialized.store(other.object_is_initialized.load());
93
94 return *this;
95 }
96
97
98 template <typename T>
99 inline Lazy<T> &Lazy<T>::operator=(Lazy &&other) noexcept
100 {
101 object = std::move(other.object);
102 object_is_initialized.store(other.object_is_initialized.load());
103
104 other.object_is_initialized.store(false);
105 other.object.reset();
106
107 return *this;
108 }
109
110
111 template <typename T>
112 inline void Lazy<T>::reset() noexcept
113 {
114 object_is_initialized.store(false);
115 object.reset();
116 }
117
118
119 template <typename T>
120 template <typename Callable>
121 inline DEAL_II_ALWAYS_INLINE void
122 Lazy<T>::ensure_initialized(const Callable &creator) const
123 {
124 // Check the object_is_initialized atomic with "acquire" semantics.
125 if (!object_is_initialized.load(std::memory_order_acquire))
126#ifdef DEAL_II_HAVE_CXX20
127 [[unlikely]]
128#endif
129 {
130 std::lock_guard<std::mutex> lock(initialization_mutex);
131
132 //
133 // Check again. If this thread won the race to the lock then we
134 // initialize the object. Otherwise another thread has already
135 // initialized the object and flipped the object_is_initialized
136 // bit. (Here, the initialization_mutex ensures consistent ordering
137 // with a memory fence, so we will observe the updated bool without
138 // acquire semantics.)
139 //
140 if (!object_is_initialized.load(std::memory_order_relaxed)) {
141 Assert(object.has_value() == false, dealii::ExcInternalError());
142 object.emplace(std::move(creator()));
143
144 // Flip the object_is_initialized boolean with "release" semantics.
145 object_is_initialized.store(true, std::memory_order_release);
146 }
147 }
148 }
149
150
151 template <typename T>
152 inline DEAL_II_ALWAYS_INLINE bool Lazy<T>::has_value() const
153 {
154 return (object_is_initialized && object.has_value());
155 }
156
157
158 template <typename T>
159 inline DEAL_II_ALWAYS_INLINE const T &Lazy<T>::value() const
160 {
161 Assert(object_is_initialized && object.has_value(),
162 dealii::ExcMessage(
163 "value() has been called but the contained object has not been "
164 "initialized. Did you forget to call 'ensure_initialized()' "
165 "first?"));
166
167 return object.value();
168 }
169
170
171 template <typename T>
172 inline DEAL_II_ALWAYS_INLINE T &Lazy<T>::value()
173 {
174 Assert(object_is_initialized && object.has_value(),
175 dealii::ExcMessage(
176 "value() has been called but the contained object has not been "
177 "initialized. Did you forget to call 'ensure_initialized()' "
178 "first?"));
179
180 return object.value();
181 }
182
183} // namespace ryujin
Lazy & operator=(const Lazy &other)
Definition: lazy.h:89
Lazy & operator=(Lazy &&other) noexcept
Definition: lazy.h:99
void reset() noexcept
Definition: lazy.h:112
Lazy()
Definition: lazy.h:63
Lazy(const Lazy &other)
Definition: lazy.h:70
const T & value() const
Definition: lazy.h:159
Lazy(Lazy &&other) noexcept
Definition: lazy.h:78
bool has_value() const
Definition: lazy.h:152
void ensure_initialized(const Callable &creator) const