在我的代码中,a有一个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 : %d\n", (*p[0]).getN());
return 0;
}
我将一个节点对象插入到全局向量&amp;将该对象的指针插入本地向量中。我上面代码的输出是:
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
数据结构在插入后是否更改了每个对象的引用?
我该如何解决这个问题?
答案 0 :(得分:17)
可能,是的。当您添加/ push_back()
个附加元素时,std::vector
可能会重新分配其(堆)存储空间,从而使所有指针无效:
迭代器[读取:指针]失效
(对于操作)
push_back
,emplace_back
...如果向量改变了容量,那么所有这些[即所有迭代器都无效]。如果不是,则仅end()
。
如果矢量的容量因插入而没有改变,则上述失效规则不适用 - 因为矢量不会不必要地重新分配存储。因此,如果您在示例中预先将矢量的容量设置为2(例如,使用v.reserve(2)
),则指针将保持有效。如果您事先不知道尺寸,但可以延迟构建第二个矢量(使用指针),您不必保留,插入后您只需要大小最后一个元素。
然而,上述方法非常不受推荐。如果你要使vector 常量 - 至少在你将构造和使用第二个向量的函数范围内 - 你将有一个强大的非重新分配保证。或者,如果您可以提前确定大小,则可以使用std::array
,并且将指针用于该容器的存储更合适:
迭代器失效
通常,数组的迭代器在数组的整个生命周期中永远不会失效。
你也可以考虑将 indices 存储到你的向量中(尽管那里的向量可能会缩小,索引无效,或者你可能会在中间插入元素等)。
无论如何,我怀疑你可能实际上并不想做任何这样的事情,即它似乎是一个不太好的解决方案,可以通过一个完全不同的方法处理问题。
PS - 如果向量有custom allocator,那么我写的所有内容都可能无关紧要。
答案 1 :(得分:4)
您正在做的是向量p
的未定义行为,因为向量v
可以更改其对象的存储位置。
std::vector
的内存是连续的,因此在一些push_backs
之后,它可能必须分配一个新的块内存并将其内容复制到新块。这将使所有指向旧内存位置的指针无效。
答案 2 :(得分:3)
是的,如果必须重新分配,向量上的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;
}
答案 3 :(得分:1)
你偶然发现了一个众所周知的黑暗角落&#34; C ++:伟大的&#34;迭代器失效。&#34;绝对熟悉自己:
特别是,你正在打击这个现实:
vector :插入点之前的所有迭代器和引用 不受影响, 除非新容器大小大于之前的容器大小 容量(在这种情况下,所有迭代器和引用都无效) [23.2.4.3/1]
(重点是我的)
现在,关于你的问题。您可以确保向量永远不会重新分配。或者,您可以使用不存在此问题的其他容器。根据您的需要,所有容器类型都有妥协。彻底查看另一个问题并做出明智的决定。