在模拟逻辑门的程序中,我使用数组切换
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指针(inp1
,inp2
)都指向垃圾!并且第3个指针指向正确的位置。
为什么会这样?
当我向它们插入项目时,矢量如何工作?我应该使用哪种指针指向矢量项目?
答案 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_back
和pop_back
仍然是O(1),它仍然具有良好的缓存一致性。 (顺便说一句,deque
交换连续存储,以便在O(1)时间内push_front
和pop_front
进行交易。
重要的是deque
它通过一种矢量列表混合工作,您可以获得链接在一起的元素的存储块。将您的基础存储更改为deque
(并且不要将任何东西放在中间),您可以指出正常情况。
然而,据我所知,你的地图非常低效。您将名称映射到节点。您应该只使用std::map
,它具有您尝试重新创建的确切界面。您甚至可以指向地图中的任何元素,这些元素永远不会使事物无效。
<子>
*规则是,传递const-reference,除非类型是原始的(内置如int
,double
等),如果类型大小小于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)
想象一下,您需要编写基于数组的代码,以便在必要时可以重新调整数组的大小。
想象一下你将如何做到这一点。
想象一下过时指针会发生什么。
重写您的代码以使用索引而不是指针,或者保证不会发生重新分配。