Reference documentation for deal.II version GIT relicensing-1062-gc06da148b8 2024-07-15 19:20:02+00:00
\(\newcommand{\dealvcentcolon}{\mathrel{\mathop{:}}}\) \(\newcommand{\dealcoloneq}{\dealvcentcolon\mathrel{\mkern-1.2mu}=}\) \(\newcommand{\jump}[1]{\left[\!\left[ #1 \right]\!\right]}\) \(\newcommand{\average}[1]{\left\{\!\left\{ #1 \right\}\!\right\}}\)
Loading...
Searching...
No Matches
lazy.h
Go to the documentation of this file.
1// ------------------------------------------------------------------------
2//
3// SPDX-License-Identifier: LGPL-2.1-or-later
4// Copyright (C) 2023 - 2024 by the deal.II authors
5//
6// This file is part of the deal.II library.
7//
8// Part of the source code is dual licensed under Apache-2.0 WITH
9// LLVM-exception OR LGPL-2.1-or-later. Detailed license information
10// governing the source code and code contributions can be found in
11// LICENSE.md and CONTRIBUTING.md at the top level directory of deal.II.
12//
13// ------------------------------------------------------------------------
14
15#ifndef dealii_lazy_h
16#define dealii_lazy_h
17
18
19#include <deal.II/base/config.h>
20
23#include <deal.II/base/mutex.h>
24
25#include <atomic>
26#include <mutex>
27#include <optional>
28#include <type_traits>
29
30
32
126template <typename T>
127DEAL_II_CXX20_REQUIRES((std::is_move_constructible_v<T> &&
128 std::is_move_assignable_v<T>))
129class Lazy
130{
131public:
136
137
143 Lazy(const Lazy &other);
144
145
152 Lazy(Lazy &&other) noexcept;
153
154
162 Lazy &
163 operator=(const Lazy &other);
164
165
174 Lazy &
175 operator=(Lazy &&other) noexcept;
176
177
181 void
182 reset() noexcept;
183
184
203 template <typename Callable>
204 void
205 ensure_initialized(const Callable &creator) const
206 DEAL_II_CXX20_REQUIRES((std::is_invocable_r_v<T, Callable>));
207
208
213 bool
214 has_value() const;
215
216
223 const T &
224 value() const;
225
226
233 T &
234 value();
235
236
254 template <typename Callable>
255 const T &
256 value_or_initialize(const Callable &creator) const
257 DEAL_II_CXX20_REQUIRES((std::is_invocable_r_v<T, Callable>));
258
259
265 template <typename Callable>
266 DEAL_II_ALWAYS_INLINE inline T &
267 value_or_initialize(const Callable &creator)
268 DEAL_II_CXX20_REQUIRES((std::is_invocable_r_v<T, Callable>));
269
270
274 std::size_t
275 memory_consumption() const;
276
277private:
281 mutable std::optional<T> object;
282
283
288 mutable std::atomic<bool> object_is_initialized;
289
290
294 mutable Threads::Mutex initialization_mutex;
295};
296
302// ------------------------------- inline functions --------------------------
303
304
305template <typename T>
306DEAL_II_CXX20_REQUIRES((std::is_move_constructible_v<T> &&
307 std::is_move_assignable_v<T>))
308inline Lazy<T>::Lazy()
309 : object_is_initialized(false)
310{}
311
312
313template <typename T>
314DEAL_II_CXX20_REQUIRES((std::is_move_constructible_v<T> &&
315 std::is_move_assignable_v<T>))
316inline Lazy<T>::Lazy(const Lazy &other)
317 : object(other.object)
318{
319 object_is_initialized.store(other.object_is_initialized.load());
320}
321
322
323template <typename T>
324DEAL_II_CXX20_REQUIRES((std::is_move_constructible_v<T> &&
325 std::is_move_assignable_v<T>))
326inline Lazy<T>::Lazy(Lazy &&other) noexcept
327 : object(std::move(other.object))
328{
329 object_is_initialized.store(other.object_is_initialized.load());
330
331 // Mark the other object as uninitialized. This is marginally non-trivial
332 // because moving from std::optional<T> does *not* result in an empty
333 // std::optional<T> but instead one that does contain a T, but one that
334 // has been moved from -- typically something akin to a default-initialized
335 // T. That seems undesirable, so reset everything to an empty state.
336 other.object_is_initialized.store(false);
337 other.object.reset();
338}
339
340
341template <typename T>
342DEAL_II_CXX20_REQUIRES((std::is_move_constructible_v<T> &&
343 std::is_move_assignable_v<T>))
344inline Lazy<T> &Lazy<T>::operator=(const Lazy &other)
345{
346 object = other.object;
347 object_is_initialized.store(other.object_is_initialized.load());
348 return *this;
349}
350
351
352template <typename T>
353DEAL_II_CXX20_REQUIRES((std::is_move_constructible_v<T> &&
354 std::is_move_assignable_v<T>))
355inline Lazy<T> &Lazy<T>::operator=(Lazy &&other) noexcept
356{
357 object = std::move(other.object);
358 object_is_initialized.store(other.object_is_initialized.load());
359
360 // Mark the other object as uninitialized. This is marginally non-trivial
361 // because moving from std::optional<T> does *not* result in an empty
362 // std::optional<T> but instead one that does contain a T, but one that
363 // has been moved from -- typically something akin to a default-initialized
364 // T. That seems undesirable, so reset everything to an empty state.
365 other.object_is_initialized.store(false);
366 other.object.reset();
367
368 return *this;
369}
370
371
372template <typename T>
373DEAL_II_CXX20_REQUIRES((std::is_move_constructible_v<T> &&
374 std::is_move_assignable_v<T>))
375inline void Lazy<T>::reset() noexcept
376{
377 object_is_initialized.store(false);
378 object.reset();
379}
380
381
382template <typename T>
383DEAL_II_CXX20_REQUIRES((std::is_move_constructible_v<T> &&
384 std::is_move_assignable_v<T>))
385template <typename Callable>
387 void Lazy<T>::ensure_initialized(const Callable &creator) const
388 DEAL_II_CXX20_REQUIRES((std::is_invocable_r_v<T, Callable>))
389{
390 //
391 // Use Schmidt's double checking [1] for checking and initializing the
392 // object.
393 //
394 // [1] https://en.wikipedia.org/wiki/Double-checked_locking
395 //
396
397 //
398 // Check the object_is_initialized atomic with "acquire" semantics [1].
399 //
400 // This ensures that (a) all subsequent reads (of the object) are
401 // ordered after this check, and that (b) all writes to the object
402 // before the atomic bool was set to true with "release" semantics are
403 // visible on this thread.
404 //
405 // [1]
406 // https://en.cppreference.com/w/cpp/atomic/memory_order#Release-Acquire_ordering
407 //
408 if (!object_is_initialized.load(std::memory_order_acquire))
409#ifdef DEAL_II_HAVE_CXX20
410 [[unlikely]]
411#endif
412 {
413 std::lock_guard<std::mutex> lock(initialization_mutex);
414
415 //
416 // Check again. If this thread won the race to the lock then we
417 // initialize the object. Otherwise another thread has already
418 // initialized the object and flipped the object_is_initialized
419 // bit. (Here, the initialization_mutex ensures consistent ordering
420 // with a memory fence, so we will observe the updated bool without
421 // acquire semantics.)
422 //
423 if (!object_is_initialized.load(std::memory_order_relaxed))
424 {
425 Assert(object.has_value() == false, ExcInternalError());
426 object.emplace(std::move(creator()));
427
428 //
429 // Flip the object_is_initialized boolean with "release"
430 // semantics [1].
431 //
432 // This ensures that the above move is visible on all threads
433 // before checking the atomic bool with acquire semantics.
434 //
435 object_is_initialized.store(true, std::memory_order_release);
436 }
437 }
438
439 Assert(
440 object.has_value(),
442 "The internal std::optional<T> object does not contain a valid object "
443 "even though we have just initialized it."));
444}
445
446
447template <typename T>
448DEAL_II_CXX20_REQUIRES((std::is_move_constructible_v<T> &&
449 std::is_move_assignable_v<T>))
450inline DEAL_II_ALWAYS_INLINE bool Lazy<T>::has_value() const
451{
452 //
453 // In principle it would be sufficient to solely check the atomic<bool>
454 // object_is_initialized because the load() is performed with "acquire"
455 // semantics. But just in case let's check the object.has_value() boolean
456 // as well:
457 //
458 return (object_is_initialized && object.has_value());
459}
460
461
462template <typename T>
463DEAL_II_CXX20_REQUIRES((std::is_move_constructible_v<T> &&
464 std::is_move_assignable_v<T>))
465inline DEAL_II_ALWAYS_INLINE const T &Lazy<T>::value() const
466{
467 Assert(
468 object_is_initialized && object.has_value(),
470 "value() has been called but the contained object has not been "
471 "initialized. Did you forget to call 'ensure_initialized()' first?"));
472
473 return object.value();
474}
475
476
477template <typename T>
478DEAL_II_CXX20_REQUIRES((std::is_move_constructible_v<T> &&
479 std::is_move_assignable_v<T>))
480inline DEAL_II_ALWAYS_INLINE T &Lazy<T>::value()
481{
482 Assert(
483 object_is_initialized && object.has_value(),
485 "value() has been called but the contained object has not been "
486 "initialized. Did you forget to call 'ensure_initialized()' first?"));
487
488 return object.value();
489}
490
491
492template <typename T>
493DEAL_II_CXX20_REQUIRES((std::is_move_constructible_v<T> &&
494 std::is_move_assignable_v<T>))
495template <typename Callable>
497 const Callable &creator) const
498 DEAL_II_CXX20_REQUIRES((std::is_invocable_r_v<T, Callable>))
499{
500 ensure_initialized(creator);
501 return object.value();
502}
503
504
505template <typename T>
506DEAL_II_CXX20_REQUIRES((std::is_move_constructible_v<T> &&
507 std::is_move_assignable_v<T>))
508template <typename Callable>
510 const Callable &creator)
511 DEAL_II_CXX20_REQUIRES((std::is_invocable_r_v<T, Callable>))
512{
513 ensure_initialized(creator);
514 return object.value();
515}
516
517
518template <typename T>
519DEAL_II_CXX20_REQUIRES((std::is_move_constructible_v<T> &&
520 std::is_move_assignable_v<T>))
521std::size_t Lazy<T>::memory_consumption() const
522{
523 return MemoryConsumption::memory_consumption(object) + //
524 sizeof(*this) - sizeof(object);
525}
526
527
529#endif
Definition lazy.h:130
const T & value_or_initialize(const Callable &creator) const
Definition lazy.h:496
void ensure_initialized(const Callable &creator) const
Definition lazy.h:387
Lazy()
Definition lazy.h:308
Lazy & operator=(Lazy &&other) noexcept
Definition lazy.h:355
Lazy & operator=(const Lazy &other)
Definition lazy.h:344
void reset() noexcept
Definition lazy.h:375
std::atomic< bool > object_is_initialized
Definition lazy.h:288
Lazy(const Lazy &other)
Definition lazy.h:316
Lazy(Lazy &&other) noexcept
Definition lazy.h:326
std::optional< T > object
Definition lazy.h:281
#define DEAL_II_ALWAYS_INLINE
Definition config.h:109
#define DEAL_II_NAMESPACE_OPEN
Definition config.h:502
#define DEAL_II_CXX20_REQUIRES(condition)
Definition config.h:177
#define DEAL_II_NAMESPACE_CLOSE
Definition config.h:503
#define Assert(cond, exc)
static ::ExceptionBase & ExcInternalError()
static ::ExceptionBase & ExcMessage(std::string arg1)
std::enable_if_t< std::is_fundamental_v< T >, std::size_t > memory_consumption(const T &t)
STL namespace.