C ++:指针向量在push_back()

时间:2016-01-10 16:56:55

标签: c++ pointers vector

在我的代码中,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数据结构在插入后是否更改了每个对象的引用? 我该如何解决这个问题?

4 个答案:

答案 0 :(得分:17)

&#34;插入后向量是否会更改引用?&#34;

可能,是的。当您添加/ push_back()个附加元素时,std::vector可能会重新分配其(堆)存储空间,从而使所有指针无效:

  

迭代器[读取:指针]失效

     

(对于操作)push_backemplace_back ...如果向量改变了容量,那么所有这些[即所有迭代器都无效]。如果不是,则仅end()

&#34;我该如何解决这个问题?&#34;

如果矢量的容量因插入而没有改变,则上述失效规则不适用 - 因为矢量不会不必要地重新分配存储。因此,如果您在示例中预先将矢量的容量设置为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;绝对熟悉自己:

Iterator invalidation rules

特别是,你正在打击这个现实:

  

vector :插入点之前的所有迭代器和引用   不受影响, 除非新容器大小大于之前的容器大小   容量(在这种情况下,所有迭代器和引用都无效)   [23.2.4.3/1]

(重点是我的)

现在,关于你的问题。您可以确保向量永远不会重新分配。或者,您可以使用不存在此问题的其他容器。根据您的需要,所有容器类型都有妥协。彻底查看另一个问题并做出明智的决定。