前言
众所周知,inline只是对编译器提出的申请,如果该请求被接受,则会使用某个expression合理地将该函数扩展开来。
处理inline函数
一般而言,处理一个inline函数有两个阶段:
- 分析函数定义以确定函数的intrinsic inline ability
如果一个函数因为其复杂度或建构问题,被编译器判断为无法inline,则会被转为一个static函数,并在“被编译模块”内产生对应的函数定义。理想情况下链接器会将产生出来的重复代码清除,但实际情况视编译环境而定。 - 真正的inline函数扩展操作是在调用的哪一点上,这会带来参数求值操作及临时对象管理等问题
形式参数
在inline扩展期间,每一个形参都会被实参所取代,但这种取代操作并非想象中的一一替换,因为这会造成重复求值的可能。一般来说,为了避免这一可能性,我们会引入临时对象。举例而言,如果某个实参是一个常量表达式,我们可以在替换前先完成求值操作,后继需要该实参的地方则以计算值绑定。
问题实例
现有inline函数如下:
1 | inline |
现有三个调用操作:
1 | inline |
三次调用的扩展情况如下:
1 | minval = val1 < val2 ? val1 :val2; //expression 1 |
expression1与expression2均不会产生任何副作用,而expression3需要导入一个临时对象,以避免重复求值:
1 | int t1; |
局部变量
如果我们的inline函数存在局部对象,如:
1 | inline |
我们的调用操作如下:
1 | void bar() { |
inline被展开后的可能结果如下:
1 | int local_var; |
一般而言,inline函数中的每一个局部变量都必须被放在函数调用的一个封闭区段当中,拥有一个独一无二的名称。如果inline函数以单一表达式扩展多次,那么每一次扩展都需要自己的一组局部变量。如果inline函数以分离的多个式子被扩展多次,那么只需要一组局部变量,就可以重复使用。
inline函数中的局部变量,再加上有副作用的参数,可能会导致大量临时性对象的产生。特别是如果它以单一表达式被扩展多次的话。举例而言:
1 | minval = min(val1,val2) + min(foo(),foo()+1); |
可能被扩展为:
1 | int _min_lv_minval_00; |
inline函数对于封装提供了一种必要的支持,可以有效存取封装于class中的nonpublic数据,同时也是C程序宏定义的有效替代物。
一如我所描述过的,参数带有副作用,又或者以一个单一表达式做多重调用,或在inline函数中存在多个局部变量,都会产生临时性对象。编译器可能无法对它们执行优化操作。另外,inline中再次含有inline可能会形成低效的串联(可能会发生于复杂class体系下的constructors)。inline函数提供了一个强而有力的工具,然而,与non-inline函数比起来,它们需要更加小心地处理。