Std::Thread Pass by Reference Calls Copy Constructor

std::thread pass by reference calls copy constructor

std::thread takes its arguments by value. You can get reference semantics back by using std::reference_wrapper:

std::thread newThread(session, &sock, std::ref(logger));

Obviously you must make sure that logger outlives the thread.

std::thread in constructor referencing it's deleted copy constructor?

You have to std::move() the RenderThread constructor's parameter into the constructor of the m_renderThread member:

   : m_renderThread(std::move(threadToGive)) {}

Start std::thread in member function of object that does not have a copy constructor

std::thread use std::invoke to call the function. std::invoke is smart enough to be able to take a pointer to an object and call a member function with it. That means you can change t2 to

std::thread t2(&Dummy::print, &d);

and get the correct behavior.

Why is this constructor being called 3 times when passing to std::thread?

It's an implementation detail of the C++ standard library and is probably different in different implementations. There is more going on in std::thread t(takeWidget, widget) than simply calling takeWidget(widget). For example, in the GCC implementation, the thread constructor calls make_tuple() which would move or copy the parameters depending on their types.

Copy constructor calls when creating a new thread

If you set breakpoint in copy constructor you can see constructor call context in Call Stack window. In debug mode i found next points when constructor is called:

  • First off the functional object is copied in helper function bind

  • Then the functional object is moved into a internal functional object _Bind

  • After that a class for launching threads _LaunchPad is created. In

    a constructor it takes rvalue reference to the _Bind instance so we have

    another one move constructor call

  • move constructor of _LaunchPad is called when copy of it is created in new thread.

Thus we have 4 copy constructor call in your case. If you added move constructor you would see 1 copy constructor and 3 move constructor calls.

In release mode all empty constructor calls is elided and assembler code looks quite simple

std::thread initialization with class argument results with class object being copied multiple times

There are a lot of copying/moving going on in the background. Note however, that neither the copy constructor nor the move constructor is called when the thread constructor is called.

Consider a function like this:

template<typename T> void foo(T&& arg);

When you have r-value references to template arguments C++ treats this a bit special. I will just outline the rules here. When you call foo with an argument, the argument type will be

  • && - when the argument is an r-value
  • & - all other cases

That is, either the argument will be passed as an r-value reference or a standard reference. Either way, no constructor will be invoked.

Now look at the constructor of the thread object:

template <class Fn, class... Args>
explicit thread (Fn&& fn, Args&&... args);

This constructor applies the same syntax, so arguments will never be copied/moved into the constructor arguments.

The below code contains an example.

#include <iostream>
#include <thread>

class Foo{
public:
int id;

Foo()
{
id = 1;
std::cout << "Default constructor, id = " << id << std::endl;
}

Foo(const Foo& f)
{
id = f.id + 1;
std::cout << "Copy constructor, id = " << id << std::endl;
}

Foo(Foo&& f)
{
id = f.id;
std::cout << "Move constructor, id = " << id << std::endl;
}
};

void doNothing(Foo f)
{
std::cout << "doNothing\n";
}

template<typename T>
void test(T&& arg)
{
}

int main()
{
Foo f; // Default constructor is called

test(f); // Note here that we see no prints from copy/move constructors

std::cout << "About to create thread object\n";
std::thread t{doNothing, f};
t.join();

return 0;
}

The output from this code is

Default constructor, iCount = 1
About to create thread object
Copy constructor, id = 2
Move constructor, id = 2
Move constructor, id = 2
doNothing
  • First, the object is created.
  • We call our test function just to see that nothing happens, no constructor calls.
  • Because we pass in an l-value to the thread constructor the argument has type l-value reference, hence the object is copied (with the copy constructor) into the thread object.
  • The object is moved into the underlying thread (managed by the thread object)
  • Object is finally moved into the thread-function doNothing's argument


Related Topics



Leave a reply



Submit