C++:指针的矢量在push_back()之后丢失引用

C++: vector of pointer loses the reference after push_back()

本文关键字:back 之后 引用 push 指针 C++      更新时间:2023-10-16

在我的代码中,有一个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_backemplace_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]

(重点是我的)

现在,谈谈你的问题。您可以确保向量永远不会重新分配。或者,您可以使用不存在此问题的其他容器。根据您的需要,所有容器类型都有折衷方案。仔细检查另一个问题,并做出明智的决定。