博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
effective C++ 条款 30:透彻了解inlining的里里外外
阅读量:5347 次
发布时间:2019-06-15

本文共 2288 字,大约阅读时间需要 7 分钟。

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

当你inline某个函数,或许编译器就因此又能力对它(函数本体)执行语境相关最优化。

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

如果inline函数的本体很小,编译器针对“函数本体”所产生的码可能比针对“函数调用”所产出的码更小。将函数inline可以导致更小的目标码,从而提高效率。

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

隐喻方式是将函数定义于class定义式内:

class Person{

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

明确申请inline函数的做法是在其定义式前加上关键字inline。

template<typename T>

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

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

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

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

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

inline void f(){…} //假设编译器有意愿inline“对f的调用”

void (*pf)() = f;

f();//这个调用将被inlined,因为是一个正常调用

pf();//这个调用或许不被inlined,因为通过指针达成

有时候编译器生成构造函数和析构函数的outline副本,这样他们就可以获得指针指向那些函数,

在array内部元素的构造和析构过程中使用。

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。

程序库设计者必须评估“将函数声明为inline”的冲击:inline函数无法随着程序库的升级而升级。f是程序库内的一个inline函数,

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

从实用观点出发,大部分调试器对inline函数都束手无策。

转载于:https://www.cnblogs.com/lidan/archive/2012/02/04/2338677.html

你可能感兴趣的文章
SuperMap iServerJava 6R扩展领域开发及压力测试---判断点在那个面内(1)
查看>>
Week03-面向对象入门
查看>>
一个控制台程序,模拟机器人对话
查看>>
Vue 2.x + Webpack 3.x + Nodejs 多页面项目框架(上篇——纯前端多页面)
查看>>
我的PHP学习之路
查看>>
【题解】luogu p2340 奶牛会展
查看>>
对PostgreSQL的 SPI_prepare 的理解。
查看>>
解决响应式布局下兼容性的问题
查看>>
使用DBCP连接池对连接进行管理
查看>>
【洛谷】【堆+模拟】P2278 操作系统
查看>>
hdu3307 欧拉函数
查看>>
Spring Bean InitializingBean和DisposableBean实例
查看>>
[容斥][dp][快速幂] Jzoj P5862 孤独
查看>>
Lucene 学习之二:数值类型的索引和范围查询分析
查看>>
软件开发工作模型
查看>>
Java基础之字符串匹配大全
查看>>
面向对象
查看>>
lintcode83- Single Number II- midium
查看>>
移动端 响应式、自适应、适配 实现方法分析(和其他基础知识拓展)
查看>>
selenium-窗口切换
查看>>