Is it safe to push_back an element from the same vector?

vector<int> v;
v.push_back(1);
v.push_back(v[0]);

If the second push_back causes a reallocation, the reference to the first integer in the vector will no longer be valid. So this isn't safe?

vector<int> v;
v.push_back(1);
v.reserve(v.size() + 1);
v.push_back(v[0]);

This makes it safe?

7889 次浏览

是的,它是安全的,并且标准库实现会不遗余力地实现它。

我相信实现者可以通过某种方式将这个需求追溯到23.2/11,但是我不知道是如何做到的,而且我也找不到更具体的东西。我能找到的最好的是这篇文章:

Http://www.drdobbs.com/cpp/copying-container-elements-from-the-c-li/240155771

检查 libc + + 和 libstdc + + 的实现表明它们也是安全的。

两者都是安全的,因为 push _ back 将复制值,而不是引用。如果您存储的是指针,那么对于向量来说仍然是安全的,但是要知道您的向量中有两个元素指向相同的数据。

第23.2.1节一般货柜规定

16
  • A. push _ back (t)附加一个 t 的副本要求: T 应该是 CopyInsertable 插入到 X 中。
  • A push _ back (rv)附加一个 rv 的副本。要求: T 应该是 MoveInsertable 到 X。

因此,push _ back 的实现必须确保插入 一份 v[0]。通过反例,假设一个实现在复制之前会重新分配,那么它肯定不会附加 v[0]的副本,因此违反了规范。

第一个示例是否安全并不明显,因为 push_back的最简单实现是首先重新分配向量(如果需要) ,然后复制引用。

但至少在 VisualStudio2010中似乎是安全的。它的 push_back实现在向量中推回一个元素时对这种情况进行特殊处理。 守则的结构如下:

void push_back(const _Ty& _Val)
{   // insert element at end
if (_Inside(_STD addressof(_Val)))
{   // push back an element
...
}
else
{   // push back a non-element
...
}
}

23.3.6.5/1: Causes reallocation if the new size is greater than the old capacity. If no reallocation happens, all the iterators and references before the insertion point remain valid.

因为我们在最后插入,没有引用会失效 如果向量没有调整大小。所以如果矢量是 capacity() > size()那么它肯定是有效的否则它肯定是有未定义行为的。

这个标准甚至可以保证你的第一个例子是安全的

[ Sequence.reqmts ]

在表100和表101中... ... X表示 Vector 类,a表示包含类型 T的元素的 X值,... ... t表示 X::value_type的左值或常数 rvalue

16号桌。

表达 a.push_back(t) void0 void void1附加一个 t. void2 T的拷贝应该是 CopyInsertableXvoid3 basic_stringdequelistvector

因此,即使它并非完全无关紧要,实现也必须保证它在执行 push_back时不会使引用失效。

第一个版本绝对不安全:

通过调用标准库容器或字符串成员函数获得的迭代器上的操作可以访问底层容器,但不能修改它。[注: 特别是,使迭代器无效的容器操作与与该容器关联的迭代器上的操作冲突。ー尾注]

第17.6.5.9节


请注意,这是关于数据竞争的部分,人们通常会把它与线程联系起来... ... 但实际的定义涉及到“发生在”之前的关系,我在这里没有看到 push_back的多个副作用之间的任何排序关系,也就是说,引用失效似乎不被定义为关于复制构造新的尾部元素的排序。

这并不是标准的保证,但是作为另一个数据点,v.push_back(v[0])对于 LLVM 的 libc + + 是安全的。

Libc + + 的 std::vector::push_back 在需要重新分配内存时调用 __push_back_slow_path:

void __push_back_slow_path(_Up& __x) {
allocator_type& __a = this->__alloc();
__split_buffer<value_type, allocator_type&> __v(__recommend(size() + 1),
size(),
__a);
// Note that we construct a copy of __x before deallocating
// the existing storage or moving existing elements.
__alloc_traits::construct(__a,
_VSTD::__to_raw_pointer(__v.__end_),
_VSTD::forward<_Up>(__x));
__v.__end_++;
// Moving existing elements happens here:
__swap_out_circular_buffer(__v);
// When __v goes out of scope, __x will be invalid.
}

看起来 http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-closed.html#526把这个问题(或者类似的问题)作为标准中的一个潜在缺陷来解决:

1)常量引用获取的参数在执行过程中可以更改 函数

例子:

给定性传播疾病: : 矢量 v:

Insert (v.start () ,v [2]) ;

V [2]可以通过向量的运动元素来改变

拟议的决议是,这不是一个缺陷:

要工作,必须插入(iter,value) ,因为标准 不允许它不工作。

绝对安全。

在第二个例子中

v.reserve(v.size() + 1);

这是不需要的,因为如果矢量超出它的大小,它将意味着 reserve

是 Vector 干的,不是你。