【问题标题】:Forwarding non-copyable type to std::thread将不可复制类型转发到 std::thread
【发布时间】:2015-04-02 03:07:25
【问题描述】:

以下程序不在 GCC 4.9.2 或 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); 
}

这是因为 Foo 不可复制,并且将 Foo 的实例转发到 std:thread 会尝试复制它(为什么?)。但是,如果您将相同的实例直接转发给 lambda,它会编译。

我想,我并不完全理解 std::thread 构造函数的所有要求。任何帮助将不胜感激。

编辑:即使我们使 Foo 可移动,情况 2 也不起作用。有什么线索吗?

谢谢。

【问题讨论】:

  • std::thread 的构造函数会生成一个副本,因为替代方案太容易出错(太容易导致悬空引用和数据竞争)。如果您不想要副本,请使用 reference_wrapper,或者让 lambda 也通过引用捕获 args
  • std::ref 和引用包装器——但在这种情况下这很愚蠢,因为引用必须持续超过线程的生命周期。
  • 所以,提问者,您确实知道上面的代码(如果您确实设法传递了一个右值 ref)由于竞争条件将是未定义的行为,对吧?
  • 处理这个问题的最好方法是使用shared_ptr 来保存不可复制的实例。
  • @Yakk 如果我通过使 Foo 可移动来修复它,为什么上面的代码是未定义的行为?

标签: c++ multithreading c++11 move-semantics perfect-forwarding


【解决方案1】:

http://en.cppreference.com/w/cpp/thread/thread/thread 说:“首先,构造函数将所有参数 args 复制/移动到线程可访问的存储中”

但是,Foo 既不可复制也不可移动。如果将移动构造函数添加到 Foo,则它会编译:

struct Foo {
  Foo() {};

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

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

如果您想将左值传递给线程,这不起作用:线程构造函数尝试复制或移动 Foo 对象。但是,Foo 是不可复制的,并且您不能从左值 (my_foo) 移动。你有这些可能性:

  • 把左值变成右值:

    my_function&lt;Bar&gt;(std::move(my_foo));

这很危险,因为现在 my_foo 处于不可用状态。

  • 用引用包装左值并将引用包装器传递给线程:

    my_function&lt;Bar&gt;(std::cref(my_foo));

(或std::ref,如果要修改my_foo

【讨论】:

  • @ Bulletmagnet 谢谢,这解决了它。但是,如果我没有传递一个右值,而是传递一个左值,那么当 lambda 编译时,线程仍然无法编译。请参阅上面的编辑。
猜你喜欢
  • 2012-04-17
  • 2016-06-25
  • 2013-05-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-04-07
  • 1970-01-01
  • 2020-12-15
相关资源
最近更新 更多