【问题标题】:What happens to the underlying storage upon vector's copy/move assignment?向量的复制/移动分配后底层存储会发生什么?
【发布时间】:2014-04-10 19:01:58
【问题描述】:

对于 std::vector 的复制分配,当源的大小小于目标的容量时,是否允许重新分配存储和缩小容量?还是保证不会发生重新分配/收缩(即始终尊重先前的 Reserve())?

另一方面,如果源的大小大于目标的容量并且发生重新分配,是否要求重新分配尊重源的容量(例如目标的新容量不应小于源的容量,或者甚至要求它们相同)?或者重新分配只是完成其工作(基于新大小)而不考虑源的容量?

至于移动分配,我想不会发生存储重新分配(尽管我未能在标准中找到相关部分),所以这是否意味着目标的新容量的值将与源的旧容量完全相同?我可以期望v = vector<T>{};vector<T>{}.swap(v); 具有相同的效果吗?

我想答案隐藏在标准中的某个地方,但我只是没能找到它们。 (如果 C++11 和 C++03 的情况不同,我想知道两者的各种要求。)

PS:对于上述问题的任何答案,std::string 是否相同(仅在 C++11 中表示连续存储且没有 COW,C++03 字符串不在雷达范围内)?

【问题讨论】:

  • 我在 C++11 标准中看不到关于这一点的明确声明,但我认为容量不会缩小 - 在其他地方,向量优先考虑最小化重新分配和元素复制而不是存储.如果您想要控制,您可以致电reserve()shrink_to_fit(),尽管后者没有义务实际执行任何操作。如果在复制分配期间容量必须增加,恕我直言,将固定比例超过新大小是最有意义的。您可以轻松地在几个编译器上对此进行测试 - 不同的行为会反驳标准要求(可能!)。
  • 我认为除了capacity() >= size() 的明显事实之外,该标准不保证分配后的容量。这对于允许 (a) 将源元素复制到目标现有分配中的实现,保持容量不变,以及 (b) 将元素复制并交换到新分配中以提供强大的异常保证的实现都是必要的。跨度>
  • @LightnessRacesinOrbit:感谢重定向,但是,该问题及其答案并未提及复制/移动分配对存储分配和迭代器有效性的影响。

标签: c++ string c++11 vector language-lawyer


【解决方案1】:

回答你的 PS: 至少对于std::string,您不能假设字符串的处理类似于字符向量的处理。

COW (Copy-on-Write) 不是强制实现字符串的,并不是所有的库实现都这样做。

【讨论】:

  • "COW (Copy-on-Write) 不是强制性的.." 我的理解是,在 C++11 中,强制使用 COW。 (更准确地说,迭代器现在需要在某些写入操作下保持有效)
  • 在C++03中,字符串和向量在存储方面有很大的不同。但是在 C++11 中,字符串的存储要求是连续的,COW 实现也是不可能的。所以我问向量的问题,我也想问 C++11 字符串。 C++03 字符串不在我的雷达范围内。
【解决方案2】:
std::vector<T,A>( std::vector<T,A>&& )

这保证是恒定时间(N3797 Table 99 X u(rv))。

我自己不知道如何在恒定时间内移动任意大小的向量而不只是将指针移动到缓冲区。如果这(不可能)是真的,那么构造的向量必须有一个至少与源缓冲区一样大的缓冲区。标准中没有规定vectors 需要高效,但是:右侧vector 的容量可以减少到大于或等于其size 的任何值:后置条件只是元素是一样的。从理论上讲,如果右侧vector 具有编译器出于任何原因(例如月相)选择公开的“隐藏容量”,它甚至可以是更大的容量。

