C++ 为什么要重新分配向量副本而不是移动元素?

C++ 为什么要重新分配向量副本而不是移动元素?,c++,c++11,stdvector,move-semantics,C++,C++11,Stdvector,Move Semantics,可能重复: 插入,推回和安置(\u back)可能导致重新分配标准向量。我很困惑地看到下面的代码复制了元素,而不是在重新分配容器时移动它们 #include <iostream> #include <vector> struct foo { int value; explicit foo(int value) : value(value) { std::cout << "foo(" << value <&

可能重复:

插入
推回
安置
\u back
)可能导致重新分配
标准向量
。我很困惑地看到下面的代码复制了元素,而不是在重新分配容器时移动它们

#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);
}
但是,删除复制构造函数(
foo(foo const&)=delete;
)时,将生成以下(预期)输出:

foo(1)
foo(2)
foo(move(foo(1))
~foo(1)
~foo(2)
为什么呢?一般来说,移动不是比复制更有效率,或者至少不会比复制效率低很多吗

值得注意的是,这是GCC4.7中的一个回归,还是因为编译器认为我的对象复制成本很低而进行了一些异常聪明的优化(但是怎么做?!)


还要注意,我通过实验性地放置
foos.reserve(2),确保这是由重新分配引起的在插入的前面;这导致既不执行复制也不执行移动。

这不是回归,而是错误修复。该标准规定std::vector只喜欢非抛出的元素移动构造函数

另见和


也很相关。

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

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

如果异常不是由非CopyInsertable类型的move构造函数引发的,则不会产生任何影响

[和§23.3.6.3/12中的
resize
说明要求相同。]

这意味着,如果T是可复制插入的,那么您将获得强大的异常安全性。为了确保这一点,它只能在推断(通过未指定的方式)移动构造永远不会抛出时使用移动构造。但是,不能保证
throw()
noexcept
对此是必要的或足够的。如果T是可复制插入的,它可以简单地选择始终使用复制构造。基本上,现在的情况是,标准要求像语义一样的复制构造;编译器只能在as-if规则下使用move构造,并且可以自由定义何时或是否执行该选项


若T不可复制插入,则重新分配将使用移动构造,但异常安全性取决于T的移动构造函数是否可以抛出。如果它不抛出,您将获得很强的异常安全性,但如果它抛出,您不会(我认为您可能获得了基本的保证,但甚至可能没有,而且肯定没有更多)。

trunk clang+libc++的技巧是:

foo(1)
foo(2)
foo(move(foo(1))
~foo(2)
~foo(1)
如果从move构造函数中删除
noexcept
,则会得到复制解决方案:

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

这不是一个回归,这是一个错误修复。该标准规定,
std::vector
只会选择非抛出的元素移动构造函数。@KonradRudolph:我认为这不是问题。据我所知,副本被保留下来,只是成为基本问题的指针。我错了吗?@KonradRudolph:答案不是说使用
noexcept
,而是说使用
throw()
。你试过了吗?@BenVoigt no,
throw()
也没有帮助。由于
noexcept
操作符(不要与
noexcept
规范混淆)和类型特征,可以在没有throw时分派<代码>标准::如果没有异常,则移动即可。很抱歉,这是错误的,请参阅更新的问题
noexcept
不更改任何内容。@KonradRudolph:单击错误报告链接。@KonradRudolph如果
noexcept
不更改任何内容,正确的答案不是说这个答案是错误的,而是尝试
静态断言(std::is\nothrow\u move\u constructible::value,”
@Ben错误报告(以及另一个问题的一个答案)明确提到了
noexcept
,这就是我感到困惑的原因。此外,使用
noexcept
更符合逻辑,不是吗?为了让事情变得更糟,
throw()
在我的机器上也不起作用。@Luc唉,没有骰子。元函数的计算结果为
false
。当然,这是一个错误,不是吗?两年后,我现在确信这个解释是错误的——它可能符合标准的字母,但几乎肯定不是意图:<代码>除< /代码>之外,C++用户(包括标准化专家)被广泛解释为绑定的NoFuffe规范,与您所说的相反。事实上,库实现者广泛地使用它,尽管在我的问题中使用的GCC实现可能严格符合规范,但它的性能并不理想。GCC的后续版本修复了这种行为,这一事实支持了这一点。
foo(1)
foo(2)
foo(foo(1))
~foo(1)
~foo(2)
~foo(1)