为什么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
方法不能用于执行这两个操作。
为什么要这样做呢?
- 例外安全: Herb Sutter对GotW#82中的问题给出了很好的解释。
- 单一责任原则:在GotW#82中也提到过。
top
承担一项责任,pop
负责另一项责任。 - 不要为你不需要的东西付费:对于某些代码,可能只需要检查顶部元素然后弹出它,而不必制作元素的(可能很昂贵的)副本。 (这在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();
使用选项类型是返回可能可用或不可用的内容的最佳方式。