为什么重新分配矢量副本而不是移动元素?

时间:2012-04-12 16:18:41

标签: c++ c++11 stdvector move-semantics

  

可能重复:
  How to enforce move semantics when a vector grows?

insertpush_backemplace_back)可能导致重新分配std::vector。在重新分配容器时,我感到困惑的是看到以下代码复制元素而不是移动它们。

#include <iostream>
#include <vector>

struct foo {
    int value;

    explicit foo(int value) : value(value) {
        std::cout << "foo(" << value << ")\n";
    }

    foo(foo const& other) noexcept : value(other.value) {
        std::cout << "foo(foo(" << value << "))\n";
    }

    foo(foo&& other) noexcept : value(std::move(other.value)) {
        other.value = -1;
        std::cout << "foo(move(foo(" << value << "))\n";
    }

    ~foo() {
        if (value != -1)
            std::cout << "~foo(" << value << ")\n";
    }
};

int main() {
    std::vector<foo> foos;
    foos.emplace_back(1);
    foos.emplace_back(2);
}

在我使用特定编译器(GCC 4.7)的特定计算机上,将打印以下内容:

foo(1)
foo(2)
foo(foo(1))
~foo(1)
~foo(1)
~foo(2)

但是,删除复制构造函数(foo(foo const&) = delete;)时,会生成以下(预期)输出:

foo(1)
foo(2)
foo(move(foo(1))
~foo(1)
~foo(2)

为什么?移动通常不会比复制更有效率,或者至少效率不高吗?

值得注意的是GCC 4.5.1 does the expected thing - 这是GCC 4.7中的回归还是一些非常聪明的优化,因为编译器发现我的对象复制起来很便宜(但是怎么样?!)?

另请注意,我通过实验性地在插入前放置foos.reserve(2);来确保重新分配导致此 ;这导致既不复制也不移动。

3 个答案:

答案 0 :(得分:12)

简短的回答是我认为@BenVoigt基本上是正确的。

reserve(§23.3.6.3/ 2)的描述中,它说:

  

如果除了非CopyInsertable类型的move构造函数之外抛出异常,则没有效果。

[§23.3.6.3/ 12中对resize的描述要求相同。]

这意味着如果T是CopyInsertable,则会获得强大的异常安全性。为了确保,如果推断(通过未指定的方式)移动构造永远不会抛出,它可以使用移动构造。但是,无法保证throw()noexcept对此有必要或充分。如果T是CopyInsertable,它可以只选择始终使用复制构造。基本上,正在发生的是标准需要复制构造式语义;编译器只能在as-if规则下使用move构造,并且可以自由定义何时或是否会运用该选项。

如果T不是CopyInsertable,重新分配使用移动构造,但异常安全取决于T的移动构造函数是否可以抛出。如果它没有抛出,你就会获得强大的异常安全性,但是如果它抛出,你就不会(我认为你可能得到了基本的保证,但也许甚至没有,绝对没有)。

答案 1 :(得分:7)

这不是回归,而是修复错误。该标准指定std :: vector只会优先选择非投掷的元素移动构造函数。

另请参阅this explanationthis bug report

This question也很重要。

答案 2 :(得分:7)

Tip-of-trunk clang + libc ++ gets:

foo(1)
foo(2)
foo(move(foo(1))
~foo(2)
~foo(1)

如果从移动构造函数中删除noexcept,则会获得复制解决方案:

foo(1)
foo(2)
foo(foo(1))
~foo(1)
~foo(2)
~foo(1)