Why is this pointer needed when calling std::call_once()?
Asked Answered
U

2

9

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 for std::call_once) provides the object on which to apply the member function (either directly, or via a pointer, or wrapped in std::ref), and the remaining arguments are passed as arguments to the member function. Otherwise, the second (should be the third one for std::call_once) and subsequent arguments are passed as arguments to the function or callable object specified as the first argument.

Uniaxial answered 21/4, 2014 at 12:20 Comment(0)
K
18

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);
Kunz answered 21/4, 2014 at 12:21 Comment(5)
So it's equivalent to call this->open_connection(), right?Uniaxial
@Uniaxial It will result in this->open_connection() being called once.Kunz
Shouldn't the third parameter be the input parameter for the function indicated by the second parameter of call_once(), like examples from here?Uniaxial
@Uniaxial Yes and no. The examples have non-member functions. Member functions have an implicit first parameter for this. That is how the object's state can be made accessible in them. But call_once (and std::thread, std::bind and others) are not member functions, so they need this first parameter to be passed explicitly. That is the only way they can bind a member function to an instance.Kunz
Clear now. Thanks for your detailed explanation.Uniaxial
T
8

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(); } );
Taro answered 21/4, 2014 at 19:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.