为什么重新分配向量副本而不是移动元素? [英] Why does reallocating a vector copy instead of moving the elements?

查看:120
本文介绍了为什么重新分配向量副本而不是移动元素?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述


可能重复:

如何在向量增长时执行移动语义?


插入 push_back emplace _back )可以导致重新分配 std :: vector 。在重新分配容器时,我发现下面的代码复制 而不是移动,我很困惑。

  #include< iostream> 
#include< vector>

struct foo {
int value;

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

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

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

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

int main(){
std :: vector< foo> foos;
foos.emplace_back(1);
foos.emplace_back在我的特定机器上使用我的特定编译器(GCC 4.7),这将打印以下内容:


$ b



  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
〜foo(2)

为什么? ,或至少比复制效率低得多?



它注意到 GCC 4.5。 1执行预期的事情 - 这是一个在GCC 4.7的回归或者是一些狡猾的聪明的优化,因为编译器看到我的对象是便宜的复制(但如何?)。



另外请注意,我确保这个是由重新分配,通过实验放置一个 foos.reserve(2); 在插入前面;

解决方案

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

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


如果一个非CopyInsertable类型的move构造函数抛出异常,则没有效果。


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



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



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


Possible Duplicate:
How to enforce move semantics when a vector grows?

insert, push_back and emplace(_back) can cause a reallocation of a std::vector. I was baffled to see that the following code copies the elements instead of moving them while reallocating the container.

#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);
}

On my specific machine using my specific compiler (GCC 4.7) this prints the following:

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

However, when deleting the copy constructor (foo(foo const&) = delete;), the following (expected) output is generated:

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

Why is that? Would’t moving generally be more efficient, or at least not much less efficient, than copying?

It bears noting that GCC 4.5.1 does the expected thing – is this a regression in GCC 4.7 or is it some deviously clever optimisation because the compiler sees that my object is cheap to copy (but how?!)?

Also note that I made sure that this is caused by reallocation, by experimentally putting a foos.reserve(2); in front of the insertions; this causes neither copy nor move to be executed.

解决方案

The short answer is that I think @BenVoigt is basically correct.

In the description of reserve (§23.3.6.3/2), it says:

If an exception is thrown other than by the move constructor of a non-CopyInsertable type, there are no effects.

[And the description of resize in §23.3.6.3/12 requires the same.]

This means that if T is CopyInsertable, you get strong exception safety. To assure that, it can only use move construction if it deduces (by unspecified means) that move construction will never throw. There's no guarantee that either throw() or noexcept will be necessary or sufficient for that though. If T is CopyInsertable, it can simply choose to always use copy construction. Basically, what's happening is that the standard requires copy construction-like semantics; the compiler can only use move construction under the as-if rule, and it's free to define when or if it'll exercise that option.

If T is not CopyInsertable, reallocation will use move construction, but exception safety depends on whether T's move constructor can throw. If it doesn't throw, you get strong exception safety, but if it throws, you don't (I think you probably get the basic guarantee, but maybe not even that and definitely no more).

这篇关于为什么重新分配向量副本而不是移动元素?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