【问题标题】:What should the 'pop()' method return when the stack is empty? [duplicate]当堆栈为空时,'pop()' 方法应该返回什么? [复制]
【发布时间】:2011-11-15 10:47:30
【问题描述】:

可能重复:
C++ STL stack question: Why does pop() not throw an exception if the stack is empty?

在 C++ 中设计堆栈时,pop() 方法(或 front() 方法)在堆栈为空时应该返回什么?以下哪个设计更好?

  1. 抛出异常
  2. 未定义,但需要用户调用 isempty() 方法在调用 pop() 之前进行检查
  3. 返回一个布尔代码,同时使用一个额外的参数(引用)来传递弹出的元素
  4. 定义一个唯一的空元素

好的,我看到我的问题不是很清楚,让我尝试重写它:

有一些数据结构可以基于链表实现,如堆栈、队列,它们中的每一个都有一个返回前端元素(或尾部)的方法。

我想知道,在数据为空的情况下,有没有设计这种方法的原则指导。

而我对更好的定义是“正确使用容易,错误使用困难”。

【问题讨论】:

  • 定义“更好”。哪一个满足用户最小惊喜原则?
  • 无法回答。这取决于代码库其余部分的策略。
  • 您可能还想考虑效率。在每个弹出窗口中检查空堆栈对某些人来说可能过于昂贵。所以一种选择是有两个函数,一个抛出,另一个是 UB(如 std.:vector)
  • 如果你的堆栈是低级的/应用程序内部的,我不会让它太友好,因为这意味着一个错误。
  • 如果这是明确的 C++ 根据问题为什么它也有 C 标签?

标签: c++ c data-structures


【解决方案1】:

按合同编程的风格是,有一个非空堆栈是调用pop的一个前提,而调用一个不满足其前提条件的方法有一个未定义 结果。我的实现会抛出一个std::logic_error,但这不是必需的。在 C 语言中,我的实现是 abort 通过 assert

pop的调用者负责在调用pop之前保证栈不为空的前提条件成立。因此堆栈应该有一个isEmpty 方法供调用者检查。

【讨论】:

    【解决方案2】:

    C++ STL 实际上不会通过pop() 返回任何内容,因为它将返回对象的值和实际从堆栈的内部数据结构中弹出对象分离,使它们成为两个独立的函数。因此,这是您在设计堆栈数据结构时要考虑的另一种选择。

    对于这些类型的数据结构,您的第三个选项也是一种非常惯用的方法。

    对于您的第四个选项,而不是“唯一的空元素”,我实际上会对您的第三个选项做一个变体,其中您的 pop() 函数采用指针参数而不是引用类型,如果有则返回 NULL堆栈中没有任何对象。

    【讨论】:

    • 在这种情况下,问题可能是:top() 应用于空堆栈时应该做什么?有趣的是,关于它的信息很少可以在网上找到。
    • 感谢 Jiri 的评论,这正是我的观点
    • 这是我喜欢指针类型的原因之一……你总是可以返回 NULL,这表明你没有得到有效的响应。对我来说,这总是比“默认空类型”更好的选择,后者最终会变得模棱两可或造成进一步的混乱(即“默认空类型”的列表??)。
    【解决方案3】:

    运行代码的环境类型是什么?通常,与现有的行为范式相匹配要好得多,而不是采用自己的做事方式。

    当你从一个空的抽象列表中请求一个元素时,它会抛出异常吗?如果是这样,最好让弹出非满堆栈抛出异常。

    当定义行为非常容易时,未定义的行为是一个糟糕的选择。

    如果大部分代码通过 return 语句返回项目,那么返回一个控件(如果它工作则返回布尔值)是一个糟糕的设计。如果大部分代码通过参数列表返回项目,那么通过 return 语句返回控件是一个不错的设计,前提是对类似集合的其他调用也是如此。

    一个空元素没有多大意义,它变成了一个神奇的值。例如,如果我创建一个列表并在其中推送五个空元素,它与其中没有空元素的列表相同吗?它与其中包含一个空元素的列表相同吗?包含一些元素和一个空元素的列表是否相同?空列表是一个“特殊”对象是一回事,但空元素是有问题的,因为它们并不真正包含元素的行为,它们包含列表的行为。良好的面向对象将行为的内容封装在它所描述的同一对象中。

    请注意,空元素与哨兵不同。哨兵是包含在集合中的实现细节(理想情况下不应该暴露在外部)。当我阅读“返回一个空元素”时,我认为必须非常了解堆栈的实现才能使用它。类之间过多的密切关系称为紧密耦合,它会使代码更难以修改/修复/更改。

    如果您确实采用自己的做事方式,那么您应该最低限度地使代码的整个方面表现相同。它使阅读维护变得更容易。

    【讨论】:

    • 我同意我应该保持所有行为相同,我只是在寻找方法......我认为可能会有类似“Effective C++”的建议,给我一些方向
    • 有很多选择。如果您正在构建自己的收藏库(有时会发生),那么以不同的方式做事(根据您的需要)是合适的。就最佳实践而言,最好保持一致,并使用有益于所有类型编码的良好编码实践(单元测试、特定于语言的实践(如 if (0 == x))等)。
    【解决方案4】:

    我可能建议同时使用poptrypop 方法。 pop 将简单地调用 trypop 并在失败时抛出异常。我的理由是,对于堆栈的某些用途,当堆栈为空时尝试弹出表示不应发生的程序逻辑错误 - 不平衡的推送/弹出,或者由于资源耗尽而对早期推送失败的错误处理.对于其他用途,弹出失败仅意味着您处于输入的末尾。在使用带有异常的编程模型时,区分这些用途可以避免在执行空堆栈检查和抛出异常时使调用者混乱。

    【讨论】:

    • “不要为不使用的东西付费”的 C++ 理念表明,从空堆栈中弹出元素应该是未定义的行为。由于算法的结构,正确的算法要么不会尝试从空堆栈中弹出,要么正确的范围检查;通过显式调用empty() 或使用不同的方法进行边界检查并根据需要引发或返回哨兵。
    • 我认为那是 C 哲学,而 C++ 哲学是“为你不使用的一切付出双倍的代价”...... :-)
    【解决方案5】:

    SGI STL implementation of stack 有这个设计说明:

    有人可能想知道为什么 pop() 返回 void,而不是 value_type。那 是,为什么必须使用 top() 和 pop() 来检查和删除顶部 元素,而不是将两者组合在一个成员函数中?在 事实上,这种设计是有充分理由的。如果 pop() 返回 顶部元素,它必须按值返回,而不是按 引用:按引用返回将创建一个悬空指针。返回 然而,按价值计算是低效的:它至少涉及一个冗余 复制构造函数调用。 因为 pop() 不可能返回一个 以一种既有效又正确的方式进行价值,它更 明智的做法是它根本不返回任何价值,并要求客户 使用 top() 检查栈顶的值。

    SGI 进一步为 pop() 指定:

    前提条件:empty() 为假。后置条件: size() 将是 减 1。

    至于 top() 的行为,SGI 规定:

    前提条件:empty() 为假。

    【讨论】:

      【解决方案6】:

      我会使用选项类型,也许是Boost.Optional。它专门用于提供对可选返回值的支持,这正是您想要的。

      【讨论】:

        猜你喜欢
        • 2010-09-30
        • 1970-01-01
        • 1970-01-01
        • 2020-12-31
        • 2019-06-22
        • 2021-12-24
        • 1970-01-01
        • 2021-04-15
        相关资源
        最近更新 更多