deal.II version GIT relicensing-2291-g668cd86249 2024-12-24 11:30:00+00:00
|
#include <deal.II/base/lazy.h>
Public Member Functions | |
Lazy () | |
Lazy (const Lazy &other) | |
Lazy (Lazy &&other) noexcept | |
Lazy & | operator= (const Lazy &other) |
Lazy & | operator= (Lazy &&other) noexcept |
void | reset () noexcept |
template<typename Callable > | |
void | ensure_initialized (const Callable &creator) const |
bool | has_value () const |
const T & | value () const |
template<typename Callable > | |
const T & | value_or_initialize (const Callable &creator) const |
std::size_t | memory_consumption () const |
Private Attributes | |
Threads::TaskResult< T > | task_result |
std::atomic< bool > | object_is_initialized |
This class is a wrapper that provides a convenient mechanism for lazy initialization of the contained object on first use. The class ensures that on-demand initialization of some expensive data structure happens (a) exactly once in a thread-safe manner, and that (b) subsequent checks in hot paths are cheap.
Lazy<T> is closely modeled after the std::optional
interface providing a reset()
and value()
method, but also and extending it with two methods: ensure_initialized(creator)
which, as the name suggests, ensures that the contained object is properly initialized. If the Lazy<T>
happens happens to contain no value yet, it initializes the wrapped object by calling the creator()
function object and storing the return value. In addition a value_or_initialize(creator)
function is provided that, similarly, ensures that the object is properly initialized and then returns a reference to the contained value.
Example usage could look like the following, where the FE
class stores a matrix that is expensive to compute and so we do not want to do it unless it is actually needed. As a consequence, rather than storing a matrix, we store a Lazy<FullMatrix<double>>
that by default is empty; whenever the matrix is first requested, we create it and store it for later reuse:
std::future
and std::async
, the action has to be provided at the point where the std::future
object is initialized. Both are valid approaches and, depending on context, can usefully be employed. The difference is simply in what kind of information the provided lambda function can capture: Is it the environment available at the time the constructor is run, or the environment available at the time the access function is run. The latter has the advantage that the information captured is always up to date, whereas in the scheme with std::async
, one has to be careful not to capture information in the lambda function that could be changed by later calls to member functions but before the lambda function is finally evaluated in the getter function. (There is another difference: std::future::get()
can only be called once, as the function returns the computed object by value and may move the object out of its internal storage. As a consequence, the call to FE::get_prolongation_matrix()
is only valid the first time around. Lazy does not have this restriction.)Copy constructor. If the other
object contains an initialized value, then that value will be copied into the current object. If the other
object is uninitialized, then the current object will be as well.
Move constructor. If the other
object contains an initialized value, then that value will be moved into the current object, and the other
object will end up being empty (as if default initialized). If the other
object is uninitialized, then the current object will be as well.
Copy assignment. If the other
object contains an initialized value, then that value will be copied into the current object. If the other
object is uninitialized, then the current object will be as well.
Any content of the current object is lost in the assignment.
Move assignment. If the other
object contains an initialized value, then that value will be moved into the current object, and the other
object will end up being empty (as if default initialized). If the other
object is uninitialized, then the current object will be as well.
Any content of the current object is lost in the move assignment.
|
noexcept |
Reset the Lazy<T> object to an uninitialized state.
void Lazy< T >::ensure_initialized | ( | const Callable & | creator | ) | const |
Initialize the wrapped object.
If the contained object is already initialized this function simply returns and does nothing.
If, instead, the object has not yet been initialized then the creator
function object (oftentimes a lambda function) is called to initialize the contained object.
This operation is thread safe: The ensure_initialized() method guarantees that the creator function object is only called once on one of the calling threads and that after completion the initialization result (which is stored in the std::optional) is visible on all threads.
Returns true if the contained object has been initialized, otherwise false.
const T & Lazy< T >::value | ( | ) | const |
Return a const reference to the contained object.
const T & Lazy< T >::value_or_initialize | ( | const Callable & | creator | ) | const |
If the underlying object is initialized the function simply returns a const reference to the contained value. Otherwise, the creator()
function object is called to initialize the object first.
This function mimics the syntax of the std::optional<T> interface and is functionally equivalent to calling ensure_initialized() followed by value(). It returns a const
reference to make clear that the object created by the creator
function is what it is, and is not subject to later modification unless one calls reset() and creates a new object.
std::size_t Lazy< T >::memory_consumption | ( | ) | const |
Compute the memory consumption of this structure.
|
mutableprivate |