在 N3797 标准中,任何容器上都没有 capacity 的上限。一个符合要求的实现可以使所有std::vectors 具有至少200 万个元素容量(除非allocator 失败——这可用于强制0 的容量),没有任何操作能够将该值降低到200 万以下. (shrink_to_fit 只是一个建议,std::vector&lt;T,A&gt;().swap(x) 可以创建一个 200 万容量的vector 并交换它。

由于以上大部分内容都属于否定形式,我只能说,在每次提及vectorallocatorallocatecapacity 时搜索标准。 capacity 在任何时候都不会用上限来描述。在空的std::vector 构造函数中对allocator 的额外调用没有任何限制(除了异常安全)(如果allocator 失败,您可能必须保持大小为0 和容量为0,但将该状态提取到任何不使用相同allocator 的东西都具有挑战性)。


对于复制分配和移动分配,复制分配不保证超出最基本的容量(即capacity() &gt;= size())。

对于移动分配,这取决于它的应用方式:

23.2.1 [container.requirements.general] /10

除非另有说明(明确地或通过根据其他函数定义函数),调用 容器成员函数或将容器作为参数传递给库函数不应无效 迭代器或更改该容器内的对象的值。

表 96 和表 99 中的a = rv(又名std::vector&lt;T,A&gt;&amp; operator=(std::vector&lt;T,A&gt;&amp;&amp;))案例是我们关心的问题。既没有提到 rv 中包含的值被破坏,也没有提到它们的迭代器无效。因此,在 23.2.1/10 下,迭代器不会失效。

但是,这并不要求移动缓冲区。缓冲区要么从 rhs 移动到 lhs,要么在 rhs vector 中保持不变。表 99 隐含地提到了这种情况,当它说 lhs 项目可以移动分配到时(这是 std::array 可以工作的唯一方式)。

由于std::array 只能移动元素,而std::vector 没有进一步保证移动缓冲区,因此不必移动缓冲区。然而,移动缓冲区似乎是一种合法的实现方式。


在实践中,std::vector&lt;T,A&gt;( std::vector&lt;T,A&gt; const&amp; ) 会将右侧的内容复制到左侧,并且在每个实现中我检查左侧的 capacity 等于 size 的结果vector。同样,std::vector&lt;T,A&gt;( ForwardIterator, ForwardIterator ) 将生成一个恰好适合其输入的 vector

请注意,std::vector&lt;T,A&gt;::operator=(std::vector&lt;T,A&gt;&amp;&amp;) 在复杂性上保持线性。

【讨论】:

  • 感谢您的回答。在我看来,任何可能导致向量重新分配/缩小的情况都是标准不会错过的重要内容,因此您可以将标准的任何摘录引导到允许“just fit “ 行为?再次感谢!
  • 感谢您花费时间和精力提供这些详细信息。但是,我从一开始就询问复制/移动分配,而不是复制/移动构造。后者对于我的问题要简单得多,因为目标对象中不存在 previous 状态。再次感谢。 (如果我没能理解你的真正意思,我很抱歉。)
【解决方案3】:

我在标准中找不到任何允许分配的内容 一个向量到一个有足够容量来减少容量的向量。 如果我在分配之前完成了reserve,我保证 只要迭代器不会因重新分配而失效 向量永远不会大于我保留的容量。

移动分配的问题很特殊。没有 似乎是允许它使迭代器无效的任何特殊情况 (除非源大于容量 目的地),但这种失败的目标 任务。我怀疑这是标准中的缺陷。

编辑:

对于它的价值,在表 96 中,对于 a = rv(其中 a 是 一个容器,rv 是一个相同类型的非常量右值 容器),标准给出了线性复杂度,并说 “a 的所有现有元素要么被移动分配到要么 被摧毁”。显然,标准 的意图是 容量不会减少;执行时间的好处 move 仅适用于单个元素,不适用于容器 本身。

【讨论】:

  • 非常感谢您的回答。我实际上在 comp.lang.c++ 上问了同样的问题,今天有一个人回答,他的回答正好相反:groups.google.com/forum/#!topic/comp.lang.c++/65-CXiHWRCc 我真的不知道谁错了,谁对了。该标准似乎支持您的判断(虽然不够模糊),但实施似乎站在他一边。
猜你喜欢
  • 2019-06-17
  • 1970-01-01
  • 2013-09-06
  • 1970-01-01
  • 2019-04-23
  • 1970-01-01
  • 2020-04-10
  • 1970-01-01
  • 2011-04-11
相关资源
最近更新 更多