Effective C++条款30:透彻了解inlining的里里外外(Understand the ins and outs of inlining)

news2024/11/15 14:09:59

Effective C++条款30:透彻了解inlining的里里外外(Understand the ins and outs of inlining)

  • 条款30:透彻了解inlining的里里外外
    • 1、inline函数的优缺点
    • 2、隐式内联和显式内联
      • 2.1 隐式内联
      • 2.2 显式内联
    • 3、函数模板必须inline么?
    • 4、编译器忽略内联的情况
    • 5、构造函数和析构函数是否要被inline?
    • 6、牢记
  • 总结


《Effective C++》是一本轻薄短小的高密度的“专家经验积累”。本系列就是对Effective C++进行通读:

第5章:实现

在这里插入图片描述


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

1、inline函数的优缺点

优点

  • 避免函数调用的开销

  实际上比你想象的要获取的更多,因为避免函数调用的开销只是这个故事的一部分。编译器最优化是为了浓缩没有函数调用的代码而设计,所以当你inline一个函数时,你可能使编译器在函数体上执行特定场景下的优化操作。大多数编译器不会在outlined的函数调用上执行这样的优化。

缺点

  • 以函数体代替函数调用,因此目标码增大。

  • 在内存有限的机器上,过度的inlining会造成占用空间过大的问题

  • 即使拥有虚内存,inline造成的代码膨胀也会造成额外的换页行为,降低指令高速缓存装置的集中率,以及伴随效率的损失。

2、隐式内联和显式内联

  需要注意的是inline是对编译器的请求而不是强制命令。请求可以显示或者隐式的提出来。

2.1 隐式内联

  隐式内联:当成员函数定义在类的内部时,这个函数是隐式inline的(隐式内联只有这一种情况)。例如:

class Person {
public:
    //隐式内联(编译器自动申请),这个函数(age)不仅在类中声明,还在类中进行了定义
    int age()const { return theAge; }
private:
    int theAge;
};

  这样的函数通常是成员函数,但是条款46中解释道friend函数也能在类中定义。如果是这样,它们也会被隐式声明成inline。

2.2 显式内联

  显式内联:我们也可以通过inline关键字显式的指出一个函数作为内联函数。例如:

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

3、函数模板必须inline么?

  max是个template可以让让我们联想到inline函数和模板通常被定义在头文件中的。因此一些程序要就下结论函数模板就必须是inline的。这个结论既无效并可能会有潜在的危害,值得我们对此进行分析。

  inline函数通常被置于头文件内,因为大多数建置环境在编译过程中进行inlining,编译器必须了解这个函数长成什么样子。某些编译环境能够在链接的时候执行内联,甚至有一些能够在运行时进行内联(如基于.NET CLI的托管环境),这样的环境都是例外,但不是通用规则。在大多数C++程序中inline是编译时活动。

  template模板通常也被置于头文件内,因此它一旦被使用,编译器为了将其实例化,也需要知道它长什么样子。

template的具现化与inlining无关:

  • 如果你写的模板认为具体实现处的函数应该是inlining的,那么就将template声明为inline

  • 如果你写的代码没有理由应该是inlining的,那么就将不要将template声明为inline(因为可能会产生代码膨胀)

4、编译器忽略内联的情况

  即使你将函数声明为inline的,inline也只是一个对编译器的请求,而编译器可能会将其忽略。例如:

  • 太过复杂的函数:例如带有循环或递归

  • 对virtual函数的调用:virtual意味着“只有在运行时才能决定调用哪个函数,”而inline意味着“执行程序之前,在调用点处用函数体进行替换”。如果编译器不知道将会调用哪个函数,你就不能因为拒绝为函数体内联而责备它。

这些叙述整合起来的意思就是:

  • 一个表面看似inline的函数,或者显式使用inline声明的函数,到底是不是一个内联函数,取决于你所使用的编译环境——而这个编译环境主要是只编译器。幸运的是,编译器会对这个过程进行诊断,如果inline一个函数失败了,它会发出一个警告(见条款53)。

  有时候即使编译器迫切的希望对函数进行inline,它们也会为其生成一个单独的函数体。例如,如果你的程序需要获知内联函数的地址,编译器就必须为其生成一个outline的函数体。它们不能使用一个不存在的函数指针吧?加上如下事实:编译器使用函数指针进行函数调用时不会为其进行inline,这意味着对内联函数的调用可能会被内联也可能不会,取决于函数调用是如何进行的:

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

  未被inline的inline函数还是会缠住你,即使你从未使用函数指针也是如此,因为并不是只有程序员才会需要函数指针。有时候编译器也会为构造函数和析构函数生成一份outline副本,这样一来它们就可以获得指针指向那些函数,在array内部元素的构造和析构过程中使用。

5、构造函数和析构函数是否要被inline?

  构造函数和析构函数通常情况下是inline函数的槽糕候选人,而不像表面看上去那样,考虑以下Derived class的构造函数:

class Base{
public:
    //...
private:
    std::string bm1, bm2;
};
 
