当对头元素使用引用时,C++ 优先级队列的行为很奇怪
Posted
技术标签:
【中文标题】当对头元素使用引用时,C++ 优先级队列的行为很奇怪【英文标题】:C++ priority queue behaves wierdly when using reference for the head element 【发布时间】:2013-07-26 20:12:24 【问题描述】:我有以下代码试图转储存储在 C++ 中的 priority_queue 中的值:
priority_queue<Point, vector<Point>, myCmp> pq;
int i;
for (i = 0; i < 10; i++)
Point p(i,i);
pq.push(p);
const Point& p0 = pq.top();
while(!pq.empty())
cout<<p0.x<<p0.y<<endl;
pq.pop();
//I'm getting output like "00 11 22 33 ... 99"
正如代码中的注释所说,每次队列弹出一个值时,我的引用变量 p0 都会不断变化。这对我来说没有意义,因为我认为 p0 应该始终是对 (0,0) 对象的引用,即开始时队列的前面。我知道我可以使用
Point p0 = pq.top()
获取前端元素的副本并避免该问题。但是,有人可以解释使用引用的问题吗?
附:我对 C++ 队列做了同样的事情,但没有发现这个问题。
【问题讨论】:
这是一种可枚举的标准做法;在迭代它们时,修改它们并不能保证引用将继续有效。 p0 指向存储在队列前面的对象。没有保证在弹出之后保持相同的位置(实际上,如果您使用 list实际上,我将在评论中给出更深入的答案。
所以当你得到一个引用时,你实际上只是得到了一个指向前面元素的指针。 std::vector 是底层容器,因此您拥有的代码(在功能上)与您拥有的代码更相似:
vector<Point> pq;
int i;
for (i = 0; i < 10; i++)
Point p(i,i);
pq.push_back(p);
Point* p = &pq[0];
while(!pq.empty())
cout<<p->x<<p->y<<endl;
pq.erase(pq.begin(),pq.begin()+1);
//I'm getting output like "00 11 22 33 ... 99"
现在没有保证 p* 在您开始弄乱值后指向向量的第一个成员。确实,如果我添加一个 pq.resize(100);在 p 分配之后,虽然 pq[0] 仍然等于 (0,0),但 p 指向外部随机未保留内存(读取输出垃圾或出现 seg-fault)。由于纯粹的机会,您现在正在获得正确的价值!这就是为什么你总是需要在弹出之前复制或移动。
【讨论】:
【参考方案2】:在您更改 priority_queue
(例如通过弹出)后,所有引用都将失效。所以这是未定义(?)行为
会发生什么:
您获得对顶部元素的引用。它存储为底层std::vector
的第一个元素,因此您只需链接到vector
中的第一个元素。当popping
重新排列vector
到新状态时,删除了前一个顶部元素。新的顶部元素应位于 vector
的首位。所以,如果vector
在同一个地方引用指向新的顶部
添加:
一些参考资料:
制作流行音乐: § 23.6.4.3/4
void pop(); 4 效果: pop_heap(c.begin(), c.end(), comp); c.pop_back();
§ 25.4.6.2/2
效果:将位置 first 中的值与位置 last 中的值交换 - 1 并使得 [first,last - 1) 放入一个堆中。
我没有发现 pop_back
for vector
与 erase(end() - 1)
完全相同(仅适用于字符串)的信息,但它似乎是真的。
§ 23.3.6.5/3
迭代器擦除(const_iterator position); 迭代器擦除(首先是 const_iterator,最后是 const_iterator); 效果:使迭代器和引用失效在擦除点或之后
因此,在pop
之后依赖引用将指向新的顶部似乎是有效的,因为即使重新分配似乎也是不可能的。但无论如何都不要使用它。
*所有参考 N3242
【讨论】:
以上是关于当对头元素使用引用时,C++ 优先级队列的行为很奇怪的主要内容,如果未能解决你的问题,请参考以下文章