【问题标题】:Why doesn't std::priority_queue have a clear() member function为什么 std::priority_queue 没有 clear() 成员函数
【发布时间】:2013-10-03 18:10:18
【问题描述】:

我今天做了一些黑客攻击,发现 std::priority_queue 没有clear() 成员函数。是否有任何技术原因可以解释为什么标准委员会可能忽略了这一点?

明确地说,我知道通过分配很容易解决这个问题:

oldPQ = std::priority_queue<int>{};

这种解决方案不太理想,因为:

  1. 它需要您重复输入 - 这在维护下无法继续工作。正如@chris 在下面指出的那样,如果您使用默认构造函数,则可以简化此操作,但如果您有自定义比较器,则可能无法实现。
  2. std::priority_queue 不能在需要 clear() 成员函数的模板函数中使用。
  3. 一般不符合其他容器提供的通用接口是不可取的。特别是,从std::forward_liststd::unordered_mapstd::string 的所有内容都有clear()。我注意到的唯一其他例外是​​ std::array,其语义没有意义,以及 std::stackstd::queue,当 std::deque 无需任何额外努力即可工作时,语义更值得怀疑。

看似有问题但实际上不一定是: 因为用于std::priority_queue 的内部容器是模板化的,并且可能没有自己的clear() 成员函数,这产生了一个有趣的问题,特别是它提出了向后兼容性的问题。这不是问题,因为:

  1. 对于不提供clear()的内部容器,只要没有人尝试调用std::priority_queue::clear(),代码就会继续编译。
  2. SFINAE 仍然可以通过在内部容器可用时调用clear() 来提供新接口(clear 成员),如果不可用则重复弹出。

我认为这是 C++ 标准中的一个缺陷。假设技术讨论没有为为什么省略此方法提供强有力的理由,我打算继续创建标准提案。

编辑:

似乎这正在委员会内处理(注意最后一篇文章):https://groups.google.com/a/isocpp.org/forum/?fromgroups#!searchin/std-discussion/clear/std-discussion/_mYobAFBOrM/ty-2347w1T4J

http://wg21.cmeerw.net/lwg/issue2194

【问题讨论】:

  • oldPQ = {}; 不应该工作吗?我知道其他原因仍然有效,但这不需要重复类型。
  • @chris - 好收获!从来没有建立过你可以做到的联系。
  • clear 是序列和关联容器的要求(它们单独需要)。事实上,似乎所有三个容器适配器stackqueuepriority_queue 都没有clear
  • 另外,如果您想知道某事是否是错误:isocpp forums -- ISO C++ Standard – Discussion。描述:“您想知道为什么某个 C++ 语言或库功能会以这种方式工作吗?多个功能如何交互?或者您是否在标准中发现了错误?”

标签: c++ c++11 stl


【解决方案1】:

众所周知,容器适配器的规范过于迂腐:因为相应数据结构的“抽象”规范(来自一些关于抽象算法和数据结构的书)不包括操作 clear规范的优先级队列或堆栈,适配器中未提供。这确实经常使在实践中使用这些适配器非常不方便。

不过,好消息是内部容器成员在适配器内部被声明为适配器的 protected 成员,名为 c。这可能是专门为您能够轻松实现自己的适配器版本而完成的:通过从标准适配器继承并添加您想要添加的任何成员函数,包括clear

至于将这些适配器的接口与标准容器接口进行比较……我认为这不是一个有效的比较。这些适配器从未打算在接口方面与容器兼容。恰恰相反,这些适配器的目的主要是限制数据结构的公共接口,并迫使它进入其规范抽象定义所允许的狭窄范围内。

例如,您不能迭代规范堆栈。根据定义,堆栈不是“可迭代的”。堆栈适配器禁用迭代接口的事实是一件好事。但是clear 的缺席确实让人觉得太迂腐了,因为它具有很大的实用价值,而且看起来不像是对规范接口的严重违反。

【讨论】:

  • 我接受这可能是所使用的推理,但认为这还不够。 1) std::priority_queue 并不意味着继承自。它的析构函数不是虚拟的。 2)std::priority_queue,至少对我而言,在涉及哪些操作看起来合理时,与std::stackstd::queue 属于不同的桶。特别是对于后两者,人们很容易问“为什么不使用std::deque?”,std::priority_queue 没有简单的替代方案。更准确地说,std::priority_queue 增加了功能,而不仅仅是隐藏了一些功能。
  • @Mark: 1)在模板编程中,继承并不意味着代表ISA关系,这意味着析构函数是否是虚拟的完全无关紧要,2)c是一个protected 成员清楚明确地表明适配器类 实际上是要继承自,3)如果您不喜欢在这种情况下使用公共继承(即使没有错了),您可以选择私有的,手动重新公开继承的成员。
  • 析构函数应该是公共的和虚拟的或受保护的和非虚拟的;滥用会暴露未定义的行为机会。 Herb Sutter 和 Andrei Alexandrescu 的“C++ 编码标准”在这一点上的指导与撰写时一样重要。模板编程并不能缓解这一规则,关于整个类别的崇高声明也站不住脚。私有继承是表达组合的粗略且糟糕的技术。我会接受这个建议在制作 STL 时可能并不为人所知。正如 Scott Meyers 喜欢说的“我们还年轻”。
  • @Mark:不是真的。有大量的模板元编程技术批判性地基于公共继承的纯实用属性(CRTP 只是一个巨大的例子),它们完全忽略了多态破坏问题。没有必要将这个纯粹的经典 OOP 问题带入模板元编程领域。 “暴露未定义的行为机会”在 C++ 世界中不是一个论点。一个普通的除法运算符“暴露了未定义的行为机会”。这并不意味着我们应该避免它。
  • CRTP 绝不是指南的例外。这些问题同样真实,指导也很容易遵循,而且没有缺点。选择A或B,很简单。至于未定义的行为,一般来说,是的,我们在语言中允许很多;我们可以用这么多方式射自己的脚,这不是很好吗?如果免费防止未定义的行为很容易,为什么不呢?因为我们喜欢糟糕的设计?因为我们要保存几个字符?宁愿在编译时失败而不是在运行时出现未定义的行为。假设你的代码的用户是愚蠢的。
猜你喜欢
  • 1970-01-01
  • 2011-04-21
  • 1970-01-01
  • 2013-01-28
  • 1970-01-01
  • 1970-01-01
  • 2021-06-10
  • 1970-01-01
相关资源
最近更新 更多