• C++对象模型——Inline Functions(第四章)


    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 函数比起来,它们须要更加小心地处理.

  • 相关阅读:
    socket错误码获取
    代码整洁之道读书笔记函数
    算法学习之堆排序
    包含与继承区别
    提高 LayerBacked Memory Use
    RenderBuffer
    算法学习之快速排序
    NSTimer
    DNS and BIND ... (转载) zhumao
    Samba学习笔记(转载) zhumao
  • 原文地址:https://www.cnblogs.com/jzssuanfa/p/7056020.html
Copyright © 2020-2023  润新知