引言  inline函数

在函数声明或定义中函数返回类型前加上关键字inline即把min()指定为内联。

inline函数对编译器而言必须是可见的,以便它能够在调用点内展开该函数。与非inline函数不同的是,inline函数必须在调用该函数的每个文本文件中定义。当然,对于同一程序的不同文件,如果inline函数出现的话,其定义必须相同。

为保证不会发生这样的事情,建议把inline函数的定义放到头文件中。在每个调用该inline函数的文件中包含该头文件。这种方法保证对每个inline函数只有一个定义,且程序员无需复制代码,并且不可能在程序的生命期中引起无意的不匹配的事情。

第一节 C++ inline函数的使用

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

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

1.inline函数的声明:

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

隐喻方式:

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

明确声明:

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

inline函数通常放置在头文件内,因为大多数建置环境(build environments)在编译过程中进行inlining,为了将“函数调用”替换为“被调用函数的本体”,编译器必须知道那个函数长什么样子。

templates 通常也被置于头文件内,因为他一旦被引用,编译器(在编译期)为了将它具现化,需要知道它长什么样子。如果template没有理由要求它所具现的每个函数都是inlined,就应该避免将这个template声明为inline(不论显式还是隐式)。

2.inlining与virtual

大部分编译器拒绝将太过复杂(带有循环或递归)的函数inlining,而所有对virtual函数的调用也都会使inlining落空。因为:

inline:执行前,先将调用动作替换为被调用函数的本体

virtual:等待,直到运行期才确定调用哪个函数

有时,虽然编译器有意愿inlining某个函数,还是可能为该函数生成一个函数本体。例如,如果程序要取某个inline函数的地址,编译器通常必须为此函数生成一个outlined函数本体。编译器通常不对“通过函数指针而进行的调用”实施inlining,这意味着对inline函数的调用有可能inlined,也可能不被inlined:

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

即时你从未使用函数指针,“未被成功inlined”的inline函数还是有可能缠住你,因为程序猿并非唯一要求函数指针的人!有时候编译器会生成构造函数和析构函数的outlined副本,如此一来他们就可以获得指针指向那些函数吗,在array内部元素的构造和析构过程中使用。

3.inline与构造/析构函数

实际上构造函数和析构函数往往都是inlining的糟糕候选人。

 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构造函数至少一定会陆续调用其成员变量和base class两者的构造函数,而那些调用(它们自身也可能被inlined)会影响编译器是否对此空白函数inlining。

相同的理由也适用于Base构造函数。

程序库设计者必须评估“将函数声明为inline”的冲击:inline函数无法随着程序库的升级而升级。f是程序库内的一个inline函数,客户将“f函数本体”编进其程序中,一旦程序库设计者决定改变f,所有用到f的客户端程序都必须重新编译。然而若f是non-inline函数,客户端只要重新连接就好了,如果是程序库采用动态链接,升级后的函数甚至可以不知不觉的被应用程序吸纳。
从实用观点出发,大部分调试器对inline函数都束手无策。

◆总结

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

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

[Effective C++ --030]透彻了解inlining的里里外外的更多相关文章

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

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

  2. [Effective C++系列]-透彻了解inlining的里里外外

    Understand the ins and outs of inlining.   [原理] Inline函数背后的做法是将“对函数的每一个调用”都用函数本体(function body)替换之.其 ...

  3. 【30】透彻了解inlining 的里里外外

    1.inline方法相当于文本替换,不需要承担方法调用的额外开销,同时还有潜在的优势,文本替换后,编译器会进行代码优化.而对于方法调用,编译器没有能力进行代码优化. 2.显而易见,inline方法往往 ...

  4. 条款30:透彻了解inline的里里外外(understand the ins and outs of inlining)

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

  5. 条款30:透彻了解inline的里里外外。

    inline可以带来各种好处: 首先其可以使得消除函数调用带来的开销,再者编译器对这种非函数的代码可以做出更多的优化策略.   但是inline函数首先肯定是会导致程序代码的大小更加的庞大,这样会带来 ...

  6. 《Effective C++》第三版笔记

    阅读此笔记前,请先阅读 <Effective C++>第二版笔记  和  <More Effective C++>笔记 这里只记录与上面笔记不同的条款,主要是 "面对 ...

  7. 《Effective C++》读书摘要

    http://www.cnblogs.com/fanzhidongyzby/archive/2012/11/18/2775603.html 1.让自己习惯C++ 条款01:视C++为一个语言联邦 条款 ...

  8. Effective C++ 的55个条款

    看完Effective C++才觉得平时程序设计时需要注意的一些问题,有一定的收获,不过因为没什么项目实践, 并未很深入了解具体情况如何,还需后继实践~ 列举一下55个条款: 1. 视C++为一个语言 ...

  9. Effective C++学习进阶版

    记得前段时间又一次拿起<Effective C++>的时候,有种豁然开朗的感觉,所以翻出了我第一遍读时做的笔记.只做参考以及查阅之用.如有需要请参阅<Effective C++> ...

随机推荐

  1. HDU-4861 Couple doubi

    http://acm.hdu.edu.cn/showproblem.php?pid=4861 Couple doubi Time Limit: 2000/1000 MS (Java/Others)   ...

  2. 【LeetCode 229】Majority Element II

    Given an integer array of size n, find all elements that appear more than ⌊ n/3 ⌋ times. The algorit ...

  3. Selenium2Library系列 keywords 之 _SelectElementKeywords 之 _get_select_list_options_selected(self, locator)

    def _get_select_list_options_selected(self, locator): select = self._get_select_list(locator) # TODO ...

  4. 【windows核心编程】使用远程线程注入DLL

    前言 该技术是指通过在[目标进程]中创建一个[远程线程]来达到注入的目的. 创建的[远程线程]函数为LoadLibrary, 线程函数的参数为DLL名字, 想要做的工作在DLL中编写.  示意图如下: ...

  5. 最简单例子图解JVM内存分配和回收

    一.简介 JVM采用分代垃圾回收.在JVM的内存空间中把堆空间分为年老代和年轻代.将大量(据说是90%以上)创建了没多久就会消亡的对象存储在年轻代,而年老代中存放生命周期长久的实例对象.年轻代中又被分 ...

  6. 第三百零六天 how can I 坚持

    今天做了件并不是我风格的事,送了张公交卡,还没送出去,好难搞啊.这天会铭记的.如果将来我们能走在一起. 中午去朝阳门拿了我的荣事达破壁机,好大啊,怎么带回家啊,还有,回家要不要买两只烤鸭啊. 今天聊了 ...

  7. ES6学习(1)——如何通过babel将ES6转化成ES5

    使用babel编译ES6 babel是一个工具,可以通过多个平台,让js文件从ES6转化成ES5,从而支持一些浏览器并未支持的语法. Insall babel $ sudo npm install b ...

  8. Struts2的国际化

    1.概述 把在无需改写源代码即可让开发出来的应用程序能够支持多种语言和数据格式的技术称为国际化. 与国际化对应的是本地化, 指让一个具备国际化支持的应用程序支持某个特定的地区 Struts2国际化是建 ...

  9. struts2+Hibernate4+spring3+EasyUI环境搭建之一:准备工作

    SSHE环境搭建第一步:安装软件(经验:安装软件路径最好不要有空格.括弧.中文等特殊符号)1.Jdk72.tomcat73.maven34.MyEclipse10.7 破解及优化设置(设置本地安装jd ...

  10. 【转】在企业内部分发 iOS 应用程序

    (via:破船之家,原文:Provision iOS IPA App for In-House Enterprise Distribution)   在企业内部分发 iOS 应用程序非常复杂.经过努力 ...