通过引用同一向量的元素插入到向量中
Inserting into vector by reference to element of same vector
我想知道是否有更有经验的人能够澄清这是否是在向量上执行的错误操作:
std::vector<int> v{1, 2, 3, 4, 5};
v.insert(v.begin() + 1, v[0]);
我问的原因是因为要插入的元素是对向量中第 0 个元素的引用。如果插入强制调整矢量大小(因为其容量已满),则对v[0]
的引用将失效,并且代码可能会插入不正确的值。下面是一些可能演示的伪代码:
template <typename T>
void vector_insert_method(Iterator pos, const T& value) {
if capacity full:
reallocate array and copy over element
// if value was reference to elem in this vector,
// that reference might be invalidated
insert element
++size
}
此问题在并发系统上可能更现实。
一个类似且相关的问题是,如果您尝试插入您尝试插入的位置之后的元素,会发生什么。例如,执行类似v.insert(v.begin(), v[2])
的操作,因为标准指出对插入点之后元素的引用无效。这能保证有效吗?
最初的问题问...
[..] 这是否是在向量上执行的错误操作:
v.insert(v.begin(), v[0]); // ^^^ no + 1 here!
可能是的,这可能是未定义的行为。引用N3337("几乎C++11"AFAIK):
[..]如果未发生重新分配,则插入点之前的所有迭代器和引用仍然有效。[..]
§23.3.6.5/1
虽然这不是阅读标准时习惯的明确措辞,但我将其解释为:迭代器以及对插入点及其之后的引用无效。
因此,对v[0]
的引用因调用insert
而无效。即使没有发生重新分配,insert
也必须将所有元素转移到更高的指数(因此v[0]
重新定位为v[1]
)。
假设元素类型不是微不足道的可破坏的,那么无论重新定位如何完成(通过移动或复制),在从v[0]
分配/构造v[1]
之后,需要调用v[0]
的析构函数(及其生命周期结束)才能将新对象(您要插入的对象)放置在v[0]
的内存位置。因此,在这里,您的引用变成了悬而未决的引用,当之后直接使用它来构造新v[0]
时,会导致未定义的行为。不,据我所知,这个问题可以通过不构造新对象而是将新对象分配给"旧"对象来规避std::vector::insert()
。我不确定std::vector
是否要求以这种方式行事,尽管它的元素类型必须CopyInsertable
这暗示可能是这种情况。
更新:我已经使用了另一个答案中提供的代码,添加了一些打印调试。这并没有显示我所期望的(破坏一个元素然后访问它),但仍然表明标准库实现(此处由 ideone 使用)不符合标准:即使容量足够大,它也会使对插入点之前的元素引用无效。这样它就可以规避上述问题(但打破了标准......所以)。
(*):人们可能会争论何时发生无效。这个问题的最佳答案是 IMO 在调用insert
函数之前引用是有效的,并且在从该函数返回后(肯定)无效。无效的确切点未指定。
编辑后的问题提出了同样的问题,但进行了极其重要的修改:
v.insert(v.begin() + 1, v[0]); // ^^^^^
现在这是一个完全不同的情况。对v[0]
的引用不一定会因调用insert
而无效,因为插入点在"后面"v[0]
。可能出现的唯一问题是std::vector
必须重新分配其内部缓冲区,但在这种情况下,以下操作序列应保证正确的行为:
- 分配更大容量的新内存
- 在新分配的内存区域中的正确索引处构造新元素。
- 将元素从旧(太小)内存复制/移动到新分配的内存区域。
- 释放旧记忆。
好的,经过很多环顾四周,标准库似乎有义务完成这项工作。这个想法是,由于标准没有明确说这不应该工作,所以它必须工作。
Andrew Koenig 在这里写了一些关于它的文章,并提出了一个解决方案,其中分配新内存,移动元素,然后才解除分配旧内存。
这里和这里还有其他讨论(#526)。
至于第二种情况,似乎标准库(MacOS 上的 clang )也考虑了您尝试在向量中插入对元素的引用的情况,该元素位于您尝试插入的点之后。为了测试这一点,我为名为 Integer 的整数编写了一个包装类,它的行为与 int 完全相同,只是析构函数将其内部值设置为 -5。因此,如果 std::vector 插入没有考虑到这种情况,我们应该看到插入的 -5 而不是实际值。
#include <vector>
#include <iostream>
using std::cout; using std::endl;
using std::ostream; using std::vector;
class Integer {
public:
Integer() {
x = -1; // default value
}
~Integer() {
x = -5; // destructed value. Not 0 so we can clearly see it
}
Integer(int r) {
x = r;
}
Integer(const Integer& other) {
x = other.x;
}
Integer operator=(const Integer& other) {
x = other.x;
return *this;
}
operator int() const{
return x;
}
friend ostream& operator<<(ostream& os, const Integer& thing) {
os << thing.x;
return os;
}
private:
int x;
};
ostream& operator<<(ostream& os, const vector<Integer> &v) {
std::copy(v.begin(), v.end(), std::ostream_iterator<Integer>(os, ", "));
return os;
}
int main() {
std::vector<Integer> ret {18, 7, 4, 24,11};
cout << "Before: " << ret << endl;
ret.insert(ret.begin(), ret[1]);
cout << "After: " << ret << endl;
return 0;
}
这为我们提供了正确的输出:
Before: 18, 7, 4, 24, 11,
After: 7, 18, 7, 4, 24, 11,
- 向量元素的引用地址与它所指向的向量元素的地址不同.为什么
- 在 C++ 中输出枚举类类型的向量元素
- 用向量的向量元素初始化向量的空向量
- 为什么不允许使用可变长度数组作为向量元素?
- 在函数中传递向量元素
- 使用 Rcpp 加速替换迭代算法中的列表和向量元素是否合法?
- 返回向量元素的 l 值的正确方法是什么?
- 当向量增长时,指向向量元素的C++指针是否无效
- 如何在C++中从一个向量元素减去std::unorderede_map向量元素并更新它
- C++:向量元素赋值导致访问冲突
- 将向量元素与字符串元素进行比较,而不初始化向量
- 从2D矢量中找出最小尺寸的向量元素的更好方法
- 当我尝试将一个向量元素的值分配给另一个向量元素时,为什么我的应用程序会崩溃
- 使用递归C++向量元素的总和
- 如何查找为数组中的最低值编制索引的向量元素
- 限制指针访问STL ::向量元素
- 访问索引指向向量元素时的seg故障 - 做错了什么
- 如何使用指针访问向量元素的各个元素
- 向量元素数据损坏了Find()操作
- 将字符值分配给向量元素.C