【发布时间】: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