【问题标题】:Why should pop() take an argument?为什么 pop() 应该接受一个论点?
【发布时间】:2011-02-14 22:06:36
【问题描述】:

快速背景
我是一名 Java 开发人员,在空闲/无聊的时间里一直在玩 C++。

前言
在 C++ 中,您经常会看到 pop 通过引用获取参数:

void pop(Item& removed);

我知道用您删除的内容“填充”参数很好。这对我来说完全有道理。这样,要求删除顶部项目的人可以查看已删除的内容。

但是,如果我要在 Java 中执行此操作,我会执行以下操作:

Item pop() throws StackException;

这样,在弹出之后我们返回:NULL 结果,一个项目,或者一个异常将被抛出。

我的 C++ 教科书向我展示了上面的示例,但我看到很多堆栈实现不带参数(例如stl stack)。

问题
C++如何实现pop函数?

奖金
为什么?

【问题讨论】:

    标签: java c++ stack


    【解决方案1】:

    回答这个问题:你不应该在 C++ 中实现 pop 函数,因为它已经被 STL 实现了。 std::stack 容器适配器提供了方法 top 来获取对栈顶元素的引用,并提供方法 pop 来移除顶部元素。请注意,正如您所询问的那样,不能单独使用 pop 方法来执行这两个操作。

    为什么要这样做?

    1. 异常安全: Herb Sutter 在GotW #82 中很好地解释了这个问题。
    2. 单一职责原则: 在 GotW #82 中也提到过。 top 负责一项职责,pop 负责另一项职责。
    3. 不要为不需要的东西付费: 对于某些代码,检查顶部元素然后弹出它可能就足够了,而无需制作元素的(可能很昂贵)副本。 (SGI STL 文档中提到了这一点。)

    任何希望获得元素副本的代码都可以免费执行此操作:

    Foo f(s.top());
    s.pop();
    

    另外,this discussion 可能很有趣。

    如果您要实现 pop 来返回值,那么是按值返回还是将其写入 out 参数并不重要。大多数编译器都实现了RVO,它将优化按值返回方法,使其与复制输入输出参数方法一样有效。请记住,其中任何一个都可能比使用 top() 或 front() 检查对象的效率低,因为在这种情况下,绝对不会进行复制。

    【讨论】:

    • 很棒的链接,非常感谢。所以,如果我在一次采访中被要求在 C++ 中实现 pop() ......我应该给他们你的答案吗? :p
    • +1… 但也许“如果你用 C++ 实现 pop 函数,你应该遵循标准容器接口。”
    • @Stephano:取决于面试官想要什么。有些人可能会对您了解 std::stack 并知道它具有 pushtoppop 方法感到满意。有些人可能希望您使用固定长度的数组来实现自己的堆栈,只是为了看看您是否可以做到。
    • 顺便说一句,如果受访者提到std::stack,那么一个很好的后续问题是询问该适配器中使用了哪种底层容器以及为什么它是一个不错的选择。
    • @Dan:您应该在答案中明确std::stack::pop() 与问题中的界面不同(即pop() on;y 调整容器,它没有返回顶部元素作为 rturn 值或通过传入的引用)。
    【解决方案2】:

    使用 C++0x 使整个事情再次变得困难。

    作为

    stack.pop(item); // move top data to item without copying
    

    可以有效地从堆栈中移动顶部元素。而

    item = stack.top(); // make a copy of the top element
    stack.pop(); // delete top element
    

    不允许这样的优化。

    【讨论】:

    • item = std::move(stack.top()); stack.pop(); 确实有效。值得注意的是,C++03 中这种设计的主要原因(异常安全)在 C++11 中不再是这种情况(因为移动构造函数应该是无抛出的)。
    【解决方案3】:

    IMO,C++ 中 Java 的 pop 函数等效的一个很好的签名是这样的:

    boost::optional<Item> pop();
    

    使用选项类型是返回可能或可能不可用的内容的最佳方式。

    【讨论】:

      【解决方案4】:

      我能看到在 C++ 中使用这种语法的唯一原因:

      void pop(Item& removed);
      

      如果您担心会发生不必要的复制。

      如果您返回Item,它可能需要一个额外的对象副本,这可能很昂贵。

      实际上,C++ 编译器非常擅长复制省略,并且几乎总是实现返回值优化(通常即使在编译时禁用优化),这使得这一点没有实际意义,甚至可能意味着简单的“按值返回”在某些情况下,版本会变得更快

      但如果您进行过早优化(如果您担心编译器可能不会优化掉副本,即使在实践中它这样做) ,您可能会主张通过分配给引用参数来“返回”参数。

      更多信息here

      【讨论】:

        【解决方案5】:

        Java 方法的问题在于它的pop() 方法至少有两个作用:删除一个元素,并返回一个元素。这违反了软件设计的单一职责原则,这反过来又为设计复杂性和其他问题打开了大门。这也意味着性能损失。

        在 STL 方式中,想法是有时当您 pop() 时,您对弹出的项目不感兴趣。您只想要删除顶部元素的效果。如果函数返回元素而你忽略它,那么这是一个浪费的副本。

        如果您提供两个重载,一个接受引用,另一个不接受,则您允许用户选择他(或她)是否对返回的元素感兴趣。调用的性能将达到最佳。

        STL 不会重载pop() 函数,而是将它们拆分为两个函数:back()(或top(),在std::stack 适配器的情况下)和pop()back() 函数只是返回元素,而 pop() 函数只是删除它。

        【讨论】:

        • 一个函数作为一个原子操作有两种效果,并不违反软件设计的单一职责原则。
        • @Dave 有多少理由要更改删除并返回元素的函数pop()?如果你可以数多个,那就违反了单一职责原则。
        • @Wilhelm - 对于 Java 中的某些队列实现,pop() 的要求是它原子地执行这两个操作。
        • 单一职责原则涉及系统中类或组件的职责,而不是方法的影响。你知道的,你可以把设计原则走得太远。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2020-09-20
        • 2018-02-15
        • 2018-10-23
        • 1970-01-01
        • 1970-01-01
        • 2021-09-13
        • 1970-01-01
        相关资源
        最近更新 更多