Reference documentation for deal.II version 9.6.0
\(\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
130template <typename T>
131DEAL_II_CXX20_REQUIRES((std::is_move_constructible_v<T> &&
132 std::is_move_assignable_v<T>))
133class Lazy
134{
135public:
140
141
147 Lazy(const Lazy &other);
148
149
156 Lazy(Lazy &&other) noexcept;
157
158
166 Lazy &
167 operator=(const Lazy &other);
168
169
178 Lazy &
179 operator=(Lazy &&other) noexcept;
180
181
185 void
186 reset() noexcept;
187
188
207 template <typename Callable>
208 void
209 ensure_initialized(const Callable &creator) const
210 DEAL_II_CXX20_REQUIRES((std::is_invocable_r_v<T, Callable>));
211
212
217 bool
218 has_value() const;
219
220
227 const T &
228 value() const;
229
230
237 T &
238 value();
239
240
258 template <typename Callable>
259 const T &
260 value_or_initialize(const Callable &creator) const
261 DEAL_II_CXX20_REQUIRES((std::is_invocable_r_v<T, Callable>));
262
263
269 template <typename Callable>
270 DEAL_II_ALWAYS_INLINE inline T &
271 value_or_initialize(const Callable &creator)
272 DEAL_II_CXX20_REQUIRES((std::is_invocable_r_v<T, Callable>));
273
274
278 std::size_t
279 memory_consumption() const;
280
281private:
285 mutable std::optional<T> object;
286
287
292 mutable std::atomic<bool> object_is_initialized;
293
294
298 mutable Threads::Mutex initialization_mutex;
299};
300
306// ------------------------------- inline functions --------------------------
307
308
309template <typename T>
310DEAL_II_CXX20_REQUIRES((std::is_move_constructible_v<T> &&
311 std::is_move_assignable_v<T>))
312inline Lazy<T>::Lazy()
313 : object_is_initialized(false)
314{}
315
316
317template <typename T>
318DEAL_II_CXX20_REQUIRES((std::is_move_constructible_v<T> &&
319 std::is_move_assignable_v<T>))
320inline Lazy<T>::Lazy(const Lazy &other)
321 : object(other.object)
322{
323 object_is_initialized.store(other.object_is_initialized.load());
324}
325
326
327template <typename T>
328DEAL_II_CXX20_REQUIRES((std::is_move_constructible_v<T> &&
329 std::is_move_assignable_v<T>))
330inline Lazy<T>::Lazy(Lazy &&other) noexcept
331 : object(std::move(other.object))
332{
333 object_is_initialized.store(other.object_is_initialized.load());
334
335 // Mark the other object as uninitialized. This is marginally non-trivial
336 // because moving from std::optional<T> does *not* result in an empty
337 // std::optional<T> but instead one that does contain a T, but one that
338 // has been moved from -- typically something akin to a default-initialized
339 // T. That seems undesirable, so reset everything to an empty state.
340 other.object_is_initialized.store(false);
341 other.object.reset();
342}
343
344
345template <typename T>
346DEAL_II_CXX20_REQUIRES((std::is_move_constructible_v<T> &&
347 std::is_move_assignable_v<T>))
348inline Lazy<T> &Lazy<T>::operator=(const Lazy &other)
349{
350 object = other.object;
351 object_is_initialized.store(other.object_is_initialized.load());
352 return *this;
353}
354
355
356template <typename T>
357DEAL_II_CXX20_REQUIRES((std::is_move_constructible_v<T> &&
358 std::is_move_assignable_v<T>))
359inline Lazy<T> &Lazy<T>::operator=(Lazy &&other) noexcept
360{
361 object = std::move(other.object);
362 object_is_initialized.store(other.object_is_initialized.load());
363
364 // Mark the other object as uninitialized. This is marginally non-trivial
365 // because moving from std::optional<T> does *not* result in an empty
366 // std::optional<T> but instead one that does contain a T, but one that
367 // has been moved from -- typically something akin to a default-initialized
368 // T. That seems undesirable, so reset everything to an empty state.
369 other.object_is_initialized.store(false);
370 other.object.reset();
371
372 return *this;
373}
374
375
376template <typename T>
377DEAL_II_CXX20_REQUIRES((std::is_move_constructible_v<T> &&
378 std::is_move_assignable_v<T>))
379inline void Lazy<T>::reset() noexcept
380{
381 object_is_initialized.store(false);
382 object.reset();
383}
384
385
386template <typename T>
387DEAL_II_CXX20_REQUIRES((std::is_move_constructible_v<T> &&
388 std::is_move_assignable_v<T>))
389template <typename Callable>
391 void Lazy<T>::ensure_initialized(const Callable &creator) const
392 DEAL_II_CXX20_REQUIRES((std::is_invocable_r_v<T, Callable>))
393{
394 //
395 // Use Schmidt's double checking [1] for checking and initializing the
396 // object.
397 //
398 // [1] https://en.wikipedia.org/wiki/Double-checked_locking
399 //
400
401 //
402 // Check the object_is_initialized atomic with "acquire" semantics [1].
403 //
404 // This ensures that (a) all subsequent reads (of the object) are
405 // ordered after this check, and that (b) all writes to the object
406 // before the atomic bool was set to true with "release" semantics are
407 // visible on this thread.
408 //
409 // [1]
410 // https://en.cppreference.com/w/cpp/atomic/memory_order#Release-Acquire_ordering
411 //
412 if (!object_is_initialized.load(std::memory_order_acquire))
413#ifdef DEAL_II_HAVE_CXX20
414 [[unlikely]]
415#endif
416 {
417 std::lock_guard<std::mutex> lock(initialization_mutex);
418
419 //
420 // Check again. If this thread won the race to the lock then we
421 // initialize the object. Otherwise another thread has already
422 // initialized the object and flipped the object_is_initialized
423 // bit. (Here, the initialization_mutex ensures consistent ordering
424 // with a memory fence, so we will observe the updated bool without
425 // acquire semantics.)
426 //
427 if (!object_is_initialized.load(std::memory_order_relaxed))
428 {
429 Assert(object.has_value() == false, ExcInternalError());
430 object.emplace(std::move(creator()));
431
432 //
433 // Flip the object_is_initialized boolean with "release"
434 // semantics [1].
435 //
436 // This ensures that the above move is visible on all threads
437 // before checking the atomic bool with acquire semantics.
438 //
439 object_is_initialized.store(true, std::memory_order_release);
440 }
441 }
442
443 Assert(
444 object.has_value(),
446 "The internal std::optional<T> object does not contain a valid object "
447 "even though we have just initialized it."));
448}
449
450
451template <typename T>
452DEAL_II_CXX20_REQUIRES((std::is_move_constructible_v<T> &&
453 std::is_move_assignable_v<T>))
454inline DEAL_II_ALWAYS_INLINE bool Lazy<T>::has_value() const
455{
456 //
457 // In principle it would be sufficient to solely check the atomic<bool>
458 // object_is_initialized because the load() is performed with "acquire"
459 // semantics. But just in case let's check the object.has_value() boolean
460 // as well:
461 //
462 return (object_is_initialized && object.has_value());
463}
464
465
466template <typename T>
467DEAL_II_CXX20_REQUIRES((std::is_move_constructible_v<T> &&
468 std::is_move_assignable_v<T>))
469inline DEAL_II_ALWAYS_INLINE const T &Lazy<T>::value() const
470{
471 Assert(
472 object_is_initialized && object.has_value(),
474 "value() has been called but the contained object has not been "
475 "initialized. Did you forget to call 'ensure_initialized()' first?"));
476
477 return object.value();
478}
479
480
481template <typename T>
482DEAL_II_CXX20_REQUIRES((std::is_move_constructible_v<T> &&
483 std::is_move_assignable_v<T>))
484inline DEAL_II_ALWAYS_INLINE T &Lazy<T>::value()
485{
486 Assert(
487 object_is_initialized && object.has_value(),
489 "value() has been called but the contained object has not been "
490 "initialized. Did you forget to call 'ensure_initialized()' first?"));
491
492 return object.value();
493}
494
495
496template <typename T>
497DEAL_II_CXX20_REQUIRES((std::is_move_constructible_v<T> &&
498 std::is_move_assignable_v<T>))
499template <typename Callable>
501 const Callable &creator) const
502 DEAL_II_CXX20_REQUIRES((std::is_invocable_r_v<T, Callable>))
503{
504 ensure_initialized(creator);
505 return object.value();
506}
507
508
509template <typename T>
510DEAL_II_CXX20_REQUIRES((std::is_move_constructible_v<T> &&
511 std::is_move_assignable_v<T>))
512template <typename Callable>
514 const Callable &creator)
515 DEAL_II_CXX20_REQUIRES((std::is_invocable_r_v<T, Callable>))
516{
517 ensure_initialized(creator);
518 return object.value();
519}
520
521
522template <typename T>
523DEAL_II_CXX20_REQUIRES((std::is_move_constructible_v<T> &&
524 std::is_move_assignable_v<T>))
525std::size_t Lazy<T>::memory_consumption() const
526{
527 return MemoryConsumption::memory_consumption(object) + //
528 sizeof(*this) - sizeof(object);
529}
530
531
533#endif
Definition lazy.h:134
const T & value_or_initialize(const Callable &creator) const
Definition lazy.h:500
void ensure_initialized(const Callable &creator) const
Definition lazy.h:391
Lazy()
Definition lazy.h:312
Lazy & operator=(Lazy &&other) noexcept
Definition lazy.h:359
Lazy & operator=(const Lazy &other)
Definition lazy.h:348
void reset() noexcept
Definition lazy.h:379
std::atomic< bool > object_is_initialized
Definition lazy.h:292
Lazy(const Lazy &other)
Definition lazy.h:320
Lazy(Lazy &&other) noexcept
Definition lazy.h:330
std::optional< T > object
Definition lazy.h:285
#define DEAL_II_ALWAYS_INLINE
Definition config.h:109
#define DEAL_II_NAMESPACE_OPEN
Definition config.h:503
#define DEAL_II_CXX20_REQUIRES(condition)
Definition config.h:177
#define DEAL_II_NAMESPACE_CLOSE
Definition config.h:504
#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.