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

时间:2013-09-13 14:27:30

标签: c++ vector reference language-lawyer push-back

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]);

这样可以安全吗?

9 个答案:

答案 0 :(得分:31)

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

  

1)const引用所采用的参数可以在执行期间更改   功能

     

示例:

     

鉴于std :: vector v:

     

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

     

v [2]可以通过移动向量元素

来改变

提议的决议是这不是缺陷:

  

vector :: insert(iter,value)因标准而需要工作   不允许它不工作。

答案 1 :(得分:21)

是的,它是安全的,并且标准库实现跳过了箍来实现它。

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

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

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

答案 2 :(得分:13)

该标准保证您的第一个例子即使是安全的。引用C ++ 11

[sequence.reqmts]

  

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

的左值或常量值      

16表101 ......

     

表达式 a.push_back(t) 返回类型 void 操作语义附加t. 要求: T CopyInsertableX容器 basic_stringdequelistvector

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

答案 3 :(得分:7)

第一个示例是安全的并不明显,因为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
                    ...
        }
    }

答案 4 :(得分:3)

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

当需要重新分配内存时,

libc++'s 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.
}

答案 5 :(得分:1)

第一个版本绝对不安全:

  

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

来自第17.6.5.9节


请注意,这是关于数据竞赛的部分,人们通常认为它与线程结合...但实际定义涉及“之前发生”关系,而且我看不到多边之间的任何排序关系 - push_back在这里发挥作用,即参考无效似乎没有被定义为关于复制构造新尾元素的有序。

答案 6 :(得分:0)

完全安全。

在你的第二个例子中,你有

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

这是不需要的,因为如果向量超出其大小,则意味着reserve

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

答案 7 :(得分:-1)

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

第23.2.1节“一般容器要求”

16
  • a.push_back(t)附加t的副本。要求:T应为CopyInsertable in X。
  • a.push_back(rv)附加rv的副本。要求:T应为MoveInsertable进入X。

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

答案 8 :(得分:-2)

从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()然后它保证可以工作,否则它将保证是未定义的行为。