deal.II version GIT relicensing-2291-g668cd86249 2024-12-24 11:30:00+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
Public Member Functions | Private Attributes | List of all members

#include <deal.II/base/lazy.h>

Inheritance diagram for Lazy< T >:
Inheritance graph
[legend]

Public Member Functions

 Lazy ()
 
 Lazy (const Lazy &other)
 
 Lazy (Lazy &&other) noexcept
 
Lazyoperator= (const Lazy &other)
 
Lazyoperator= (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< boolobject_is_initialized
 

Detailed Description

template<typename T>
class Lazy< T >

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:

template<...>
class FE
{
public:
const FullMatrix<double> & get_prolongation_matrix() const
{
prolongation_matrix.ensure_initialized([&](){
// Some expensive operation initializing the prolongation matrix
// that we only want to perform once and when necessary.
});
return prolongation_matrix.value();
}
private:
Lazy<FullMatrix<double>> prolongation_matrix;
};
Definition lazy.h:135
Note
Conceptually, this class is not so different from std::future, which can also be used to represent a possibly-not-yet-available value on which one can wait when used with the "deferred" policy of std::async. In particular, the following code could be used in place of the one above:
template<...>
class FE
{
public:
FE () {
prolongation_matrix = std::async(std::launch::deferred,
[&](){
// Some expensive operation initializing the prolongation matrix
// that we only want to perform once and when necessary.
});
}
FullMatrix<double> get_prolongation_matrix() const
{
return prolongation_matrix.get();
}
private:
std::future<FullMatrix<double>> prolongation_matrix;
};
The difference to what Lazy does is that for Lazy, the action must be specified in the place where we want to access the deferred computation's result. In contrast, in the scheme with 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.)
This class, function, or variable is a template, and it can only be instantiated if the following condition is true:
std::is_move_constructible_v<T> &&
std::is_move_assignable_v<T >
If your compiler supports the C++20 standard, then this constraint will be enforced by a C++20 requires clause.

Definition at line 134 of file lazy.h.

Constructor & Destructor Documentation

◆ Lazy() [1/3]

template<typename T >
Lazy< T >::Lazy ( )

Default Constructor.

◆ Lazy() [2/3]

template<typename T >
Lazy< T >::Lazy ( const Lazy< T > &  other)

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.

◆ Lazy() [3/3]

template<typename T >
Lazy< T >::Lazy ( Lazy< T > &&  other)
noexcept

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.

Member Function Documentation

◆ operator=() [1/2]

template<typename T >
Lazy & Lazy< T >::operator= ( const Lazy< T > &  other)

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.

◆ operator=() [2/2]

template<typename T >
Lazy & Lazy< T >::operator= ( Lazy< T > &&  other)
noexcept

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.

◆ reset()

template<typename T >
void Lazy< T >::reset ( )
noexcept

Reset the Lazy<T> object to an uninitialized state.

◆ ensure_initialized()

template<typename T >
template<typename Callable >
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.

Note
This class, function, or variable is a template, and it can only be instantiated if the following condition is true:
std::is_invocable_r_v<T, Callable>
If your compiler supports the C++20 standard, then this constraint will be enforced by a C++20 requires clause.

◆ has_value()

template<typename T >
bool Lazy< T >::has_value ( ) const

Returns true if the contained object has been initialized, otherwise false.

◆ value()

template<typename T >
const T & Lazy< T >::value ( ) const

Return a const reference to the contained object.

Precondition
The object has been initialized with a call to ensure_initialized() or value_or_initialized().

◆ value_or_initialize()

template<typename T >
template<typename Callable >
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.

Postcondition
The underlying object is initialized, meaning, has_value() returns true.
Note
This class, function, or variable is a template, and it can only be instantiated if the following condition is true:
std::is_invocable_r_v<T, Callable>
If your compiler supports the C++20 standard, then this constraint will be enforced by a C++20 requires clause.

◆ memory_consumption()

template<typename T >
std::size_t Lazy< T >::memory_consumption ( ) const

Compute the memory consumption of this structure.

Member Data Documentation

◆ task_result

template<typename T >
Threads::TaskResult<T> Lazy< T >::task_result
mutableprivate

A handle to the task used to create the lazily initialized object.

Definition at line 264 of file lazy.h.

◆ object_is_initialized

template<typename T >
std::atomic<bool> Lazy< T >::object_is_initialized
mutableprivate

An atomic bool used for checking whether the object is initialized in a thread-safe manner.

Definition at line 271 of file lazy.h.


The documentation for this class was generated from the following file: