liveBook · Manning

Lazy initialization

As explained in C++ Concurrency in Action, Second Edition

Lazy initialization is a technique that delays constructing or initializing a resource until it is first required.

Lazy initialization delays creating an expensive resource—such as a database connection or a large allocation—until it is needed. In multithreaded code, protect initialization without needlessly making every caller wait just to check whether the resource exists. section 3.3.1

Common C++ approaches illustrate the trade-offs:

  • A mutex can protect the check and initialization, but requiring every caller to acquire it just to check can serialize access. section 3.3.1
  • Avoid naïve double-checked locking: its unsynchronized pointer read can race with initialization, leaving both the pointer and the object in an invalid state and causing undefined behavior. section 3.3.1
  • Use std::call_once with a std::once_flag to initialize a shared resource or class connection before it is used. section 3.3.1
Listing 3.11. Thread-safe lazy initialization using a mutex
std::shared_ptr<some_resource> resource_ptr;
std::mutex resource_mutex;
void foo()
{
    std::unique_lock<std::mutex> lk(resource_mutex);   #1
    if(!resource_ptr)
    {
        resource_ptr.reset(new some_resource);         #2
    }
    lk.unlock();
    resource_ptr->do_something();
}

See a mutex-protected initialization example and where the lock is released. section 3.3.1

void undefined_behaviour_with_double_checked_locking()
{
    if(!resource_ptr)                                  #1
    {
        std::lock_guard<std::mutex> lk(resource_mutex);
        if(!resource_ptr)                              #2
        {
            resource_ptr.reset(new some_resource);     #3
        }
    }
    resource_ptr->do_something();                      #4
}


!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":2,\"ch\":55},{\"line\":2,\"ch\":57}],[{\"line\":5,\"ch\":55},{\"line\":5,\"ch\":57}],[{\"line\":7,\"ch\":55},{\"line\":7,\"ch\":57}],[{\"line\":10,\"ch\":55},{\"line\":10,\"ch\":57}]]"}
!@%STYLE%@!

Inspect the double-checked locking pattern and its unsynchronized reads. section 3.3.1

Listing 3.12. Thread-safe lazy initialization of a class member using std::call_once
class X
{
private:
    connection_info connection_details;
    connection_handle connection;
    std::once_flag connection_init_flag;
    void open_connection()
    {
        connection=connection_manager.open(connection_details);
    }
public:
    X(connection_info const& connection_details_):
        connection_details(connection_details_)
    {}
    void send_data(data_packet const& data)                             #1
    {
        std::call_once(connection_init_flag,&X::open_connection,this);  #2
        connection.send_data(data);
    }
    data_packet receive_data()                                          #3
    {
        std::call_once(connection_init_flag,&X::open_connection,this);  #2
        return connection.receive_data();
    }
};


!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":14,\"ch\":72},{\"line\":14,\"ch\":74}],[{\"line\":16,\"ch\":72},{\"line\":16,\"ch\":74}],[{\"line\":21,\"ch\":72},{\"line\":21,\"ch\":74}],[{\"line\":19,\"ch\":72},{\"line\":19,\"ch\":74}],[{\"line\":16,\"ch\":72},{\"line\":16,\"ch\":74}],[{\"line\":21,\"ch\":72},{\"line\":21,\"ch\":74}]]"}
!@%STYLE%@!

See std::call_once used to initialize a class connection before sending or receiving data. section 3.3.1

How C++ Concurrency in Action, Second Edition covers it

How deep it goes
Compares mutex protection, double-checked locking, and std::call_once, while deferring full memory-model treatment.
Examples
Uses an expensive resource, a shared_ptr to some_resource, and a class connection as examples.
What you can do after
Apply std::call_once with std::once_flag to initialize a shared resource or class connection before use.
How it connects
Connects lazy initialization with mutexes, double-checked locking, data races, and std::call_once.

Also explained in

Compare how 3 books explain lazy initialization →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of C++ Concurrency in Action, Second Edition. The section links point to the parts of the book it draws on.