Sad egg
Sad egg

Reputation: 99

In C++, How a std::thread can call a member function without creating an object?

If we have a class H with some operator() overloaded. How it is possible to create a thread from these member functions without instantiating an object from class H. Consider the following code

#include<iostream>
#include<thread>

class H {
    public:
        void operator()(){
            printf("This is H(), I take no argument\n");
        }

        void operator()(int x){
            printf("This is H(), I received %d \n",x);
        }

};

int main(){

    int param = 0xD;

    //No object created
    std::thread td_1 = std::thread(H());
    std::thread td_2 = std::thread(H(),param);

    td_1.join();
    td_2.join();

    //From an object
    H h;
    std::thread td_3 = std::thread(h);
    std::thread td_4 = std::thread(h,param);

    td_3.join();
    td_4.join();

    return 0;
}

produce the output :

This is H(), I take no argument
This is H(), I received 13 
This is H(), I take no argument
This is H(), I received 13 

The question is, how td_1 and td_2 called the member function operator() of class H without an object of class H?

Upvotes: 2

Views: 4508

Answers (3)

VLL
VLL

Reputation: 10165

Consider this function:

void f() {
    printf("This is f(), I take no argument\n");    
}

A thread that calls the function is constructed like std::thread(f). Code like std::thread(f()) is invalid, because the parameter of std::thread must be callable (in this case, a function object). If you call the function before passing it to the constructor, std::thread can no longer call it.

So you pass f to the constructor, and it later becomes f(), and the function is called.

Similar to passing the name of a function, you can pass an object to the constructor, and the thread later calls operator(). When you write std::thread(H()), you construct a temporary object. Because class H has operator(), this code is accepted.

In fact, std::thread(H{}) is also accepted. This shows that the parentheses refer to the constructor H::H(), rather than H::operator(). You did not write a constructor for the class, but the compiler creates a default constructor.

You could also use this code to construct a temporary object with H() and immediately call operator():

int main() {
    H()(); // Valid: H() is callable
    //f()(); // Invalid: f() is not callable
}

Upvotes: 2

user12002570
user12002570

Reputation: 1

how td_1 and td_2 called the member function operator() of class H without an object of class H?

td_1 and td_2 does create objects of type H. Those objects are temporaries. Next, those supplied function object(which are temporaries in this case) are moved/copied into the storage belonging to the newly created thread of execution and invoked from there.

You can confirm this by adding a default constructor and move constructor inside class H as shown below:

#include<iostream>
#include<thread>

class H {
    public:
        void operator()(){
            printf("This is H(), I take no argument\n");
        }

        void operator()(int x){
            printf("This is H(), I received %d \n",x);
        }
        //default constructor
        H()
        {
            std::cout<<"default constructor called"<<std::endl;
        }
        //move constructor 
        H(H&&)
        {
            std::cout<<"move constructor called"<<std::endl;
        }

};

int main(){

    int param = 0xD;

    std::thread td_1 = std::thread(H());
    std::thread td_2 = std::thread(H(),param);

    td_1.join();
    td_2.join();

 
    return 0;
}

The output of the above program is:

default constructor called
move constructor called
move constructor called
default constructor called
move constructor called
move constructor called
This is H(), I take no argument
This is H(), I received 13 

Upvotes: 6

user17732522
user17732522

Reputation: 76794

The syntax H() is creating a temporary object of type H in this context. The temporary object is passed to the std::thread constructor.

Now, if the thread were to call operator() on that temporary, that would be a problem, since the temporary will only live until the end of the line std::thread td_1 = std::thread(H()); and the thread function may execute after that.

However, the thread doesn't actually use that temporary. Instead a new object of the (decayed) argument type is created for the thread, copied/moved from the temporary object you gave to the constructor. This H object lives until the thread exits and on this object operator() is called.

Upvotes: 0

Related Questions