为什么pop()会参与争论?

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

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

void pop(Item& removed); 

我知道用你删除的内容“填写”参数是很好的。 这完全对我有意义。 这样,要求删除顶部项目的人可以查看删除的内容。

但是,如果我在Java中这样做,我会做这样的事情:

 Item pop() throws StackException; 

这样,在pop之后我们返回:结果为NULL,Item或exception将被抛出。

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

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

奖金
为什么?

回答这个问题:你不应该在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(); 

此外, 这个讨论可能很有趣。

如果您要实现pop来返回值,那么无论是按值返回还是将其写入out参数都无关紧要。 大多数编译器都实现了RVO ,它将优化返回值方法,使其与copy-into-out-parameter方法一样高效。 请记住,其中任何一个都可能不如使用top()或front()检查对象,因为在这种情况下绝对没有复制完成。

Java方法的问题是它的pop()方法至少有两个效果:删除一个元素,然后返回一个元素。 这违反了软件设计的单一责任原则,这又为设计复杂性和其他问题打开了大门。 它还意味着性能损失。

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

如果你提供两个重载,一个是引用而另一个不引用,那么你允许用户选择他(或她)是否对返回的元素感兴趣。 通话的表现将是最佳的。

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

使用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 

不允许这样的优化。

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

 void pop(Item& removed); 

如果你担心不必要的副本发生了。

如果您返回该项,则可能需要该对象的附加副本,这可能很昂贵。

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

但是如果你进入过早优化(如果你担心编译器可能不会优化副本,即使在实践中它会这样做),你可能会争论通过分配参考参数来“返回”参数。

更多信息在这里

IMO,一个很好的签名,用于C ++中Java的pop函数的等价,如下所示:

 boost::optional pop(); 

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