C++ STL栈问题:为啥栈为空时pop()不抛出异常?

Posted

技术标签:

【中文标题】C++ STL栈问题:为啥栈为空时pop()不抛出异常?【英文标题】:C++ STL stack question: Why does pop() not throw an exception if the stack is empty?C++ STL栈问题:为什么栈为空时pop()不抛出异常? 【发布时间】:2011-06-20 23:55:59 【问题描述】:

如果堆栈为空且没有可弹出的内容,为什么 std::stack::pop() 不抛出异常?

(我正在为我自己的代码设计一个专门的堆栈,并且想知道这种方法(需要手动检查堆栈是否为空)与抛出异常之间的权衡。

我的猜测是,尽管 C++ 支持异常处理,但它的运行时开销很小,因此,为了获得最佳性能,决定不在 std::stack::pop 中抛出异常)。

【问题讨论】:

您几乎猜对了。问题不在于异常的开销。它每次都在测试堆栈是否为空。如果您使用 std::stack,您应该知道(或检查自己)何时它变为空。 我不确定我是否理解在每次弹出之前检查堆栈是否为空会是低效的。这将是一个非常小的常数时间比较,不是吗? @Nocturne:它会很小,但它仍然是一些东西。什么>什么都没有。也就是说,std::stack 是一个容器适配器,因此在这种情况下,它只是执行底层容器所做的任何事情。 @Fred:实际上想一想,大多数算法可能无论如何都会检查空堆栈。所以提供一个 IsEmpty() 方法并让 pop 抛出一个异常对我来说更有意义(捕捉错误等等),而性能问题有点无关紧要。 @Moron:并非所有算法都需要检查。根据发生的情况,算法的其余部分可能会保证您永远不会弹出空堆栈。 【参考方案1】:

我认为 pop() 不必抛出异常的原因与效率或性能无关,但与 - 异常有关。

正如elsewhere所争论的那样:

    SGI 解释:http://www.sgi.com/tech/stl/stack.html 有人可能想知道为什么 pop() 返回 void,而不是 value_type。那是, 为什么必须使用 top() 和 pop() 来 检查并移除顶部元素, 而不是将两者组合在一起 单成员函数?事实上,有 是这个设计的一个很好的理由。如果 pop() 返回顶部元素,它 将不得不按价值返回 比引用:引用返回 将创建一个悬空指针。 但是,按值返回是 低效:至少涉及一个 冗余复制构造函数调用。自从 pop() 不可能返回一个 以这样的方式价值,即两者兼而有之 高效正确,更 明智的让它在 所有并要求客户使用 top() 检查顶部的值 堆栈的。

    std::stack 是一个模板。如果 pop() 返回顶部元素,它 将不得不按价值返回 比根据上述参考 解释。这意味着,在调用者 必须将其复制到另一个 T 对象的类型。这涉及副本 构造函数或复制赋值 运营商电话。如果这个类型 T 是 足够复杂,它会抛出一个 复制构造期间的异常或 复制作业?在这种情况下, 右值,即栈顶(返回 按价值)只是丢失并且有 没有其他方法可以从 堆栈作为堆栈的弹出操作是 顺利完成!

一旦我们得出结论 pop 应该返回它弹出的元素,因此它的界面被固定为void pop(),它 - 这是我的意见 - 不再有任何意义来规定什么在空堆栈上调用 pop() 时发生。

注意标准要求!empty()作为调用pop()的前提条件。

UncleBens(在 cmets 中)当然有一点,即在运行时不检查先决条件(C++ std AFAIK 从未规定过)对它有一定的性能气味。但是,引用原始问题的一部分:(强调我的)

