6

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

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]
4

3 回答 3

4

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

但是为什么这不会自动发生在我身上?

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

为了避免在这种情况下出现副本,您需要a通过 using来转移std::move(a)(即使将其传递给std::thread)。它第一次复制的原因是因为 std::thread 不能保证在您完成构造 std::thread 后该值将存在(并且您没有明确地将其移入)。因此,它会做安全的事情并制作副本(不获取您传入的内容的引用/指针并存储它:代码不知道您是否会使其保持活动状态)。

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

于 2013-04-14T18:19:19.263 回答
2

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

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

如果您使您的类型可移动,您会发现第二个副本变成了移动。

为什么一个实现会这样做,而不是直接复制到最终目的地?可能有许多依赖于实现的原因。在堆栈上构建一个函数+参数可能更简单tuple,然后将其移动到最终目的地。

于 2019-04-13T14:56:09.700 回答
0

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

于 2013-04-14T18:07:25.417 回答