2

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?

Sad egg
  • 79
  • 1
  • 9
  • 4
    Your question has nothing to do with threading, to be able to call a member function either you need an instance of H, or the member function must be static. For passing objects (by reference) to threads have a look at lambda functions, and for starting threads have a look at std::async (IMO a better abstraction for starting asynchronous stuff) – Pepijn Kramer Feb 17 '22 at 05:36
  • 3
    `td_1` and `td_2` does create instances of the class `H`. You can confirm this by **adding a default constructor** `H(){std::cout<<"default constructor called"< – Jason Feb 17 '22 at 05:37
  • 3
    In `std::thread(H())`, the `H()` is creating a temporary object. it is that object that gets the `operator ()` called on. – NathanOliver Feb 17 '22 at 05:37
  • 1
    @NathanOliver `std::thread` will make a copy of the temporary and `operator()` will be called on that copy. – user17732522 Feb 17 '22 at 05:51

3 Answers3

6

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 
Jason
  • 36,170
  • 5
  • 26
  • 60
  • Why move constructor is called twice? Can you tell me this question is related to which basic concept of C++? I just cant understand any answer here – Sad egg Feb 17 '22 at 06:10
  • A side question, How creating a thread like `std::thread t3(&H::operator(),H());` is also valid ? The `operator()` of `H` don't accept any argument – Sad egg Feb 17 '22 at 06:14
  • 1
    @Sadegg it's more about implementation of that constructor which is uses an overloaded version in that case. It takes a pointer to member and instance of object. If `ptr` is a pointer to a member function, and `obj` is an instance, `obj.*ptr()` is the call to that member using `obj`. – Swift - Friday Pie Feb 17 '22 at 06:20
  • 2
    @Sadegg For your other question take a look at [Why move constructor is called twice when passing temporaries to thread function?](https://stackoverflow.com/questions/50362849/why-move-constructor-is-called-twice-when-passing-temporaries-to-thread-function). You can also ask a separate question for why move constructor called twice if the above provided link doesn't have any accepted answer for that question. – Jason Feb 17 '22 at 06:23
  • 1
    @Sadegg " I just cant understand any answer here" Then you should start reading about objects, ownership, move idioms and the basics about temporary objects. – Klaus Feb 17 '22 at 12:58
2

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
}
VLL
  • 9,634
  • 1
  • 29
  • 54
0

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.

user17732522
  • 53,019
  • 2
  • 56
  • 105
  • So the line `std::thread td_1 = std::thread(H());`, the `H()` is the constructor or the `()` operator of temp object of class `H` ? – Sad egg Feb 17 '22 at 06:03
  • 2
    @Sadegg The `H()` is for the constructor and not for overloaded `operator()`. – Jason Feb 17 '22 at 06:04