一般而言,处理一个inline函数,有两个阶段:
- 分析函数定义,以决定函数的“intrinsic inline ability”(本质的inline能力)。
如果函数因其复杂度,或因其构建问题,被判断不可成为inline,它会被转为一个static函数,并在“被编译模块”内产生对函数的定义。
- 真正的inline函数扩展操作是在调用那一点上。这会带来参数的求值操作(evaluation)以及临时对象的管理。
同样是在扩展点上,编译器将决定这一调用是否“不可为inline”。inline函数如果只是一个表达式,则其第二或后继调用操作就不会被展开:
new_pt.x(lhs.x() + rhs.x());
//不会被展开成这样
new_pt.x = lhs._x + x_5pointFV(&rhs);
//大概是这样
new_pt.x(lhs._x + rhs._x);
形式参数(Formal Arguments)
在inline扩展期间,每一个形式参数都会被对应的实际参数取代。一般而言,面对“会带来副作用的实际参数”(比如多次求值),通常都需要引入临时对象。如果实际参数是一个常量表达式,我们可以在替换之前先完成求值操作,然后替换;如果不是不是个常量表达式,也不是个带副作用的表达式,那么就直接代换。
如下的inline函数:
inline int min(int i,int j)
{
return i < j ? i : j;
}
inline int bar()
{
int minval;
int val1 = 1024;
int val2 = 2048;
/*(1)*/minval = min(val1,val2);
/*(2)*/minval = min(1024,2048);
/*(3)*/minval = min(foo(),bar() + 1);
return minval;
}
//(1)
//参数直接替换
minval = val1 < val2 ? val1 : val2;
//(2)直接拥抱常量
minval = 1024;
//(3)那一行引发副作用,需要导入一个临时对象
//以避免重复求值
int t1;
int t2;
minval = (t1 = foo(),t2 = bar() + 1),
t1 < t2 ? t1 : t2;
局部变量
在inline函数中加入一个局部变量,如下:
inline int
min(int i,int j)
{
int minval = i < j ? i : k;
return minval;
}
//下面是调用
int local_var;
int minval;
//...
minval = min(val1,val2);
inline被扩展的样子:
{
//将inline函数局部变量处以“mangling”操作
int _min_lv_minval;
minval = (_min_lv_minval =
val1 < val2 ? val1 : val2),
_min_lv_minval;
}
一般而言,inline函数中每一个局部变量都必须放在函数调用的一个封闭区间,有用独一无二的名字。如果inline函数以单一表达式扩展多次,则每次扩展都需要自己的一组局部变量。如果inline函数以分离的多个式子被扩展多次,那么只需要一组局部变量,就可以重复使用。
inline函数中的局部变量,再加上有副作用的参数,可能会导致大量临时性对象的产生。特别是如果它以单一表达式被扩展多次的话:
minval = min(val,val2) + min(foo(),foo() + 1);
//扩展成如下
//为局部变量产生临时变量
int _min_lv_minval_00;
int _min_lv_minval_01;
//为防止副作用值而产生临时变量
int t1;
int t2;
minval =
((_min_lv_minval_00 =
val1 < val2 ? val1 : val2),
_min_lv_minval_00)
+
((_min_lv_minval_01 = (t1 = foo()),
(t2 = foo() + 1),
t1 < t2 ? t1 : t2),
_min_lv_minval_01);
inline函数对于封装提供了一种必要的支持,可以有效的存取封装于class中的nonpublic 数据,同时它也是C程序大量使用的#define的一个安全替代品。然而inline函数如果被调用太多次的话,会产生大量的扩展码,使程序膨胀。
参数带有副作用的,或是以一个单一表达式做多重调用,或是在inline函数中有多个局部变量,都会产生临时对象,编译器也许(或也许不)能够它们移除。此外,inline中再由inline,可能会使一个表面看起来平凡的inline却因其连锁复杂度而没办法扩展开来。这种情况发生于赋值class体系下的constructor,或是object体系中一些表面上并不正确的inline调用所组成的串链。
既要安全又要效率的程序,inline函数提供了强大而有力的工具,然而与non-inline函数比起来,它们需要更加小心地处理。