在我的代码a中,有节点对象的全局向量和节点指针的局部向量:
#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 : %d\n", (*p[0]).getN());
return 0;
}
I将节点对象插入全局向量,并将该对象的指针插入局部向量。我上述代码的输出是:
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 : %d\n", (*p[0]).getN());
return 0;
}
代码打印一个垃圾值:
first node id : 32390176
我无法弄清楚这个问题。
vector
数据结构是否会在插入后会更改每个对象的引用?
我该如何解决?
是的,是的。当您向其添加其他元素时,可能会重新分配其(堆)存储 - 从而使所有指针无效:
术语[读:指针]无效(用于操作)
std::vector
,push_back
...如果向量更改了容量,则所有这些都[即所有迭代器都是无效的]。如果没有,只有
。 “我该如何修复?” 如果向量的容量由于插入而不会变化,则上述无效规则不会适用 - 因为向量不会不必要地重新分配存储。因此,如果您将矢量的能力预先设置为示例2(例如用emplace_back
end()
constant
-至少在构造和使用第二个向量的函数范围内 - 您将有很大的保证非算法。另外,如果您可以提前确定尺寸,则可以使用
v.reserve(2)
,并且将指针在该容器的存储中使用更合适: siterator无效
作为规则,在整个数组的整个生命周期中,迭代器的迭代器永远不会无效。您还可能考虑将Indices
存储到您的向量中(尽管也可能在其中缩小,使索引无效,或者您可能会在中间插入元素等)。yyes,在向量上的a,我怀疑您实际上可能不想做任何事情,即,这似乎是一个不太好的解决方案,可以完全以不同的方法处理。 PS-如果向量具有
custom分配器,那么我写的所有内容可能都是无关紧要的。
std::array
将所有参考文献(和指针)无效,如果必须重新分配该矢量中的元素。 有多种方法可以解决这个问题。 如果您知道您的向量将具有特定数量的节点,则可以使用push_back()
reserve()
这将确保向量已预先列出足够的存储,因此它不需要重新分配。
如果您不提前知道尺寸,那么您必须更改有关方法的内容。 您可能会使用int main()
{
v.reserve(2);
.
.
.
}
而不是
std::deque
std::vector
时
std::deque
没有无效的引用。 您可以存储索引而不是指针。 或者,您可能需要在制作指针之前将所有节点推入矢量。
push_back()
您正在做的是向量的未定义行为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 : %d\n", (*p[0]).getN());
return 0;
}
p
,因为向量可以在存储其对象的位置更改。A
v
的内存是连续的,因此,在许多std::vector
之后,它可能必须分配新的块内存并将其内容复制到新块中。 这将使所有指向旧内存位置的指针无效。
您偶然发现了C ++的伟大“黑暗角落”之一:伟大的“迭代无效”。绝对熟悉这一点:
术语无效规则
矢:插入点之前的所有迭代器和参考 不受影响,没有新的容器尺寸大于以前 容量(在这种情况下,所有迭代器和参考都无效)[23.2.4.3/1]
(重点是我的)
现在,关于您的问题。您可以确保向量永远不会重新分配。或者,您可以使用没有此问题的其他容器。所有容器类型之间都有妥协,具体取决于您的需求。彻底查看另一个问题并做出明智的决定。