第9章 内联函数
C++从C中继承的一个重要特征是效率。假如C++的效率显著地低于C的效率,那么就会有很大一批程序员不去使用它。
在C中,保持效率的一个方法是使用宏(macro)。宏可以不要普通的函数调用代价就可使之看起来像函数调用。宏的实现是用预处理器而不是编译器。预处理器直接用宏代码代替宏调用,所以就没有了参数压栈、生成汇编语言的CALL、返回参数、执行汇编语言的RETURN等的开销。所有的工作由预处理器来完成,因此不用花费什么就具有了程序调用的便利和可读性。
在C++中,使用预处理器宏存在两个问题。第一个问题在C中也存在:宏看起来像一个函数调用,但并不总是这样。这样就隐藏了难以发现的错误。第二个问题是C++特有的:预处理器不允许访问类的成员数据。这意味着预处理器宏不能用作类的成员函数。
为了既保持预处理器宏的效率又增加安全性,而且还能像一般成员函数一样可以在类里访问自如,C++引入了内联函数(inline function)。本章将介绍C++中预处理器宏存在的问题、在C++中如何用内联函数解决这些问题以及使用内联函数的方针和内联函数的工作机制。
9.1 预处理器的缺陷
预处理器宏存在问题的关键是我们可能认为预处理器的行为和编译器的行为一样。当然,这是有意使宏在外观上和行为上与函数调用一样,因此容易被混淆。当微妙的差异出现时,问题就出现了。
考虑下面这个简单例子:
现在假如有一个如下所示的F调用:
预处理器展开它,出现下面不希望的情况:
出现这个问题是因为在宏定义中F和括号之间存在空格。当这个空格取消后,调用宏时可以有空格空隙。像下面的调用:
依然可以正确地展开为:
上面的例子虽然非常微不足道,但问题非常明显。当在宏调用中使用表达式作为参数时,真正的问题就出现了。
这里存在两个问题。第一个问题是表达式在宏内展开,所以它们的优先级不同于所期望的优先级。例如:
现在假如用表达式作参数:
宏将展开成:
因为&的优先级比>=的低,所以宏的展开结果将会使我们惊讶。一旦发现这个问题,可以通过在宏定义内的各个地方使用括弧来解决。(这是创建预处理器宏时使用的好方法。)上面的定义可改写成如下:
然而,发现问题可能很难,我们可能一直认为宏的行为是正确的。在前面没有加括号的版本的例子中,大多数表达式将正确工作,因为>=的优先级比像+、/、—,甚至按位移动操作符的优先级都低。因此,很容易想到它对于所有的表达式都正确,包括那些位逻辑操作符。
前面的问题可以通过谨慎地编程来解决:在宏中将所有的内容都用括号括起来。第二个问题则复杂一些。不像普通函数,每次在宏中使用一个参数,都对这个参数求值。只要使用普通变量调用宏仅,求值就无危险。但假如参数求值有副作用,那么结果可能出乎预料,并肯定不能模仿函数行为。
例如,下面这个宏决定它的参数是否在一定范围:
只要使用一个“普通”参数,宏和真的函数的工作方式非常相似。但只要一松懈并开始相信它是一个真的函数时,问题就出现了。如下所示:
注意宏名中所有大写字母的使用。这是一种很有用的做法,因为大写的字母告诉读者这是一个宏而不是一个函数,所以如果出现问题,也可以起到一定的提示作用。
下面是这个程序的输出,它完全不是想从真正的函数期望得到的结果:
当a等于4时,仅测试了条件表达式第一部分,表达式只求值一次,所以宏调用的副作用是a等于5,这是在相同的情况下从普通函数调用所期望得到的。但当数字在值域范围内时,两个表达式都测试,产生两次自增操作。产生这个结果是由于再次对参数操作。一旦数字出了范围,两个条件仍然测试,所以也产生两次自增操作。根据参数不同产生的副作用也不同。
很清楚,这不是我们想从看起来像函数调用的宏中所希望得到的行为。在这种情况下,明显的解决方法是设计真正的函数。当然,如果多次调用函数将会增加额外的开销并可能降低效率。不幸的是,问题可能并不总是如此明显。可能不知不觉地得到一个包含混合函数和宏的库函数,所以像这样的问题可能隐藏了一些难以发现的错误。例如,在cstdio中的putc()宏可能对它的第二个参数求值两次。这在标准C中作了详细说明。作为宏toupper()不谨慎地执行也会对第二个参数求值多次。如在使用toupper(*p++)[1]时会产生不希望的结果。
9.1.1 宏和访问
当然,在C中需要对预处理器宏谨慎地编码和使用。要不是因为宏没有成员函数作用域这一要求,我们也会在C++中侥幸成功地使用它。预处理器只是简单地执行字符替代,所以不可能用下面这样或近似的形式写:
另外,这里没有指明正在使用哪个对象。在宏里简直没有办法表示类的范围。由于没有可以取代预处理器宏的方法,程序设计者出于效率考虑,不得不让一些数据成员成为public类型,这样就会暴露内部实现并妨碍在这个实现中的改变,从而消除了private提供的保护。
[1]更多的细节参见Andrew Koenig的著作《C Traps&Pitfalls》(Addison-Wesley,1989).