C++ 在向量中插入元素是否会损坏指向该向量的指针?

C++ 在向量中插入元素是否会损坏指向该向量的指针?,c++,pointers,vector,C++,Pointers,Vector,在一个模拟逻辑门的程序中,我从使用阵列转换而来 node N[1000]; 到向量 vector<node> N; 向量N; 在使用矢量之前,我的程序工作得很好,但现在它打印错误的结果,所以我尝试调试,发现错误发生在这里: node* Simulator::FindNode(string h) { int i; for(i = 0; i < NNodes; i++) { if (N[i].getname() == h)

在一个模拟逻辑门的程序中,我从使用阵列转换而来

node N[1000];
到向量

vector<node> N;
向量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 
node*模拟器::FindNode(字符串h)
{
int i;
对于(i=0;i>节点名;
inp1=FindNode(节点名称);
s1=inp1;
inp_文件>>节点名;
inp2=FindNode(NodeName)//inp1在这里被破坏了
inp_文件>>节点名;
out=FindNode(节点名称)//inp2和inp1在这里被破坏
第一次调用
FindNode
时,第一个指针inp1指向正确的位置,即
&N[0]

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

第三次调用
FindNode
时,第一个和第二个指针(
inp1
inp2
)都指向垃圾!第三个指针指向正确的位置

为什么会发生这种情况?

当我向vector中插入项时,vector是如何工作的?我应该使用哪种指针指向vector项?

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


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

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


如果事先知道向量中有多少元素,可以使用该方法预先分配空间。

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


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

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

使用deque而不是矢量所付出的代价是在随机访问上多了一层间接寻址。根据您的使用情况,这可能是完全不相关的。如果需要,您应该使用迭代器对整个deque进行迭代。这和在向量上迭代一样快


收益是:零再分配

假设您需要编写基于数组的代码,以便在必要时可以重新调整数组的大小

想象一下你会怎么做

想象一下陈旧的指针会发生什么

重写代码以使用索引而不是指针,或者根据需要确保不会发生重新分配。

一些事情

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

另外,您的参数是通过值传递的,而它可能应该通过常量引用传递:
conststring&h
。这避免了不必要的副本,并且通常*您应该通过常量引用而不是通过值来传递内容

这很糟糕:

node n;
N.push_back(n);
N[NNodes].setname(h);
节点
可能应该有一个构造函数,该构造函数接受一个
常量字符串&
,并在初始化期间设置名称。这样,您就永远不会有没有名称的节点,如:

node n(h);
N.push_back(n);
或更简洁:

N.push_back(node(h));
好多了

其次,是的,
vector
可以使指向元素的指针无效;也就是说,当载体的容量需要增加时。如果可以,
reserve()。在你的情况下,你不能,所以你可以走两条不同的路线

第一条路线是。不要直接指向对象,而是将它们的索引放入数组中。注意,虽然它们的地址可能会改变,但它们在向量中的位置不会改变。您需要
Simulator::FindNode
返回一个
size\u t
,并返回
N.size()-1
。添加一个成员,比如
node&GetNode(size\u t index)
,它只返回N[index](如果愿意,将进行错误检查)。现在,每当您需要一个成员时,将该成员的索引交给
GetNode
,您就会得到该节点的引用

另一种方法是更换容器。例如,您可以使用
deque
。它没有连续的存储,但很像
vector
push_-back
pop_-back
仍然是O(1),并且它仍然具有良好的缓存一致性。(顺便说一句,
deque
用连续存储交换了在O(1)时间内
push_front
pop_front
的能力)

重要的是,
deque
在从任意一端进行推送或弹出操作期间不会使指针无效。它通过一种向量列表混合方式工作,在这里你可以得到
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
{
}