std :: thread为什么对象被复制两次?

时间:2013-04-13 17:24:44

标签: c++ c++11

为什么在下面的示例代码中,对象被复制两次?根据线程类的文档构造函数将所有参数复制到线程本地存储,因此我们有理由进行第一次复制。第二个怎么样?

class A {
public:
    A() {cout << "[C]" << endl;}
    ~A() {cout << "[~D]" << endl;}
    A(A const& src) {cout << "[COPY]" << endl;}
    A& operator=(A const& src) {cout << "[=}" << endl; return *this;}

    void operator() () {cout << "#" << endl;}
};

void foo()
{
    A a;
    thread t{a};
    t.join();
}

上面的输出:

[C]
[COPY]
[COPY]
[~D]
#
[~D]
[~D]

编辑: 是的,在添加移动构造函数之后:

A(A && src) {cout << "[MOVE]" << endl;}

输出如下:

[C]
[COPY]
[MOVE]
[~D]
#
[~D]
[~D]

3 个答案:

答案 0 :(得分:4)

对于您想要移动或避免复制的任何内容,请选择移动构造函数和std::move

但为什么这不会自动发生?

C ++中的移动是保守的。它通常仅在您明确写入std::move()时才会移动。这样做是因为移动语义,如果超出非常明确的情况,可能会破坏旧代码。出于这个原因,自动移动通常仅限于非常谨慎的情况。

为了避免在这种情况下复制,您需要使用a(即使将其传递到std::move(a)时)转移std::thread。它第一次复制的原因是因为std :: thread不能保证在你完成构造std :: thread之后该值将存在(并且你还没有明确地将其移入) 。因此,它会做安全的事情并制作副本(不要参考/指向你传入的内容并存储它:代码不知道你是否将它保持活着)。

同时使用移动构造函数和使用std::move将允许编译器最大限度地有效地移动您的结构。如果您正在使用VC ++(使用CTP或不使用CTP),则必须显式编写移动构造函数,否则MSVC将(甚至有时会错误地)声明并使用Copy构造函数。

答案 1 :(得分:1)

该对象被复制了两次,因为该对象不能被移动。该标准不需要这样做,但这是合法行为。

该实现内部发生的事情是,它似乎在按照标准要求对参数进行衰减复制。但是它并没有将衰减复制复制到最终目的地。它会将其存储到一些内部(可能是堆栈)存储中。然后,它将对象从该临时存储区移动到线程内的最终位置。由于您的类型不可移动,因此必须执行复制。

如果您将类型设为可移动类型,则会发现第二个副本变为可移动。

为什么实现会这样做,而不是直接复制到最终目标中?可能有许多与实现相关的原因。在栈上构建tuple的function + parameters,然后将其移至最终目标可能更简单。

答案 2 :(得分:0)

尝试:thread t {std :: move(a)};