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

Kon*_*lph 28 c++ stdvector move-semantics c++11

可能重复:
如何在向量增长时强制执行移动语义?

insert,push_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);
}
Run Code Online (Sandbox Code Playgroud)

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

foo(1)
foo(2)
foo(foo(1))
~foo(1)
~foo(1)
~foo(2)
Run Code Online (Sandbox Code Playgroud)

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

foo(1)
foo(2)
foo(move(foo(1))
~foo(1)
~foo(2)
Run Code Online (Sandbox Code Playgroud)

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

值得注意的是GCC 4.5.1做了预期的事情 - 这是GCC 4.7中的回归还是一些狡猾的聪明优化,因为编译器看到我的对象复制便宜(但是如何?!)?

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

Jer*_*fin 12

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

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

如果除非由非CopyInsertable类型的move构造函数抛出异常,则不会产生任何影响.

[并且resize§23.3.6.3/ 12中的描述要求相同.]

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

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

  • 两年后,我现在确信这种解释是错误的 - 它可能符合标准的字母,但几乎肯定不符合意图:"noexcept"被C++(包括标准专家)的用户广泛解释为*绑定*nothrow规范,与你所说的相反.事实上,库实现者广泛地使用它,虽然我的问题中使用的GCC实现可能严格符合,但它执行次优.GCC的后续版本修复了这种行为,这一点得到了支持. (3认同)

Ben*_*igt 7

这不是回归,而是一个bug修复.该标准指定std :: vector仅优先选择非抛出的元素移动构造函数.

另请参阅此解释此错误报告.

这个问题也很重要.


How*_*ant 7

Tip-of-trunk clang + libc ++获取:

foo(1)
foo(2)
foo(move(foo(1))
~foo(2)
~foo(1)
Run Code Online (Sandbox Code Playgroud)

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

foo(1)
foo(2)
foo(foo(1))
~foo(1)
~foo(2)
~foo(1)
Run Code Online (Sandbox Code Playgroud)


归档时间:

查看次数:

6493 次

最近记录:

12 年,9 月 前