2014-06-17 43 views
35

假设我写矢量分配是否使`reserve`无效?

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

bigVector.reserve(100); 
bigVector = littleVector; 

是否标准说bigVector仍然有100个元素保留?或者如果我想要记忆重新分配,我会不会觉得?也许它在STL实现中有所不同。

这是之前讨论的here,但没有给出标准参考。

+21

@davidhigh:是的,它会回答这个问题对我特别的编译器和STL实现,但我也可以回答这样的问题“是什么'I =我以类似的方式“做”。 –

+0

非常好的问题,+1。在标准中我找不到任何直接的东西;尽管如此,分配器需求的黑暗魔力可能会产生一些后果。 – Angew

+0

我知道如果源的分配器不同于目标,它会转储整个目标缓冲区。我不能评论它是否尊重当前的保留,如果他们在不参考标准的情况下使用* same * allocator。 (很好的问题,顺便说一句)。 – WhozCraig

回答

18

不幸的是,该标准在分配器感知的序列容器分配上没有详细说明行为,实际上严格来说是不一致的。

我们知道(从表28和从23.2.1p7)如果allocator_traits<allocator_type>::propagate_on_container_copy_assignment::valuetrue那么分配器在复制分配时被替换。另外,从表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()的值。

如果我们取library DR341为指导,以读取所述标准:

然而,23.3.6.3措辞[vector.capacity]段落5防止一个向量的容量减小,承接调用reserve()。这使成语无效,因为swap()因此被阻止减小容量。 [...]

DR341通过添加段落分解为23.3.6.3:

void swap(vector<T,Allocator>& x);
7 - 影响:交流的内容,并与中x*thiscapacity()
8 - 复杂性:恒定时间。

结论是,从图书馆委员会的角度来看,如果在23.3.6.3中提到,操作只修改capacity()。复制分配未在23.3.6.3中提及,因此不会修改capacity()。 (移动分配有相同的问题,特别是考虑到所提出的决议Library DR2321

显然,这是在标准的缺陷,如拷贝赋值传播不平等分配器必须结果重新分配,矛盾23.3.6.3p5。

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

  • 非还原非分配器,修改拷贝赋值capacity();
  • 未指定capacity()关于分配器修改副本分配;
  • 未减少capacity()关于非分配器传播移动分配;
  • 关于分配器传播移动分配的源容器capacity()

但是,在目前的情况下,直到明确这一点,您最好不要依赖任何特定的行为。幸运的是,这是保证不降低capacity()一个简单的解决方法:

bigVector.assign(littleVector.begin(), littleVector.end()); 
+0

容量备注限制了插入的行为。可以说容器分配不属于插入类别。 –

+0

@BenVoigt序列容器复制分配被指定为需要'CopyAssignable'。看起来非常清楚,如果初始容量不足,非分配器修改副本分配必须导致重新分配。 – ecatmur

+0

我同意。但我不知道一个僧侣是否会把这个操作称为“插入”并应用上述规则。 –

6

这取决于分配器特征。

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

如果性病:: allocator_traits :: propagate_on_container_copy_assignment()为真,则目标分配器由源分配的副本代替。如果目标分配器和源分配器之间的比较不相等,则使用目标(* this)分配器来释放内存,然后使用其他分配器在复制元素之前分配它(自C++ 11以来)

基本上,存储器被重新分配与所述新的分配器,如果分配器是不相容(如果他们不能解除分配每个-其它的存储器。

它不应该矢量实现之间关系,但是分配器实现之间(这使得有义)

+0

您是否对该声明有规范性参考(即从标准)? – ecatmur

+2

尽管这是正确的,但它并没有回答分配器是否相同时也会发生释放,或者不在容器复制分配上传播。 –

+0

@ecatmur:23.2.1p7讨论了分配器替换。分配器更换的时刻是允许容器最后一次使用旧分配器的时间;所有未来的操作都是根据替换分配器来定义的。因此,此时它必须释放其旧缓冲区。 –

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 

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


在一般情况下,你会发现,实现的行为,使得

  • 如果分配器比较平等,DST有足够的能力,将保留其旧存储,
  • 否则只会分配足够的存储空间用于新元素,并且
  • 在任何情况下都不会在乎src的容量是多少。

当然,移动分配是一个不同的故事。由于通常通过窃取源存储来实施,因此容量也会被采用。