(我正在为 我自己的代码,想知道 这种方法的权衡(其中 需要手动检查是否 堆栈是空的)与抛出一个 例外。

我会争辩说,pop() 没有返回任何内容这一事实使问题变得毫无意义。它(恕我直言)根本没有意义强制 pop() 验证堆栈是否为空,当我们真的没有从中得到任何东西时(即如果堆栈为空 pop() 可以只是一个 noop ,(诚然)Std 也没有规定)。

我认为人们可以问为什么top() 不抛出异常或者可以问为什么pop() 不返回顶部元素。如果pop 不返回任何内容,则抛出异常没有意义(在 C++ 世界中)——声称它不会像其他答案一样“因为异常的运行时成本”而抛出异常似乎暗示是 - 恕我直言 - 没有抓住重点。

【讨论】:

谢谢,内容丰富。您能否提供一个链接,让我可以访问 C++ 标准? 不过,我看不出,选择在运行时不强制执行先决条件与性能以外的其他因素有何关系... @UncleBens - 我认为异常论点是最引人注目的。如果::std::stack::pop 返回顶部元素,则不可能满足强异常保证。 @Nocture: open-std.org/jtc1/sc22/wg21 -> open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3126.pdf 我不明白这个论点。堆栈实现是否在弹出之前检查堆栈是否为空,给出了程序在弹出之后是否处于有效状态之间的区别。是否抛出异常以及这有多昂贵,不是主要问题。权衡是:是否值得实现检查执行弹出是否有效。如果程序被设计成保证永远不会从空堆栈中弹出,那么检查是多余的并且是纯粹的开销。其他客户可能永远无法确定,无论如何都必须进行检查。【参考方案2】:

你是对的。 C++ 标准总是更喜欢性能而不是安全。但可能存在包含调试范围检查的 STL 实现。

【讨论】:

正是出于这个原因(效率),STL 要求您调用 value_type& top() 后跟 void pop(),而不是使用按值返回的 pop()。 @Nathan:为了异常安全,这些是分开的。如果从按值返回的 pop 返回时复制 ctor 抛出,则您刚刚泄漏了位于堆栈顶部的项目(即,该项目丢失了,您也无法将其恢复到堆栈中)。见:gotw.ca/gotw/008.htm @Jerry 找到了!它与性能无关。事实上,强制开发者在 pop 之前调用 top 在性能方面更糟糕...... @Jerry 啊,我没想到 - 完全有道理! @Moron 如果相关元素的复制成本很高,top/pop 是否效率更高? @Moron:你有证据吗?【参考方案3】:

与 C++ 中的几乎所有功能一样,它们的设计理念是您无需为不用的东西付费。并非所有环境都支持异常,传统上,尤其是游戏开发。

因此,如果您使用 std::stack 则强制使用异常将不符合设计准则之一。即使禁用了异常,您也希望能够使用堆栈。

【讨论】:

【参考方案4】:

我认为值得一提的是,如果堆栈为空,允许抛出pop()——这不是必需的。在您的堆栈中,您可以assert 表明堆栈不为空,这将非常好(空堆栈上的pop 似乎给了 UB,所以您几乎可以做任何您想做的事情,真的)。

【讨论】:

在哪里记录了 pop() 允许抛出?规范是否指定了这一点? @Nocturne:第 17.4.4.8/3 节:“C++ 标准库中定义的任何其他没有异常规范的函数都可能抛出实现定义的异常,除非另有说明。”没有为std::stack::pop() 提供异常规范。它只是调用pop_back,它似乎也没有异常规范。 对我来说,我认为更重要的一点是push_back() 被定义为执行a.erase.(--a.end()) 如果a.begin() == a.end() 是UB,这就是允许“任何事情发生”的原因。 erase 通常保证不会为 list 抛出任何东西,如果包含类型的复制构造函数或赋值运算符不会为 vectordeque 抛出任何东西,则不会抛出任何东西;这些是您通常用来构建stack 的容器。 @Charles Bailey:大概你的意思是pop而不是push_back 糟糕,我的意思是 pop_backpop 调用的对象。【参考方案5】:

抛开性能不谈,我不认为从空堆栈中弹出是一种特殊情况 - 因此我也不会扔在那里。

【讨论】:

我认为这是一个例外情况。鉴于您无论如何都会检查空堆栈(大多数算法都内置了此检查),因此尝试弹出空堆栈将是一个例外。 +1 感谢您在这里发声。不应该抛出异常来响应调用者可以(轻松)检查的失败的先决条件。【参考方案6】:

异常是可选的,STL 希望随处可用。想想嵌入式系统:大量 C++ 代码,运行时没有异常支持。

【讨论】:

那么在嵌入式系统中,vector::at() 和 new 根本不会抛出异常? “大量 C++ 代码,运行时无异常支持” 对我来说似乎矛盾。 C++ 很明显有例外,如果你的平台不支持它,你没有使用 C++,你使用的是 C++ 的特定实现派生。

以上是关于C++ STL栈问题:为啥栈为空时pop()不抛出异常?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 C++ 中 std::mutex 的构造函数不抛出?

当条件全部为空时,查询全部,当其中一个或两个条件为空时,为空的条件不查询,sql怎么写

为啥延迟块为空时,Anylogic stopDelay() 函数会抛出错误?

蓝桥杯Leecode——栈排序

7-22 堆栈模拟队列 (25分)

程序员面试金典面试题 03.05. 栈排序