3
votes

The following program does not build in GCC 4.9.2 or clang 3.6:

#include <iostream>
#include <vector>
#include <thread>

/* Non-copyable type */
struct Foo {
  Foo() {};

  Foo(const Foo &) = delete;
  Foo &operator=(const Foo &) = delete;

  Foo(Foo &&) = default; //EDIT: This fixes CASE 1
};

/* Bar depends on Foo */
struct Bar {
  Bar(const Foo &) {}
};

template <class T, class... Args> 
void my_function(Args &&... args) {
  std::vector<T> data;
  auto lambda = [&data](Args &&... ts) { data.emplace_back(ts...); };

  lambda(std::forward<Args>(args)...);              // <-- Compile
  std::thread(lambda, std::forward<Args>(args)...); //<--- Does NOT compile
}

int main() { 
  //CASE 1: (works)
  // my_function<Bar>(Foo());
  //CASE 2: (does not work)
  Foo my_foo;
  my_function<Bar>(my_foo); 
}

This is because Foo is not copyable and forwarding an instance of Foo to an std:thread attempts to copy it (why?). However, if you forward the same instance to the lambda directly it compiles.

I guess, I don't fully understand all the requirements of the std::thread constructor. Any help would be greatly appreciated.

EDIT: Case 2 does not work even if we make Foo movable. Any clue?

Thanks.

1
std::thread's constructor makes a copy because the alternative would be way too error prone (it would be way too easy to cause dangling references and data races). If you don't want a copy, then use a reference_wrapper, or make the lambda also capture args by reference. - T.C.
std::ref and reference wrapper -- but that is silly in this context, as the reference must last beyond the thread's lifetime. - Yakk - Adam Nevraumont
So, asker, you do know the code above (if you did manage to pass an rvalue ref) would be undefined behaviour due to the race condition, right? - Yakk - Adam Nevraumont
The best way to deal this problem is to use a shared_ptr to hold the noncopyable instance. - Siyuan Ren
@Yakk If I fix it by making Foo movable, why is the code above undefined behavior? - user1532887

1 Answers

1
votes

http://en.cppreference.com/w/cpp/thread/thread/thread says: "First the constructor copies/moves all arguments args... to thread-accessible storage"

However, Foo is neither copyable nor movable. If you add the move constructor to Foo, then it compiles:

struct Foo {
  Foo() {};

  Foo(const Foo &) = delete;
  Foo &operator=(const Foo &) = delete;

  Foo(Foo &&) = default; // newly added
};

This does not work if you want to pass an lvalue to the thread: the thread constructor tries to copy or move the Foo object. However, Foo is not copyable, and you can't move from an lvalue (my_foo). You have these possibilities:

  • Turn the lvalue into an rvalue:

    my_function<Bar>(std::move(my_foo));

This is dangerous, because now my_foo is in an unusable state.

  • Wrap the lvalue with a reference and pass the reference wrapper to the thread:

    my_function<Bar>(std::cref(my_foo));

(or std::ref, if you want to modify my_foo)