4.5 Inline Functions

以下是Point class 的一个加法运算符的可能实现内容:

class Point {
friend Point operator+(const Point&, const Point&);
};
Point operator+(const Point &lhs, const Point &rhs) {
Point new_pt;
new_pt._x = lhs._x + rhs._x;
new_pt._y = lhs._y + rhs._y;
return new_pt;
}

理论上,一个比較"干净"的做法是使用 inline 函数set和get函数来完毕.

// void Point::x(float new_x) { _x = new_x; }
// float Point::x() { return _x; }
new_pt.x(lhs.x() + rhs.x());

因为受限仅仅能在上述两个函数中对_x直接存取,因此也就将稍后可能发生的data members的改变(比如在继承体系中上移或下移)所带来的冲击最小化了.假设把这些存取函数声明为 inline,就能够继续保持直接存取members的那种高效率--同一时候我们也兼顾了函数的封装性.此外,加法运算符不再须要被声明为Point的一个
friend.

    然而,实际上并不能强迫将不论什么函数都变成 inline.关键词 inline(或 class declaration中的member function或 friend function的定义)仅仅是一项请求.假设这项请求被接受,编译器就必须觉得它能够用表达式合理地将这个函数扩展开来.

    编译器相信它能够合理地扩展一个 inline 函数,意思是在某个层次上,其运行成本比一般的函数调用以及返回机制所带来的负荷低.cfront有一套复杂的測试法,通经常使用来计算assignments,function calls,virtual function calls等操作的次数.每一个表达式种类有一个权值,而 inline 函数的复杂度就以这些操作的总和来决定.

    一般而言,处理一个 inline 函数,有两个阶段:

    1.分析函数定义,以决定函数的"intrinsic inline ability"(本质的 inline 能力)."intrinsic"(本质的,固有的)一词在这里意指"与编译器相关".

    假设函数因其复杂度或建构问题,被推断为不可成为 inline,它会被转为一个 static 函数,并在"被编译模块"内产生相应的函数定义.

    2.真正的 inline 函数扩展操作是在调用的那一点,这会带来參数的求值操作以及暂时性对象的管理.

    相同是在扩展点上,编译器将决定这个调用是否"不可为inline".在cfront中,inline 函数假设仅仅有一个表达式,则其第二或后继的调用操作:

new_pt.x(lhs.x() + rhs.x());

就不会被扩展开来,这是由于在cfront中它被变成:

new_pt.x = lhs._x + x_5PointFv(&rhs);

这就全然没有带来效率上的改善!对此,唯一可以做的就是重写其内容:

new_pt.x(lhs._x + rhs._x);

形式參数 (Formal Arguments)

在 inline 扩展期间,究竟真正发生了什么事情?是的,每个形式參数都会被相应的实际參数代替.假设说有什么副作用,那就是不能够仅仅是简单地一一封塞程序中出现的每个形式參数,由于这将导致对于实际參数的多次求值操作.一般而言,面对"会带来副作用的实际參数",通常都须要引入暂时性对象.换句话说,假设实际參数是一个常量表达式,能够在替换之前先完毕求值操作;后继的
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(102, 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;

局部变量 (Local Variables)

假设轻微地改变定义,在 inline 定义中增加一个局部变量,会如何:

inline int min(int i, int j) {
int minval = i < j ? i : j;
return minval;
}

这个局部变量须要什么额外的支持或处理吗?假设有下面的调用操作:

{
int local_var;
int minval;
// ...
minval = min(va1, val2);
}

inline 被扩展后,为了维护其局部变量,可能会变成这样子(理论上这个样例中的局部变量能够被优化,其值能够直接在minval中计算):

{
int local_val;
int minval;
// 将inline函数的局部变量处以"mangling"操作
int __min_lv_minval;
minval = (__min_lv_minval = val1 < val2 ? val1 : val2), __min_lv_minval;
}

一般而言,inline 函数中的每个局部变量都必须放在函数调用的一个封闭区段中,拥有一个独一无二的名称.假设 inline 函数以单一表达式扩展多次,那么每次扩展都须要自己的一组局部变量.假设 inline 函数以分离的多个式子被扩展多次,那么仅仅需一组局部变量,就能够反复使用.

    inline 函数中的局部变量,再加上有副作用的參数,可能会导致大量暂时性对象的产生.特别是假设它以单一表达式被扩展多次的话.比如,以下的调用操作:

minval = min(val1, 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 体系下的constructors,或是object体系中一些表面上并不对的 inline 调用锁组成的串链--它们每个都会运行一小组运算,然后对还有一个对象发出请求.对于既要安全又要效率的程序,inline
函数提供了一个强而有力的工具.然而,与non-inline 函数比起来,它们须要更加小心地处理.

最新文章

  1. redux学习
  2. [iOS]技巧集锦:UITableView自定义Cell中的控件无法完全对齐Cell的左边界和右边界
  3. box2d 遍历世界中 body 的方法
  4. 6 让我们的C#程序开始做点数学运算
  5. ASP.NET读取配置文件发送邮件
  6. Python中查找字符串方法的速度比较
  7. Windows下bmp文件格式
  8. 体验Azure的 Automation “自动化” 服务预览版
  9. ios开发UI篇—在ImageView中添加按钮以及Tag的参数说明
  10. HUST 1602 Substring
  11. Java常用类之String类、Stringbuffer和Random类练习
  12. CMD 和 Git 中的代理设置
  13. 关于visual assist x插件不能用的解决方案
  14. MySQL数据查询
  15. 使用MVCPager做AJAX分页所需要注意的地方
  16. 用原生js对表格排序
  17. bzoj 1185
  18. Spark Storage(一) 集群下的区块管理
  19. 关于activity生命周期,启动模式和tag
  20. 乘积尾零——第九届蓝桥杯C语言B组(省赛)第三题

热门文章

  1. 远程登录工具 —— filezilla(FTP vs. SFTP)、xshell、secureCRT
  2. vue -- 7 个 有用的 Vue 开发技巧
  3. Kali linux 2016.2(Rolling)中metasploit的主机探测
  4. 使用与不适用@RequestBody注解的区别
  5. Framework3.5安装(Windows8.1)
  6. PostgreSQL中流复制pg_basebackup做了什么
  7. JS触发按钮事件
  8. nl---统计行号
  9. 【转】30分钟掌握 C#6
  10. 第二十四天 框架之痛-Spring MVC(四)