4

In below code I could not understand why move constructor of class is called twice considering that my thread function is taking argument by rvalue reference and so I was hoping move constructor will be called only once when arguments will be moved to thread constructor.Can somebody give insights on how thread constructor works and how it passes argument to thread function.

#include <iostream>
#include <thread>
#include <chrono>
class Test {
  public:
  Test() {}
  Test(Test&&)
  {
    std::cout<<"Move Constructor Called..."<<std::endl;
  }
};
void my_thread_func(Test&& obj)
{
  using namespace std::chrono_literals;
  std::cout<<"Inside thread function..."<<std::endl;
  std::this_thread::sleep_for(2s);
}
int main() {
  std::thread t(my_thread_func,Test());
  std::cout << "Hello World!\n";
  t.join();
  return 0;
}

This question is not concerned with that thread constructor arguments are passed by value and it is more concerned with why move constructor is called twice ?

PapaDiHatti
  • 1,841
  • 19
  • 26
  • Did you debug this step-wise? Set a break-point at `std::cout<<"Move Constructor Called..."< – Scheff's Cat May 16 '18 at 05:40
  • @Scheff Tried debugging stepwise on https://www.onlinegdb.com/ but could not understand the call stack as it seems one call is going from thread constructor and other from thread impl – PapaDiHatti May 16 '18 at 06:43
  • @Kapil I'm thinking that if there is a `std::thread` object that take `Test` by value and we can also see that (at least on gcc) there is a `std::shared_ptr` to a `_Impl_Base`. How would `Test` get passed from `std::thread` to `_Impl_Base` without an additional move? – super May 16 '18 at 06:54
  • A thread objects *stores* thread function arguments. That's one ctor. Then it calls the thread function and passes it *stored* arguments. That's the other ctor. – n. m. could be an AI May 16 '18 at 06:55
  • @n.m. When thread function is passing its stored argument to function like above which is taking argument by rvalue reference then why move constructor is needed – PapaDiHatti May 16 '18 at 06:59
  • Sorry I totally missed the rvalue ref on the thread function. Ostensibly the second ctor would not be needed then. My bad. Perhaps the std::thread implementation makes a redundant move. – n. m. could be an AI May 16 '18 at 07:02
  • @super Yes I can see there is _Impl_Base shared_ptr and that might require move constructor but some how could not fit all blocks together – PapaDiHatti May 16 '18 at 07:04
  • 7
    I have dug into an implementation of std::thread in gcc7.3. It calls a function that makes an "invoker" struct which essentially contains a tuple of the thread function and its arguments, and returns it by value. Then the implementation allocates another "state" struct on the heap which contains a copy of the "invoker". That's two constructors. I think it's redundant and the implementation could have created the object on the heap directly without having to construct a temporary invoker. The implementation would have to be made a bit more complicated though. – n. m. could be an AI May 16 '18 at 07:33
  • in VS 2017 it works as aspected – alangab May 16 '18 at 11:49
  • 1
    We also have this question [phrased in terms of copies](https://stackoverflow.com/q/49703908/8586227). – Davis Herring May 17 '18 at 00:21

1 Answers1

0

The extra move construction is allowed by the standard, but is potentially less efficient. That missed-optimization bug was https://gcc.gnu.org/PR69724 and has been fixed for the upcoming GCC 10 release.

Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521