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


