为什么resize()会在超出容量时导致向量内容的复制而不是移动?

And*_*owl 8 c++ copy-constructor move-semantics c++11

给定X下面的类(除了明确定义的特殊成员函数与此实验无关):

struct X
{
    X() { }
    X(int) { }
    X(X const&) { std::cout << "X(X const&)" << std::endl; }
    X(X&&) { std::cout << "X(X&&)" << std::endl; }
};
Run Code Online (Sandbox Code Playgroud)

以下程序创建一个类型对象的向量X并调整其大小,以便超出其容量并强制重新分配:

#include <iostream>
#include <vector>

int main()
{
    std::vector<X> v(5);
    v.resize(v.capacity() + 1);
}
Run Code Online (Sandbox Code Playgroud)

由于class X提供了一个移动构造函数,我希望在重新分配之后将向量的先前内容移动到新存储中.相当令人惊讶的是,情况似乎并非如此,我得到的输出是:

X(X const&)
X(X const&)
X(X const&)
X(X const&)
X(X const&)
Run Code Online (Sandbox Code Playgroud)

为什么?

And*_*owl 17

C++ 11标准的第23.3.6.3/14段规定(关于类模板的resize()成员函数vector<>):

备注:如果抛出异常而非非移动构造函数,CopyInsertable T 则无效.

换句话说,这意味着对于X(即CopyInsertable),resize()提供了强有力的保证:它要么成功,要么保持向量的状态不变.

为了满足这种保证,实现通常采用复制和交换习惯用法:如果X抛出的复制构造函数,我们还没有改变原始向量的内容,所以保留了承诺.

但是,如果向量的先前内容被移动到新存储中而不是被复制并且移动构造函数抛出,那么我们将不可逆转地改变向量的原始内容.

因此,实现将使用复制构造函数X将向量的内容安全地传输到新存储中,除非已知移动构造函数不抛出,在这种情况下,从前面的元素移动是安全的.

事实上,对于X移动构造函数(将其标记为noexcept)的定义稍作更改,程序的输出现在是预期的.

struct X
{
    X() { }
    X(int) { }
    X(X const&) { std::cout << "X(X const&)" << std::endl; }
    X(X&&) noexcept { std::cout << "X(X&&)" << std::endl; }
//         ^^^^^^^^
};
Run Code Online (Sandbox Code Playgroud)

  • @KerrekSB:但那就是发生了什么.我有一个问题,我弄清楚发生了什么,我想我会分享它.例如,参见[this](http://stackoverflow.com/questions/15568959/is-a-declaration-valid-inside-an-if-block-with-no-actual-block).我觉得还可以. (3认同)
  • 回答你自己的问题,无论答案是否已经知晓,都是[明确鼓励](http://blog.stackoverflow.com/2011/07/its-ok-to-ask-and-answer-your-own-问题/). (3认同)
  • 嗯,他们是,但通常只有你有一个真正的问题,然后自己弄清楚.如果你已经提前知道答案,那就有点可疑了.如果你只是想分享一些信息,你可以将问题标记为"FAQ"和"社区Wiki". (2认同)
  • +1请继续回答您自己的问题.我从你的答案中学到了很多东西. (2认同)
  • @OlafDietsche:好的,谢谢你的支持.我很高兴有人发现它很有帮助. (2认同)

Ker*_* SB 5

考虑异常保证:如果在重新分配期间出现异常,则向量必须保持不变.这只能通过复制元素并保留旧集来保证,直到整个副本成功为止.

只有当您知道移动构造函数不抛出时,才能安全地将元素移动到新位置.为此,请声明移动构造函数noexcept.