条款46 转发函数
难度:3
编写转发函数的最好方式是什么?基本答案很简单,但是我们还是可以学到标准定案之前做出的一个微妙的语言变化。
转发函数是将任务转发给其他函数或对象的有用工具,尤其是在高效完成转发的时候。
评论下面的转发函数。你会修改它吗?如果会,怎样修改?
// file f.cpp // #include "f.h" /*...*/ bool f( X x ) { return g( x ); }
回答
还记得这个问题的介绍吗?转发函数是将任务转发给其他函数或对象的有用工具,尤其是在高效完成转发的时候。
这个介绍说到了问题的重点:效率。
有两个主要增强方式使得这个函数更高效。第一个总应被采用;第二个需要权衡。
1. 以const&而不是传值方式传递参数。
这不是很明显的吗?你会问。不,不是,在这个特例里。直到最近1997年之前,C++语言标准草案指出,如果编译器能够证明参数x除了传递给g()以外,不会用于其它目的,编译器可以完全省略x(也就是说,消除不必要的x)。例如,如果客户端代码象下面这样:
X my_x; f( my_x );
那么编译器被允许按下面任一方式处理:
o 为f()创建一个my_x的拷贝(一个在f()作用域内名为x的变量),然后传递给g()。
o 直接传递my_x给g()而不创建任何拷贝,因为编译器注意到额外的拷贝除了作为传递给g()的参数以外永远都不会被使用。
后者更有效率,对吧?这就是优化编译器所支持的,对吧?
是的是的,直到1997年7月的伦敦会议。在这次会议上,草案经过修订,针对这类允许编译器优化额外拷贝的场景作了更多限制。这个变化是必要的,来避免编译器被允许肆意地消除拷贝构造时所带来的问题,尤其是拷贝构造带有副作用时。在一些合理的场景中,合理的代码可能依赖于一个对象实际上的拷贝数目。
现在,编译器仍然可能消除拷贝构造函数的场景是返回值优化(去你喜爱的书本查看细节)和临时对象。这意味着对于转发函数比如f(),编译器必须完成两次拷贝。(作为f()的作者)我们知道这种情况下额外拷贝是不必要的,我们应退回到我们的常规规则上并将参数x声明为const X&。
方针:尽可能以引用而不是传值来传递对象。
注意:如果我们一直都遵循这个常规规则,而不是试图从编译器被允许做什么这类细节中获取好处,规则的改变不会影响到我们。这是一个有关为什么越简单越好的清晰例子:尽量避免语言满是灰尘的角落,并努力不依赖于矫揉造作的微妙之处。
方针:避免语言满是灰尘的角落;采用最简单的技术最有效。
2. 将函数内联化。这里需要权衡。总之,缺省应将所有的函数外联化,然后有选择地内联化你确信需要通过内联提高性能的个别函数。
方针:避免内联或者细节调优,直到性能概要证明确实需要。
如果你将函数内联化,正面影响是你避免了调用函数f()的额外开销。
负面影响是f()的内联化暴露了f()的实现,并使得客户端代码依赖于f()的实现。如果f()发生改变,所有的客户端代码都必须重新编译。更糟的是,客户端代码现在还至少需要知道函数g()的原型声明。这实在是有点丢人,因为客户端代码不会直接调用g(),并且可能从不需要g()的原型声明(至少就我们的例子而言)。而且如果g()改为取用其它类型的参数,客户端代码也会依赖于那些类型的声明。
内联化和非内联化都是合理的选择。这是一个基于收益和弊端的权衡,依赖于你所了解的,f()现在被多么广泛的使用,以及将来它会多么频繁的变化。
Exceptional C++: [Item 46 Forwarding Functions] [条款46 转发函数],布布扣,bubuko.com