C++:指针的矢量在push_back()之后丢失引用
C++: vector of pointer loses the reference after push_back()
在我的代码中,有一个Node对象的全局向量和一个Node指针的局部向量:
#include<cstdio>
#include<cstdlib>
#include<vector>
using namespace std;
class Node {
int n;
public:
Node(int i) : n(i);
int getN() { return n; }
};
vector<Node> v;
int main() {
vector<Node*> p;
v.push_back(Node(1));
p.push_back(&v[0]);
printf("first node id : %dn", (*p[0]).getN());
return 0;
}
我在全局向量&在局部向量中插入该对象的指针。我上面代码的输出是:
first node id : 1
然而,如果我将我的主要功能更改为:
int main()
{
vector<Node*> p;
v.push_back(Node(1));
p.push_back(&v[0]);
v.push_back(Node(2));
p.push_back(&v[1]);
printf("first node id : %dn", (*p[0]).getN());
return 0;
}
代码打印一个垃圾值:
first node id : 32390176
我想不通这个问题。插入后,vector
数据结构是否会更改每个对象的引用?我该怎么解决这个问题?
"插入后向量会更改引用吗?"
可能,是的。当您添加/push_back()
附加元素时,std::vector
可能会重新分配其(堆)存储,从而使所有指针无效:
迭代程序[read:Pointer]无效
(用于操作)
push_back
、emplace_back
。。。如果向量改变了容量,则所有的迭代器[即所有迭代器都无效]。如果不是,则仅end()
。
"我该怎么解决这个问题?"
如果矢量的容量没有因插入而改变,则上述无效规则不适用,因为矢量不会不必要地重新分配存储。因此,如果您在示例中将向量的容量预设为2(例如,使用v.reserve(2)
),则指针将保持有效。如果你事先不知道大小,但你可以推迟第二个向量的构建(用指针),你不必保留,你只需要在插入最后一个元素后得到大小。
然而,上述方法是高度未被注意的。如果你要使你的向量恒定——至少在你将构造和使用第二个向量的函数的范围内——你将有一个强有力的不重新分配的保证。或者,如果你可以提前确定大小,你可以使用std::array
,并且使用指向该容器存储的指针会更合适:
迭代程序无效
通常,数组的迭代器在数组的整个生命周期内都不会失效。
您还可以考虑将索引存储到向量中(尽管在向量中,向量也可能会收缩,使索引无效,或者您可能会在中间插入元素等)。
无论如何,我怀疑你可能真的不想这样做,也就是说,对于一个可以用完全不同的方法处理的问题,这似乎不是一个很好的解决方案。
PS-如果矢量有一个自定义分配器,那么我所写的一切可能都无关紧要
是的,如果必须重新分配,向量上的push_back()
会使该向量中元素的所有引用(和指针)无效。有多种方法可以解决这个问题。如果您知道向量将具有特定数量的节点,则可以使用reserve()
。在您的示例中,您可以保留两个元素:
int main()
{
v.reserve(2);
.
.
.
}
这将确保向量已经预先分配了足够的存储空间,因此不需要重新分配。
如果你不提前知道尺寸,那么你就必须改变你的方法。您可以使用std::deque
而不是std::vector
,因为使用push_back()
时std::deque
不会使引用无效。您可以存储索引而不是指针。或者,在制作指针之前,您可能需要将所有节点推入向量中。
int main()
{
v.push_back(Node(1));
v.push_back(Node(2));
vector<Node*> p;
p.push_back(&v[0]);
p.push_back(&v[1]);
printf("first node id : %dn", (*p[0]).getN());
return 0;
}
您所做的是向量p
的未定义行为,因为向量v
可以更改其对象的存储位置。
std::vector
的内存是连续的,因此它可能在若干个push_backs
之后,必须分配一个新的块内存,并将其内容复制到新的块。这将使所有指向旧内存位置的指针失效。
您偶然发现了C++中一个著名的"黑暗角落":伟大的"迭代器无效"
迭代程序失效规则
特别是,你遇到了这个现实:
vector:插入点之前的所有迭代器和引用不受影响,除非新容器大小大于以前的容器大小容量(在这种情况下,所有迭代器和引用都无效)[23.2.4.3/1]
(重点是我的)
现在,谈谈你的问题。您可以确保向量永远不会重新分配。或者,您可以使用不存在此问题的其他容器。根据您的需要,所有容器类型都有折衷方案。仔细检查另一个问题,并做出明智的决定。
- 为什么在popback()操作之后,它仍然打印完整的矢量
- 在类定义之后定义一个私有方法
- 在循环C++中指定字符串之后,不会打印该字符串
- C++宏忽略之后的内容
- 要与"if constexpr"一起使用的编译时消息(在预处理器之后)
- strncpy之后的char数组的错误行为
- 计算十进制 c++ 之后的数字
- "x += x--"之后的 x 是什么?
- 类的前向声明之后的类成员函数定义,在类声明之前
- 为什么将双精度转换为 int 似乎在第 16 位数字之后将其四舍五入?
- 推导 std::vector::back() 的返回类型
- execlp() 在 fork() 之后无法正常工作
- vector.back() 和 vector[vector.size() - 1] 之间的区别?
- 我认为我的代码很好,但它在 cin a 之后停止并且没有进一步?
- 如何在MISRA C++之后实施CRTP
- 在 OpenCV 的 namedWindow 之前或之后初始化 Tesseract
- 检测到堆损坏:在正常块 c++ 动态 2D 数组之后
- C++ 如果在 if 为 true 之后运行,为什么还会这样做
- 在 fork() 之后,我在我的程序中不断得到相同的 pid
- OpenSSL C API:如何在程序exec()之后恢复TLS连接?