可移动、不可复制参数的std::thread

10

以下程序无法在VS11 beta,gcc 4.5或clang 3.1中构建

#include <thread>
#include <memory>

int main() {
    std::unique_ptr<int> p;
    std::thread th([](std::unique_ptr<int>) {

    },std::move(p));
    th.join();
}

这是因为参数类型不可复制,但实现尝试复制它。

据我所知,这个程序是良好形式的,并且应该可以工作。std::thread的要求似乎暗示了可移动的、不可复制的参数应该在这里工作。具体来说,它表示可调用对象和每个参数应满足MoveConstructible要求,并且INVOKE(DECAY_COPY(std::forward<F>(f)),DECAY_COPY(std::forward<Args>(args))...)应该是一个有效的表达式。

在这种情况下,我认为表达式的结果大致如下:

template <class T> typename std::decay<T>::type decay_copy(T&& v)
{ return std::forward<T>(v); }

std::unique_ptr<int> p;
auto f = [](std::unique_ptr<int>) {};

decay_copy(f)(decay_copy(std::move(p)));

我认为这个过程不应该涉及到p的复制。至少gcc可以编译这个表达式,但VS11不能。

  1. 我对要求和参数必须可复制有误吗?
  2. 标准在这个问题上是否给实现留有余地来复制参数?
  3. 还是我尝试的实现不符合规范?

你似乎是按值传递线程参数(根据匿名函数签名)。参数类型不应该是 std::unique_ptr<int>&&const std::unique_ptr<int>& 吗? - André Caron
3
@André: 没有所谓的传递副本;按值传递参数将根据调用者传递一个左值还是右值来复制或移动。 - ildjarn
1
@ildjarn:抱歉,我的意思是“按值传递”,而不是“按副本传递”。我忘记了如果有可用的移动构造函数,则按值传递参数将选择移动构造函数。 - André Caron
2个回答

14

从N3337的30.3.1.2,第3和4段:

template <class F, class ...Args> explicit thread(F&& f, Args&&... args);

要求F 和每个Ti(指Args中的每个类型)都应满足MoveConstructible的要求。调用INVOKE (DECAY_-COPY ( std::forward<F>(f)), DECAY_COPY (std::forward<Args>(args))...)(20.8.2)必须是有效的表达式。

效果:构造一个类型为thread的对象。新的执行线程将执行INVOKE (DECAY_-COPY ( std::forward<F>(f)), DECAY_COPY (std::forward<Args>(args))...),在构造线程中评估对DECAY_COPY的调用。忽略此调用返回的任何值。[注意:这意味着从复制的f的调用中没有抛出的任何异常将在构造线程而不是新线程中抛出。——end note]如果INVOKE (DECAY_COPY ( std::forward<F>(f)), DECAY_COPY (std::forward<Args>(args))...)的调用以未捕获的异常终止,则将调用std::terminate

所以是的,这应该可以工作。如果不行,那么这就是你的实现中的一个错误。

请注意,任何参数的移动/复制都将在新线程上发生。你正在向另一个线程传递引用,因此需要确保它们仍然存在,直到该线程开始才可以。


现在我无法在clang中复现这个错误,尽管我用的代码早已存在源代码库中,并且我在历史记录中有完整的命令行。我猜我应该重新检查vs11。 - bames53
1
实际上,问题似乎是旧版的libc++与最新版之间的差异。 - bames53
2
这段代码也适用于我的Just::Thread库,支持g++ 4.5和4.6以及MSVC 2010。 - Anthony Williams

3

作为另一种选择,也是标准std::thread用法,您可以传递一个引用包装器:

int p;
std::thread([](int & x) { /* ... */ }, std::ref(p));

此代码会创建一个类型为std::reference_wrapper<int>的对象,它具有值语义并包装了一个对int的引用(即复制这个包装器别名这个引用)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接