从同一向量push_back元素是否安全

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

本文关键字:back 元素 是否 安全 push 向量      更新时间:2023-10-16
vector<int> v;
v.push_back(1);
v.push_back(v[0]);

如果第二个push_back导致重新分配,则对向量中第一个整数的引用将不再有效。所以这不安全吗?

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

这样安全吗?

看起来 http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-closed.html#526 解决了这个问题(或与之非常相似的东西)作为标准中的潜在缺陷:

1) 常量引用的参数可以在执行过程中更改 的功能

例子:

给定 std::vector v:

v.insert(v.begin(), v[2]);

v[2] 可以通过移动矢量元素来改变

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

向量::插入(迭代,值)需要工作,因为标准 不允许它不起作用。

是的,它是安全的,标准库实现会跳过箍来做到这一点。

我相信实施者以某种方式将这一要求追溯到 23.2/11,但我无法弄清楚如何,我也找不到更具体的东西。我能找到的最好的是这篇文章:

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

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

该标准甚至保证您的第一个示例是安全的。引用 C++11

[序列.reqmts]

3 在表 100 和 101 中 ... X表示序列容器类,a表示包含类型为 T 的元素的 X 的值,... t表示左值或常量右值 X::value_type

16 表 101 ...

表达式 a.push_back(t) 返回类型 void 操作语义 附加 t. 的副本 要求: TCopyInsertable X 中。 容器 basic_stringdequelistvector

因此,即使它不是微不足道的,实现也必须保证在执行push_back时不会使引用无效。

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

但至少在Visual Studio 2010中它似乎是安全的。它的 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
                    ...
        }
    }
这不是

标准的保证,但作为另一个数据点,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.
}

第一个版本绝对不安全:

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

来自第 17.6.5.9 节


请注意,这是关于数据竞赛的部分,人们通常会将其与线程结合使用......但实际定义涉及"发生在之前"的关系,我在这里没有看到push_back的多重副作用之间的任何排序关系,即引用无效似乎没有被定义为关于复制构造新尾元素的有序。

它是完全安全的。

在您的第二个示例中,您有

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

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

Vector负责这些东西,而不是你。

两者都是安全的,因为push_back会复制值,而不是引用。如果你存储指针,就向量而言,这仍然是安全的,但只要知道你的向量有两个元素指向相同的数据。

部分 23.2.1 一般容器要求

16
  • a.push_back(t) 附加 t 的副本。 要求:T 应可复制插入到 X 中。
  • a.push_back(rv) 追加 rv 的副本。 要求:T 应可移动到 X 中。

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

来自 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()那么它保证工作,否则保证它是未定义的行为。