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

这使它安全吗?

4

9 回答 9

34

看起来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) 需要工作,因为标准不允许它不工作。

于 2013-09-13T20:23:36.563 回答
22

是的,它是安全的,标准库的实现跳过了一些环节来做到这一点。

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

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

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

于 2013-09-13T14:28:45.340 回答
13

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

[序列.reqmts]

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

16 表 101 ...

表达式 a.push_back(t) 返回类型 void 操作语义t. Requires: 的副本附加到中。 容器, , ,TCopyInsertableX basic_stringdequelistvector

因此,即使它不完全是微不足道的,实现也必须保证它不会在执行push_back.

于 2013-09-13T14:58:09.167 回答
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
                    ...
        }
    }
于 2013-09-13T14:51:58.493 回答
3

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

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.
}
于 2013-09-13T17:10:56.040 回答
2

第一个版本绝对不安全:

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

来自第 17.6.5.9 节


push_back请注意,这是关于数据竞争的部分,人们通常将其与线程结合起来考虑……但实际定义涉及“发生在之前”的关系,我看不到in的多个副作用之间有任何排序关系在这里玩,即引用失效似乎没有被定义为相对于复制构造新尾元素的顺序。

于 2013-09-13T15:22:02.240 回答
0

这是完全安全的。

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

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

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

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

于 2013-09-19T15:25:36.063 回答
-1

两者都是安全的,因为 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]因此违反规范。

于 2013-09-13T14:45:36.663 回答
-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(),那么它保证可以工作,否则它保证是未定义的行为。

于 2013-09-13T14:54:39.337 回答