Effective C++:条款30:透彻了解inlining的里里外外

(一)

inline函数,可以调用它们而又不需蒙受函数调用所招致的额外开销。

inline函数背后的整体观念是,将“对此函数的每一个调用”都已函数本体替换之,这样做可能增加你的目标码(object code)大小。在内存有限的机器上,过度inline会造成程序体积太大,导致换页行为,降低缓存的命中率等一些带来效率损失的行为。如果inline函数的本体很小,编译器针对“函数本体”所产生的码可能比针对“函数调用”所产出的码更小。将函数inline可以导致更小的目标码,从而提高效率。

(二)

inline只是对编译器的一个申请,不是强制命令。这种申请可以隐喻提出也可以明确提出。

(1)隐喻提出(隐喻方式是将函数定义于class定义式内):

class Person {
public:
    ...
    int age() const {return theAge;}//一个隐喻的inline申请
    ...
private:
    int theAge;
};

(2)明确提出(明确申请inline函数的做法是在其定义式前加上关键字inline):

template<typename T>
inline const T& std::max(const T& a, const T& b) {
    return a < b? b: a;
}

(三)

(1)如果我们正在写一个template而我们认为素有根据此template具现出来的函数都应该inlined,那么请将此template声明为inline;

如果template没有理由要求它所具现的每个函数都是inlined,就应该避免将这个template声明为inline(不论显式还是隐式)。inline需要成本。

(2)大部分编译器拒绝将太过复杂(例如带有循环或递归)的函数inlining,而所有对virtual函数的调用也都会使inlining落空。因为virtual意味着“等待,直到运行期才确定调用哪个函数”,而inline意味“执行前,先将调用动作替换为被调用函数的本体”。如果编译器不知道该调用哪个函数,那肯定就没法inlining了!

(3)有时,虽然编译器有意愿inlining某个函数,但还是可能为该函数生成一个函数本体。例如,如果程序要取某个inline函数的地址,因为编译器通常必须为此函数生成一个outlined函数本体(毕竟编译器没有能力提出一个指针指向并不存在的函数),所以编译器通常不对“通过函数指针而进行的调用”实施inlining。

inline void f(){…} //假设编译器有意愿inline“对f的调用”
void (*pf)() = f;
f();//这个调用将被inlined,因为是一个正常调用
pf();//这个调用或许不被inlined,因为通过指针达成

(四)

class base {
public:
    ...
private:
    std::string bm1, bm2;
};

class Derived : public Base {
public:
    Derived(){ }  //Derived 构造函数是空的 是吗?
    ...
private:
    std::string dm1, dm2, dm3;
};

这个构造函数看起来是inlining的绝佳候选人,因为他根本不含任何代码,但是:

C++对于“对象被创建和被销毁时发生什么事”做了各式各样的保证。编译器为稍早说的那个表面上看起来是空的Derived构造函数所产生的代码,相当于以下所列:

Derived::Derived() {
   Base::Base();
    try{dm1.std::string::string();}
    catch(...){
        Base::~Base();
        throw;
    }
    try{dm2.std::string::string();}
    catch(...){
        dm1.std::string::~string();
        Base::~Base();
        throw;
    }
    try{dm3.std::string::string();}
    catch(...){
        dm2.std::string::~string();
        dm1.std::string::~string();
        Base::~Base();
        throw;
    }
}

这段代码并不能代表编译器真正制造出来的代码,因为真正的编译器会以更精致复杂的做法来处理异常.尽管如此,这已能准确反映Derived的空白构造函数必须提供的行为。Derived构造函数至少一定会陆续调用其成员变量和baseclass两者的构造函数,而那些调用(它们自身也可能被inlined)会影响编译器是否对此空白函数inlining。

(五)

程序库设计者必须评估"将函数声明为inline"的冲击:inline函数无法随着程序库的升级而升级。

客户将“f函数本体”编进其程序中,一旦程序库设计者决定改变f,所有用到f的客户端程序都必须重新编译。然而若f是non-inline函数,客户端只要重新连接就好了,如果是程序库采用动态链接,升级后的函数甚至可以不知不觉的被应用程序吸纳。

请记住:

(1)将大多数inlining限制在小型,被频繁调用的函数身上.这可使日后的调试过程和二进制升级更容易。也可使潜在的代码膨胀问题最小化,使程序的速度提升机会最大化。

(2) 不要因为function templates出现在头文件,就将它们声明为inline。

Effective C++:条款30:透彻了解inlining的里里外外

时间: 06-25

Effective C++:条款30:透彻了解inlining的里里外外的相关文章

Effective C++ 条款30 透彻了解inlining的里里外外

1. inline函数既和带参宏一样不带来函数调用的额外开销,又具有和非inline函数相同的功能,也就是说,inline函数同时具备带参宏和非inline函数的优点. 此外,编译器优化机制通常针对于那些不含参数调用的代码,因此inline某个函数就有可能使编译器对它执行语句相关最优化. 2. 虽然inline函数有诸多优点,但由于inline函数对每一次函数调用都用函数本体替换,这无疑加重了编译负担,更重要的是它增加了代码量,此外,由于inline造成的代码膨胀"会导致额外的换页行为(pagi

