向量分配是否使"储备"无效?

P45*_*ent 35 c++ language-lawyer

假设我写

std::vector<T> littleVector(1);
std::vector<T> bigVector;

bigVector.reserve(100);
bigVector = littleVector;
Run Code Online (Sandbox Code Playgroud)

标准是否bigVector仍然会保留100个元素?或者,如果我要使用push_back99个元素,我是否会体验内存重新分配?也许它甚至在STL实现之间有所不同.

这在此之前已经讨论过,但没有给出标准参考.

eca*_*mur 19

不幸的是,标准低于分配器感知序列容器分配的行为,并且严格地说是不一致的.

我们知道(从表28和23.2.1p7),如果allocator_traits<allocator_type>::propagate_on_container_copy_assignment::value是,true则在复制分配时替换分配器.另外,从表96和99,我们发现,对复杂性拷贝赋值的是线性,并且后置条件上操作a = ta == t,即,(表96),其distance(a.begin(), a.end()) == distance(t.begin(), t.end()) && equal(a.begin(), a.end(), t.begin()).从23.2.1p7开始,在复制赋值后,如果分配器传播,那么a.get_allocator() == t.get_allocator().

关于载体容量,23.3.6.3 [vector.capacity]具有:

5 - 备注:重新分配使引用序列中元素的所有引用,指针和迭代器无效.保证在调用之后发生的插入期间不会发生重新分配,reserve()直到插入将使向量的大小大于值的值为止capacity().

如果我们将图书馆DR341作为阅读标准的指南:

但是,在调用reserve()之后,23.3.6.3 [vector.capacity]第5段的措辞阻止了向量的容量减少.这使成语无效,因为防止了swap()减少了容量.[...]

通过在23.3.6.3中添加段落来解决DR341:

void swap(vector<T,Allocator>& x);
7 - 功效:交流的内容和capacity()*this与的x.
8 - 复杂性:恒定时间.

结论是,从图书馆委员会的角度来看,只有capacity()在23.3.6.3中提及的情况下才会修改.23.3.6.3未提及复制分配,因此不进行修改capacity().(移动分配具有相同的问题,特别是考虑到库DR2321的建议解决方案.)

显然,这是标准中的缺陷,因为传播不等分配器的复制分配必须导致重新分配,这与23.3.6.3p5相矛盾.

我们可以期待并希望这个缺陷得到解决,有利于:

  • 未减少capacity()非分配者修改的拷贝分配;
  • 未指定capacity()分配器修改副本分配;
  • capacity()分配器传播移动分配的非减少;
  • capacity()分配器传播移动分配的源容器.

但是,在目前的情况下,在澄清之前,你最好不要依赖任何特定的行为.幸运的是,有一个简单的解决方法,保证不会减少capacity():

bigVector.assign(littleVector.begin(), littleVector.end());
Run Code Online (Sandbox Code Playgroud)


Seb*_*edl 9

operator=对于标准容器的唯一要求是,之后,src == dst如表96(23.2,一般容器要求)中所规定的那样.此外,同一个表指定了以下含义operator ==:

distance(lhs.begin(), lhs.end()) == distance(rhs.begin(), rhs.end()) // same size
  && equal(lhs.begin(), lhs.end(), rhs.begin()) // element-wise equivalent
Run Code Online (Sandbox Code Playgroud)

请注意,这不包括任何方式的容量.该标准的任何其他部分也没有提到超出一般不变量的能力capacity() >= size().因此,未指定赋值后的容量值,并且只要保留分配器要求,容器就可以自由地以任何方式实现分配.


通常,您会发现实现的行为就是这样

  • 如果分配器比较相等且dst有足够的容量,它将保留其旧存储,
  • 否则它将为新元素分配足够的存储空间,并且
  • 在任何情况下都不会关心src的容量.

当然,移动任务是一个不同的故事.由于它通常是通过窃取源存储来实现的,因此也将采用容量.


utn*_*tim 6

这取决于分配器特性.

以下是http://en.cppreference.com/w/cpp/container/vector/operator%3D的摘录:

如果std :: allocator_traits :: propagate_on_container_copy_assignment()为true,则目标分配器将替换为源分配器的副本.如果目标和源分配器不比较相等,则使用目标(*this)分配器来释放内存,然后在复制元素之前使用其他分配器来分配它.(自C++ 11起)

基本上,如果分配器不兼容(如果它们不能释放彼此的内存),则使用新分配器重新分配内存.

它不应该在向量实现之间,而是在分配器实现之间(这是有意义的).

  • 虽然这是正确的,但它并不回答当分配器相同时是否也发生解除分配,或者不在容器副本分配上传播. (2认同)