class Derived :public Base {
public:
    Derived() {}   //构造函数为空
private:
    std::string dm1, dm2, dm3;
};

  上面的Derived构造函数为空,此时你可能会认为Derived的构造函数时inlining的,但是实际上不是这样的。

  我们知道当对象被创建或者析构的时候C++必须保证一些事情的发生:

  • 当你使用new的时候,你的动态创建的对象由它们的构造函数自动初始化;当你使用delete时,对应的析构函数要被触发。

  • 当你创建一个对象时,对象的基类部分和它的每个数据成员都会被自动构建,当对象被销毁的时候相反的过程也就是自动析构就会发生。

  • 如果在构造或者析构的时候抛出异常,已经被构建出来的对象的任何部分都应该被自动释放。

  上面的Derived的构造函数虽然为空,但是其有3个数据成员,基类有2个数据成员。下面是伪代码,编译器会自动为这些数据成员进行初始化:

//伪代码
Derived::Derived() 
{
    //下面是编译器为空的Derived构造函数添加的代码
    Base::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的构造函数必须为其数据成员和基类调用构造函数,这些调用(可能它们本身是inline的)会影响inline的吸引力。

  同样的原因适用于基类构造函数,因此如果它被inline了,它里面的代码同样会被插入到Derived构造函数中(Derived构造函数会调用基类构造函数。)。并且如果string构造函数恰恰也被inline了,Derived构造函数会增加5份函数代码的拷贝(对应Derived中的5个string),现在你应该明白了为什么对Derived构造函数进行inline是一个没脑子的决定。同样的考虑也适用于Derived析构函数,我们必须看到被Derived构造函数初始化的对象被合适的销毁掉。

6、牢记

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

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

总结

期待大家和我交流,留言或者私信,一起学习,一起进步!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/78845.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

自动驾驶之3D点云聚类算法调研

1. 方法 总共分为4类 基于欧式距离的聚类Supervoxel 聚类深度(Depth) 聚类Scanline Run 聚类 1.1 基于欧氏距离的聚类 思路: 在点云上构造kd-tree, 然后在某个半径阈值(例如0.5m), 则分割为一个实例。 相似算法: RBNN (radially bounded nearest neighbor graph), 2008. …

在 Ubuntu 上安装 Discourse 开发环境

本指南只针对 Discourse 开发环境的配置&#xff0c;如果你需要在生产环境中安装 Discourse &#xff0c;请访问页面&#xff1a;Install Discourse in production with the official, supported instructions - sysadmin - Discourse Meta 中的内容。 有关开发环境的设置英文原…

[Java EE初阶] 进程调度的基本过程

纪念Java EE初阶开篇文章,不放弃,不摆烂,踏平所有障碍吧!少年!奥利给!(操作系统这方面的所有文章均不作为操作系统的专业课知识学习) 文章目录1. 进程的概念2. PCB --- 进程控制块3. 并发与并行4. 进程调度的相关属性5. 内存管理总结1. 进程的概念 进程,就是跑起来的程序,我们…

【学习笔记】《Python深度学习》第七章:高级的深度学习最佳实践

文章目录1 Keras 函数式 API1.1 函数式 API 简介1.2 多输入模型1.3 多输出模型1.4 层组成的有向无环图1.5 共享层权重1.6 将模型作为层2 使用 Keras 回调函数 和 TensorBoard 检查并监控深度学习模型2.1 训练过程中将回调函数作用于模型2.2 TensorBoard 简介&#xff1a;Tensor…

【Lilishop商城】No3-4.模块详细设计,店铺店员(店铺店员、店铺部门、店铺角色)的详细设计

仅涉及后端&#xff0c;全部目录看顶部专栏&#xff0c;代码、文档、接口路径在&#xff1a; 【Lilishop商城】记录一下B2B2C商城系统学习笔记~_清晨敲代码的博客-CSDN博客 全篇会结合业务介绍重点设计逻辑&#xff0c;其中重点包括接口类、业务类&#xff0c;具体的结合源代码…

exfat文件系统

DBR&#xff1a; DBR偏移量 字段长度&#xff08;字节&#xff09; 说明 0x40 - 0x47 8 分区的起始扇区号&#xff08;隐藏扇区数&#xff09; 0x48 - 0x4F 8 分区总扇区数 0x50 - 0x53 4 FAT表起始扇区号&#xff08;从DBR到FAT表的扇区个数&#xff09; 0x54 - 0x57 4…

【Redis】持久化操作

一、RDB(Redis Database) 1、持久化 redis一般是将数据写到内存中&#xff0c;但也可以将数据写到磁盘中&#xff0c;这个过程称之为持久化 2、什么是RDB 在指定的时间间隔内将内存中的数据集快照写入磁盘中 3、RDB是如何执行备份操作的 redis会单独创建(fork)一个子进程进行…

FPGA 20个例程篇:18.SD卡存放音频WAV播放(下)

第七章 实战项目提升&#xff0c;完善简历 18.SD卡存放音频WAV播放&#xff08;下&#xff09; 进一步地我们再结合图1的示意图来分析wav_play模块的时序逻辑设计&#xff0c;大家可以清楚地看到WM8731在Right justified和主从时钟模式下&#xff0c;是先发左声道后发右声道数…

【LeetCode】专题一 二叉树层序遍历

二叉树层序遍历 在本文中&#xff0c;我将会选取LeetCode上二叉树层序遍历的多道例题&#xff0c;并给出解答&#xff0c;通过多道题我们就可以发现&#xff0c;二叉树的层序遍历并不复杂&#xff0c;并且有着共通点。 102. 二叉树的层序遍历 给你二叉树的根节点 root &…

【Labivew】简易计算器

&#x1f6a9;write in front&#x1f6a9; &#x1f50e;大家好&#xff0c;我是謓泽&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流&#x1f50e; &#x1f3c5;2021年度博客之星物联网与嵌入式开发TOP5&#xff5…

Secure CRT远程连接很快断线问题

问题描述 我们使用Secure CRT连接远程主机时可能会遇到几分钟没操作就无法操作了&#xff0c;需要断开重新连接&#xff0c;非常的麻烦&#xff0c;假如客户端或者服务端能够在快要超时的时候给对方发送一个心跳&#xff0c;得到对方响应就重置下超时时间&#xff0c;这样就能…

arm架构 --- 中断

ARM的异常 终止程序的正常执行过程而不得不去完成的一些特殊工作 中断是异常的一种&#xff0c;包括外部硬件产生的异常和芯片内部硬件产生的内部中断。 ARM有七种处理器模式&#xff0c;其中用户模式和系统模式之外的5钟处理器模式叫做异常模式&#xff0c;用户模式之外的6…

osgEarth示例分析——osgearth_terrainprofile

前言 osgearth_terrainprofile示例&#xff0c;涉及到一个新的类 TerrainProfileCalculator(地形轮廓计算器类)&#xff0c;用来计算两个点连线之间的地形数据。左下角会根据点击的起点和终点进行计算&#xff0c;并更新显示地形信息。 效果 拖动地球&#xff0c;到某一个视…

[附源码]Python计算机毕业设计SSM基于的智慧校园安防综合管理系统(程序+LW)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

软件安全测试-Web安全测试详解-XSS攻击

目录 1. XSS攻击 1.1 XSS攻击原理 1.2 XSS能做什么 1.3 XSS三种类型 1.4 XSS三种途径 1.5 XSS测试方法 1.5.1 查看代码 1.5.2 准备测试脚本 1.5.3 自动化测试XSS漏洞 1.5.4 XSS注入常用语句 1.6 XSS漏洞防范h3 1.6.1 对输入和URL参数进行过滤(白名单和黑名单) 1.6.…

tensorflow入门(一) 计算图、张量、会话的概念

1、计算图 计算图是tensorflow中最基本的一个概念&#xff0c;tensorflow中的所有计算都被转化成计算图上的节点。tensorflow的名字已经说明了它最重要的两个概念------tensor和flow。张量这个概念在数学或者物理学中可以有不同的解释&#xff0c;在tensorflow中&#xff0c;张…

基于tensorflow的深层神经网络(三)如何用tensorflow优化神经网络

1、神经网络优化算法 梯度下降算法主要用户优化单个参数的取值&#xff0c;而反向传播算法给出了一个高效的方式在所有参数上使用梯度下降算法&#xff0c;从而使神经网络模型在训练数据上的损失函数尽可能小。反向传播算法是训练神经网络的核心算法&#xff0c;它可以根据定义…

红黑树的插入过程

一棵红黑树是一种特殊的二叉查找树&#xff0c;具有以下性质&#xff1a; 每个节点要么是红色&#xff0c;要么是黑色。根节点是黑色。每个叶子节点&#xff08;NIL&#xff09;是黑色。如果一个节点是红色的&#xff0c;那么它的两个儿子都是黑色的。从任意一个节点到其每个叶…

71.qt quick-可伸展菜单-抽屉栏示例 通用QML界面(一键换肤)

在我们之前章节已经提供过了抽屉栏和菜单伸展栏: 63.qt quick-QML侧边滑动栏(不需要任何图片资源,支持自定义左右方向和大小)_诺谦的博客-CSDN博客_qml侧边栏68.qt quick-qml多级折叠下拉导航菜单 支持动态添加/卸载 支持qml/widget加载等_诺谦的博客-CSDN博客_qml下拉菜单 由…

三维家发生工商变更:注册资本减少46%,美凯龙、阿里等股东退出

近日&#xff0c;云工业软件服务商广东三维家信息科技有限公司&#xff08;下称“三维家”&#xff09;发生工商变更&#xff0c;注册资本由16.9254亿元变更为9亿元&#xff0c;同比减少46.83%。同时&#xff0c;包括红星美凯龙、阿里巴巴等多名股东退出&#xff0c;变更时间为…