In book "C++ Concurrency in Action" §3.3.1, when introducing thread-safe lazy initialization of a class member using std::call_once()
, it gives the following example:
#include <mutex>
struct connection_info
{};
struct data_packet
{};
struct connection_handle
{
void send_data(data_packet const&)
{}
data_packet receive_data()
{
return data_packet();
}
};
struct remote_connection_manager
{
connection_handle open(connection_info const&)
{
return connection_handle();
}
} connection_manager;
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)
{
std::call_once(connection_init_flag,&X::open_connection,this);
connection.send_data(data);
}
data_packet receive_data()
{
std::call_once(connection_init_flag,&X::open_connection,this);
return connection.receive_data();
}
};
int main()
{}
From its doc, the third parameter is the parameter passing to the function X::open_connection()
. Why is this
pointer needed here when calling std::call_once()
given that X::open_connection()
has no input parameter?
std::call_once(connection_init_flag,&X::open_connection,this);
P.S.: Removing this
pointer will cause C2064 error:
error C2064: term does not evaluate to a function taking 0 arguments
Updated: This issue is further addressed clearly in §4.2.1 of book "C++ Concurrency in Action" when introducing similar functions i.e. std::async
:
If the first argument (should be the second one for
std::call_once
) is a pointer to a member function, the second argument (should be the third one forstd::call_once
) provides the object on which to apply the member function (either directly, or via a pointer, or wrapped instd::ref
), and the remaining arguments are passed as arguments to the member function. Otherwise, the second (should be the third one forstd::call_once
) and subsequent arguments are passed as arguments to the function or callable object specified as the first argument.
std::call_once ensures execution of a function exactly once by competing threads. It throws std::system_error in case it cannot complete its task.
The ISO standard does state that call_once is thread safe, after all!
The CPP Reference states std::call_once is thread safe: Executes the function f exactly once, even if called from several threads.
Why is this pointer needed when calling std::call_once()?
Because open_connection
is a non-static data member. It has to be called on something, and that something is the same instance, pointed at by this
(technically, non-static member functions have an implicit first parameter for this
.)
It could have been invoked with a different instance, although that wouldn't make sense in this case:
X x;
std::call_once(connection_init_flag, &X::open_connection, &x);
juanchopanza is correct, I would like to add that what you are actually doing might have be clearer if you replace the arguments or your code snippet by an strictly equivalent lambda:
std::call_once(connection_init_flag, [&]{ open_connection(); } );
// or
std::call_once(connection_init_flag, [this]{ open_connection(); } );
Which is also exactly equivalent to:
std::call_once(connection_init_flag, [this]{ this->open_connection(); } );
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With