在向量中插入元素会损坏指向向量的指针吗?

时间:2010-08-15 18:05:34

标签: c++ pointers vector

在模拟逻辑门的程序中,我使用数组切换

node N[1000];

到向量

vector<node> N;

我的程序在使用向量之前确实工作正常,但现在它打印出错误的结果,所以我尝试了调试,我发现错误发生在这里:

node* Simulator::FindNode(string h)
{
    int i;
    for(i = 0; i < NNodes; i++)
    {
        if (N[i].getname() == h)
        {
            return &N[i];
        }
    }

    node n ;
    N.push_back(n);
    N[NNodes].setname(h);
    NNodes++;
    return &N[NNodes-1]; //why?because of NNodes++  
}

// ...

node* inp1;
node* inp2;
node* out;
string NodeName;

inp_file >> NodeName;
inp1 = FindNode(NodeName);
s1 = inp1;

inp_file >> NodeName;
inp2 = FindNode(NodeName); //inp1 is destroyed here 

inp_file >> NodeName;
out = FindNode(NodeName); //inp2 and inp1 are destroyed here 

第一次调用FindNode时,第一个指针inp1指向正确的位置&N[0]

第二次调用FindNode时,第一个指针inp1指向垃圾,第二个指针inp2指向正确的位置&N[1]

第3次调用FindNode时,第1和第2指针(inp1inp2)都指向垃圾!并且第3个指针指向正确的位置。

为什么会这样?
当我向它们插入项目时,矢量如何工作?我应该使用哪种指针指向矢量项目?

6 个答案:

答案 0 :(得分:9)

是的,它可以重新分配整个缓冲区,使所有指向旧位置的指针无效。

您可以通过预分配来限制此功能,但这实际上只是提升了性能。更好的方法是使用索引而不是原始指针。

答案 1 :(得分:6)

一些事情。

首先,据我所知NNodes只是跟踪大小。但是你有std::vector::size()。然后,您可以使用它来获取最后插入的元素,但您只需使用std::vector::back()return &N.back();

此参数也是按值传递的,当它应该由const-reference:const string& h传递时。这样可以避免不必要的副本,通常情况下,你应该通过const-reference而不是by-value传递东西。

这很糟糕:

node n;
N.push_back(n);
N[NNodes].setname(h);

node应该有一个构造函数,它接受const string&并在初始化期间设置名称。这样你就永远不会有一个没有名字的节点,如:

node n(h);
N.push_back(n);

或更简洁:

N.push_back(node(h));

好多了。

第二,是的,vector可以使指向元素的指针无效;即,每当需要增加矢量的容量时。如果可以,reserve()提前预留容量以避免重新分配。在你的情况下你不能,所以你可以走两条不同的路线。

第一条路线是a level of indirection。而不是直接指向事物,将其索引放入数组中。请注意,虽然它们的地址可能会更改,但它们在矢量中的位置却不会。您将Simulator::FindNode返回size_t,然后返回N.size() - 1。添加node& GetNode(size_t index)之类的成员,只需return N[index];(如果您愿意,可以进行错误检查)。现在,无论何时需要成员,都可以将该成员的索引传递给GetNode,然后您将获得对该节点的引用。

另一条路线是更换容器。例如,您可以使用deque。这没有连续的存储空间,但它与vector非常相似。 push_backpop_back仍然是O(1),它仍然具有良好的缓存一致性。 (顺便说一句,deque交换连续存储,以便在O(1)时间内push_frontpop_front进行交易。

重要的是deque 它通过一种矢量列表混合工作,您可以获得链接在一起的元素的存储块。将您的基础存储更改为deque(并且不要将任何东西放在中间),您可以指出正常情况。

然而,据我所知,你的地图非常低效。您将名称映射到节点。您应该只使用std::map,它具有您尝试重新创建的确切界面。您甚至可以指向地图中的任何元素,这些元素永远不会使事物无效。

<子> *规则是,传递const-reference,除非类型是原始的(内置如intdouble等),如果类型大小小于sizeof(void*),或者如果你还需要它的副本。

也就是说,不要这样做:

void foo(const std::string& s)
{
    std::string ss(s); // make a copy, use copy
}

但是这样做:

void foo(std::string s) // make a copy, use copy
{
}

答案 2 :(得分:4)

当向量增长时,它会被重新分配,这有效地使所有指向向量元素的指针无效。

如果您事先知道向量中将包含多少元素,则可以使用reserve()方法预分配空间。

答案 3 :(得分:0)

返回指向内部STL成员的指针可能不是最好的主意。当您将对象提供给STL容器时,您基本上放弃了对它的控制。你告诉STL它可以移动它,因为它认为适合维护容器给你的承诺。返回节点所在的索引比Steven Sudit提到的更好。

获得索引后,您可以创建一个函数,返回您感兴趣的节点内容的副本。这样,您还可以使用STL容器维护数据封装,而不允许其他任何人修改其内容。< / p>

答案 4 :(得分:0)

是的,插入将使重新分配的向量元素的旧指针无效。如果你想真的很难使用稳定的指针,你可以从vector切换到deque。它提供了一个非常类似于vector的接口,可以在不重新分配的情况下增长,并通过分配更多的块来移动以前的内容。

使用deque而不是vector来支付的价格是随机访问的另一个间接级别。根据您的使用情况,这可能完全无关紧要。如果需要,您应该使用迭代器迭代遍历整个双端队列。这将是对矢量的快速迭代。

增益是:零重新分配!

答案 5 :(得分:0)

想象一下,您需要编写基于数组的代码,以便在必要时可以重新调整数组的大小。

想象一下你将如何做到这一点。

想象一下过时指针会发生什么。

重写您的代码以使用索引而不是指针,或者保证不会发生重新分配。