问题 如何使用用户定义的对象从priority_queue获取非const顶级元素?


的std :: priority_queue ::顶部 返回一个常量值。但是,我想从优先级队列中删除顶部元素,并能够在其他地方修改它。

priority_queue<SomeClass, vector<SomeClass>, SomeClassCompare > pQueue;
...
SomeClass *toBeModified = &(pQueue.top());
pQueue.pop();
toBeModified->setMember(3); // I would like to do this

有没有办法可以从优先级队列中获取顶级元素(并从队列中删除)并按照我的意愿修改它?


12807
2018-05-25 23:11


起源



答案:


标准容器和容器适配器 价值语义。将元素推入队列时,会创建一个副本。从队列中删除对象时,该对象将被销毁。

即使 top() 会给你一个非const,一旦从队列中删除该元素,该引用将变为悬空,并且取消引用它将导致未定义的行为。

这说, std::priority_queue 返回引用 const 为了防止你(有意或无意地)弄乱其内部排序 - 这与关联容器的关键,例如 std::map 和 std::set 是 const

相反,你可以做的是构建一个 复制 返回的值 top(),修改该副本,删除原始副本,并将副本推送到队列中:

SomeClass obj = pQueue.top();
pQueue.pop();
obj.setMember(42);
pQueue.push(std::move(obj)); // You can move obj into the queue if you no more need it

如果你需要 引用语义另一方面,那么你将不得不推动 指针 进入队列(可能是智能指针,具体取决于您的用例)并提供适当的自定义排序标准,该标准将根据指向的对象的属性对这些指针进行排序。

在这种情况下,请注意不要在运行时修改这些属性,使其顺序不同。这算作“搞乱容器的内部排序“,并将导致未定义的行为。


14
2018-05-25 23:20



@Ethouris你错误地将整个声明标记为“完整BS”。具有讽刺意味的是,这几乎与你攻击“废话”本身的原因相同。 - sehe
@Ethouris:请避免不必要的侵略性。有更优雅的方式不同意。我的观点是(我猜,已经过去了2。5年),复制了推入标准容器和从标准容器中移除的元素,因此OP想要做什么(获取对容器内部元素的引用,删除元素,执行间接操作) )无效 - 导致UB。这适用于所有其他容器(包括您提到的容器)的优先级队列 - queue。我不是说优先排队在这方面很特别。 - Andy Prowl
@Ethouris:此外,优先级队列提供const引用的原因与失效无关:如答案中所述,其原因是为了防止用户搞乱内部排序标准。 - Andy Prowl


答案:


标准容器和容器适配器 价值语义。将元素推入队列时,会创建一个副本。从队列中删除对象时,该对象将被销毁。

即使 top() 会给你一个非const,一旦从队列中删除该元素,该引用将变为悬空,并且取消引用它将导致未定义的行为。

这说, std::priority_queue 返回引用 const 为了防止你(有意或无意地)弄乱其内部排序 - 这与关联容器的关键,例如 std::map 和 std::set 是 const

相反,你可以做的是构建一个 复制 返回的值 top(),修改该副本,删除原始副本,并将副本推送到队列中:

SomeClass obj = pQueue.top();
pQueue.pop();
obj.setMember(42);
pQueue.push(std::move(obj)); // You can move obj into the queue if you no more need it

如果你需要 引用语义另一方面,那么你将不得不推动 指针 进入队列(可能是智能指针,具体取决于您的用例)并提供适当的自定义排序标准,该标准将根据指向的对象的属性对这些指针进行排序。

在这种情况下,请注意不要在运行时修改这些属性,使其顺序不同。这算作“搞乱容器的内部排序“,并将导致未定义的行为。


14
2018-05-25 23:20



@Ethouris你错误地将整个声明标记为“完整BS”。具有讽刺意味的是,这几乎与你攻击“废话”本身的原因相同。 - sehe
@Ethouris:请避免不必要的侵略性。有更优雅的方式不同意。我的观点是(我猜,已经过去了2。5年),复制了推入标准容器和从标准容器中移除的元素,因此OP想要做什么(获取对容器内部元素的引用,删除元素,执行间接操作) )无效 - 导致UB。这适用于所有其他容器(包括您提到的容器)的优先级队列 - queue。我不是说优先排队在这方面很特别。 - Andy Prowl
@Ethouris:此外,优先级队列提供const引用的原因与失效无关:如答案中所述,其原因是为了防止用户搞乱内部排序标准。 - Andy Prowl


我不认为价值语义在这里起任何作用。所有其他容器具有相同的值语义,几乎所有容器都提供 front() 有可变参考。

有一个确切的原因 priority_queue 不允许修改 top() element:这是因为特定元素位于顶部,因为根据其现值,它已经相应地合格。优先级队列中的元素是 总是排序 根据为队列配置的比较条件(默认情况下为operator <)。通过更改元素,您可能会破坏此条件,并导致使用已排序前置条件的所有操作都将导致未定义的行为。

总之,之所以如此 priority_queue 不允许修改包含的元素与情况完全相同 set 和钥匙 map

我知道你可能有一些专用的比较方法,你使用一个包含比较值的字段,你将修改该对象的任何内容,除了这个字段。这样您就不会违反排序顺序的要求。你可以做两件事:

  1. 使你的班级的部分应该被修改为 mutable。这样你就可以获得元素了 top() 并修改可变内容,即使这是const。

  2. 通过派生创建自己的优先级队列类 std::priority_queue。它有一个名为的字段 c (在 保护 虽然部分),其中包含对底层容器的引用 - 而底层容器总是有一个 front() 访问相同元素的方法 top() 在 priority_queue。但是,为了您自己的安全,您应该成为关键领域 const,因此它在施工期间设置并且从未改变 - 只是为了将风险降至最低。

啊,这个问题也不能通过使用指针来解决 - 指向对象将是完全相同的常量。这些“引用语义”也不会对你有任何帮助,因为如果你想要一个专用的比较方法,就必须查看对象的内容,这样你就会遇到完全相同的问题。如果您依靠简单地比较指针值,这对您有所帮助,但我更怀疑这可能是99%案例的解决方案。


1
2017-10-01 10:28



我恐怕你误解了我的“使用指针”提示'。我当然不是建议将其作为返回的引用的常量的变通方法 top()。事实上,从字面上看,下一句话警告可能的方法来搞乱内部排序。它只是建议如果用户不打算在队列中存储副本,他们应该使用指针。 - Andy Prowl
好的,我稍微改变了关于指针的评论。 - Ethouris