首页 > 代码库 > Exceptional C++: [Item 46 Forwarding Functions] [条款46 转发函数]

Exceptional C++: [Item 46 Forwarding Functions] [条款46 转发函数]

条款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()现在被多么广泛的使用,以及将来它会多么频繁的变化。