《Effective C++》:条款30:透彻了解inlining的里里外外

inline函数为什么放到头文件 inline函数是特殊的函数,它有宏的优点,却克服了宏的缺点(**条款**2).inline函数可以免除函数调用所招致的额外开销,但你实际获得的好处可能比你想象的还多,编译器会对inline函数本体执行语境相关最优化. 但使用inline函数会导致目标码(object code)变大,因为对inline函数的调用都会以函数本体替换.在内存比较小的机器上,不宜过多使用inline函数.即使使用虚拟内存,也会导致额外的换页行为(paging),降低指令高速缓存装置的

读书笔记 effective c++ Item 30 理解内联的里里外外 (大师入场啦)

最近北京房价蹭蹭猛涨,买了房子的人心花怒放,没买的人心惊肉跳,咬牙切齿,楼主作为北漂无房一族,着实又亚历山大了一把,这些天晚上睡觉总是很难入睡,即使入睡,也是浮梦连篇,即使亚历山大,对C++的热情和追求还是不减,应该是感动了周公吧,梦境从此处开始,大师入场来给我安慰了... 11点躺在床上了,脑子里总结一下最近的工作:最近的开发用到inline函数比较多,众所周知,inline的使用是为了提高程序性能,可结果却总不尽如人意,这个捉急啊,嗯?怎么突然到了山脚下,周边树木林立,郁郁葱葱,鸟儿委婉啼叫

effective c++ 条款18 make interface easy to use correctly and hard to use incorrectly

举一个容易犯错的例子 class Date { private: int month; int day; int year; public: Date(int month,int day,int year) { this->month = month; ... } } //wrong example Date date(30,3,1995);//should be 3,30 Date date(2,30,1995);//should be 3,30 使用类型可避免这个问题 class Month

More Effective C++ 条款35 让自己习惯于标准C++ 语言

(由于本书出版于1996年,因此当时的新特性现在来说可能已经习以为常,但现在重新了解反而会起到了解C++变迁的作用) 1. 1990年后C++的重要改变 1). 增加了新的语言特性:RTTI,namespaces,bool,关键词mutable和explicit,enums作为重载函数之自变量所引发的类型晋升转换,以及"在class 定义区内直接为整数型(intergral) const static class members设定初值"的能力. 2). 扩充了Templates的特性

More Effective C++ 条款17 考虑使用lazy evaluation(缓式评估)

1. lazy evaluationg实际上是"拖延战术":延缓运算直到运算结果被需要为止.如果运算结果一直不被需要,运算也就不被执行,从而提高了效率.所谓的运算结果不被执行,有时指只有部分运算结果被需要,那么采用拖延战术,便可避免另一部分不被需要的运算,从而提高效率,以下是lazy evaluation的四种用途. 2. Reference Counting(引用计数) 如果要自己实现一个string类,那么对于以下代码: String s1="Hello"; S

effective c++ 条款4 make sure that objects are initialized before they are used

1 c++ 类的数据成员的初始化发生在构造函数前 class InitialData { public: int data1; int data2; InitialData(int a, int b) { data1 = a: //this is assignment data2 = b; //this is assignment } /* InitialData(int a, int b):data1(a),data2(b) //this is initial {} */ } 2 不同cpp文

More Effective C++ 条款34 如何在一个程序中结合C++和C

1. C++和C混合使用的前提之一就是编译器产生兼容的目标文件(.lib和.dll等).所谓"兼容",指的是编译器在"预编译器相依的特性上"一致,如int和double大小,参数压栈机制等,只有在这个基础上才能讨论结合使用C++和C模块的问题. 2. 在1的基础上,要结合使用C++和C的模块,主要有以下几点需要注意: 1). name mangling(名称重整) Name mangling是C++用于支持函数重载的机制,它对重载的函数名称进行一定改变,使得每个函数

Effective C++ 条款3 尽可能用const

1. const可被施加于任何作用域内的对象,函数参数,函数返回类型,成员函数本体.用const修饰指针,如果const出现在*之前,表明指针不能更改所指向的对象的内容,如果const出现在*之后,表明指针只能指向同一块内存.另外int const*p和const int*p含义相同.如果对象成员有普通指针,那么构造该类的一个const对象时,const修饰使得该指针只能指向同一块内存,但指针指向的内容可以改变. 2. 将某些东西声明为const可以帮助编译器侦测出错误用法. 3. 编译器强制实

effective c++ 条款13 use object to manage resources.

请求的系统资源需要最终还回系统,为了避免遗忘返还这个动作,可以利用析构函数在object销毁时自动调用的特点来实现. 简单说就是用object来管理资源. 以内存资源为例 class Investment {}; Investment* creatInvestment(){...} // factory function to produce investment object void main() { Investment* pInv = creatInvestment();//call t