-2

I have been trying to invoke threads from class constructors to no avail. Why is it so hard to pass a function or object to a thread.

#include <iostream>
#include <thread>
class a{
        public:
        a();
        std::thread t;
        int data;
        //virtual void fn();
};

void fn(a *p)
{
        std::cout << "Thread Function : " << p->data << std::endl;
}

a::a():data(10)
//a::a():data(10),t(fn,this)
{

        void (*fnp)(a *p) = fn;
        fn(this);
        fnp(this);
        t(fnp, this);
}

int main ()
{
        a av;
        return 0;
}

Its output looks as:

preetam@preetam-GL702ZC:~/Desktop$ g++ v.cpp -lpthread
v.cpp: In constructor ‘a::a()’:
v.cpp:23:13: error: no match for call to ‘(std::thread) (void (*&)(a*), a*)’
  t(fnp, this);

All I want is to start a thread from the constructor and have that thread access the class members with ease.

preetam
  • 1,451
  • 1
  • 17
  • 43
  • 1
    `t = std::thread(fnp, this);`? Did you read through documentation, of [`std::thread`](https://en.cppreference.com/w/cpp/thread/thread)? – Algirdas Preidžius Mar 26 '20 at 10:39
  • it isnt that difficult. What works in the constructor initializer list doesnt necessarily work in the body. The initializer list takes initializers, in the body you attempt to call `operator()`. thread has no `operator()` – 463035818_is_not_an_ai Mar 26 '20 at 10:42
  • you would get analogous error if you write `av();` in main – 463035818_is_not_an_ai Mar 26 '20 at 10:43
  • It is the same as if you wrote this in `main`: `a av; std::thread t; t(fn, &av);`, while using the initialiser list is the same as `a av; std::thread t(fn, &av);` – molbdnilo Mar 26 '20 at 10:48

1 Answers1

1

There are a couple of ways to do this. The one that's most similar to the code in the question is simply this:

a::a() : data(10) {
    std::swap(t, std::thread(fn, this));
}

That's because threads are created in the constructor of a thread object. In this code, t gets default-constructed because it's not mentioned in the initializer list. The code in the body of the constructor constructs a temporary thread object and swaps it with the default-constructed t object. After the swap, t has the thread that's running fn(this), and the temporary object has the default-constructed thread. At the end of the statement the temporary thread object gets destroyed.

Instead of that create-and-swap approach, you can also create the thread directly in the initializer list:

a::a() : data(10), t(fn, this) { // won't work, though...
}

The problem here is a bit subtle: despite putting the initializer for data in front of the initializer for t, the constructor for t runs before data gets initialized. So there's a data race. This happens because member objects are constructed in their order of declaration; t is declared before data, so t gets constructed before data gets initialized. Regardless of their order in the initializer list of the constructor.

So, the solution is to change the order of the declarations:

class a {
    int data;
    std::thread t;
    a();        
};

Now the previous code will work correctly.

If you're writing code like this, with an important dependency on the order of declaration of the members, you owe to future maintainers, including yourself, to document that dependency with a comment:

class a {
    // IMPORTANT: data must be declared before t
    int data;
    std::thread t;
    a();        
};

Note, also, that with both of these approaches, the thread that gets launched is operating on an object whose constructor has not finished running. If the thread function uses any members of that object and the constructor modifies those members after launching the thread you've got a data race, hence, undefined behavior.

In particular, if your plan is to add virtual functions to a and override them in derived classes, you're doomed. When the thread is launched, the base class constructor is still running, and the member initializers and body of the derived class constructor have not run. They'll run after the base class constructor, and again, you're deep into undefined behavior.

Pete Becker
  • 74,985
  • 8
  • 76
  • 165