Guru of the Week 条款27:转呼叫函数

GotW#27 转呼叫函数(Forwarding Functions)

难度:3 / 10

怎样将转呼叫函数写得最好?原本答案很简单,但我们已经知道C++语言近来发生了微妙的变化。

问题

转呼叫函数对于将任务传递给其它函数或对象时很有用,尤其当它们被设计得很高效时。

评论一下下面这个转呼叫函数。你试图修改它吗?如果是的话,怎么来?

    // file f.cpp

    #include "f.h"

    /*...*/

    bool f( X x ) {

        return g( x );

    }

(说明:本次GotW的目的之一是阐明在July [1997]于London加入到C++语言中的一个微妙改进所造成的后果。)

解答

转呼叫函数对于将任务传递给其它函数或对象时很有用,尤其当它们被设计得很高效时。

关键点是:效率。

评论一下下面这个转呼叫函数。你试图修改它吗?如果是的话,怎么来?

    // file f.cpp

    #include "f.h"

    /*...*/

    bool f( X x ) {

        return g( x );

    }

有两个主要改进可使得这个函数更高效。第一个应该总被采用,第二个需要权衡。

1.传参时使用传const的引用代替传值

“这不会造成混乱吗?”你可能会问。不,它不会,至少在这种情况下。直到最近,C++语言才规定:因为编译器可以确保参数x除了被传递给g()外没有被其它地方使用,编译器可以将x完全优化掉。例如,这样的代码:

    X my_x;

    f( my_x );

编译器可以:

a)产生一个my_x的拷贝供f()使用(就是f()的代码体中的形参x),然后将这个拷贝传给g();或者

b)直接将my_x传给g()而不生成拷贝,因为它注意到这个额外的拷贝除了作g()的参数外根本没被使用。

后者更高效,不是吗?这是编译器试图作的优化,不是吗?

是的,是的,但只到July 1997的London会议。在那次会议上,“限制编译器作这种取消额外拷贝的优化”的提案得到了更多的支持。〖注1〗编译器唯一可以取消额外拷贝构造的地方是“返回值优化”(在你的C++宝典中查询细节吧)和“临时对象”。

这意味着,象f这样的转呼叫函数,编译器被要求产生两份拷贝。既然我们(作为f的作者)知道这个额外的拷贝不是必须的,我们应该按照通常的办法将x申明为const X&型的参数。

(注意:如果我们一直就是这么做的,而不是依赖于知道编译器被允许做些什么,那么,这个规则的变化不会对我们造成任何影响。这就是一个“简单就是美”例子--尽可能避开语言的细枝末节,别耍小聪明。)

2.函数内联

这个需要权衡。要之,默认将所有函数都实现为外联,有选择地将确实需要内联以提高效率的函数实现为内联。

当你将函数内联时,积极面是你避免了对f函数的调用的额外开销。

消极面是内联f暴露了f的实现,并使得用户的代码依赖于此实现,当f被改变时,所有的用户代码都必须被重编译。更严重的是,用户代码现在至少需要知道函数g()的原型,这有点恶心,因为用户根本没有直接调用函数g,原本可以根本不需要知道它的原型的(至少,从我们的例子上,是这样的)。于是,如果g()自己发生了变化,接受其它类型的其它参数时,用户的代码将变得也需要知道这些类型的申明。

内联和非内联都可以。必须在优缺点间进行权衡,取决于f现在是怎样被使用的以及使用的广泛程度,和将来可能变为怎样被使用的以及使用的广泛程度。

GotW给出的代码规范:

l         传参时,用传const的引用来代替传值

l         避免函数内联,除非profiler告诉你有这个必要(程序员在猜测效能瓶颈点方面是很不准的)

 

1:这个改进是必要的,它避免了编译器未经允许地省略拷贝构造时带来问题,尤其当拷贝构造有副作用时。很多时候,代码需要计算对象的拷贝数目。

你可能感兴趣的:(Guru of the Week 条款27:转呼叫函数)