Why move constructor is called twice when passing temporaries to thread function? [duplicate]
Asked Answered
I

1

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 ?

Intertwist answered 16/5, 2018 at 5:11 Comment(10)
Did you debug this step-wise? Set a break-point at std::cout<<"Move Constructor Called..."<<std::endl; and when execution stops there, have a look at call stack (from where Test::Test() has been called). I suspect one call in std::thread::thread() and another in call of my_thread_func(). I would enjoy to read what you can observe in debugger.Obel
@Scheff Tried debugging stepwise on onlinegdb.com but could not understand the call stack as it seems one call is going from thread constructor and other from thread implIntertwist
@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?Atmometer
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.Balm
@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 neededIntertwist
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.Balm
@Atmometer Yes I can see there is _Impl_Base shared_ptr and that might require move constructor but some how could not fit all blocks togetherIntertwist
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.Balm
in VS 2017 it works as aspectedWhitethorn
We also have this question phrased in terms of copies.Regurgitate
T
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.

Tyrelltyrian answered 5/2, 2020 at 14:5 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.