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:
{
prolongation_matrix.ensure_initialized([&](){
});
return prolongation_matrix.value();
}
private:
};
- 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,
[&](){
});
}
{
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 133 of file lazy.h.
template<typename T >
template<typename Callable >
void Lazy< T >::ensure_initialized |
( |
const Callable & | creator | ) |
const |
|
inline |
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.
Definition at line 391 of file lazy.h.