Effective C++笔记之二十一:One Definition Rule(ODR)

ODR细节有点复杂,跨越各种情况。基本内容如下:
●普通(非模板)的noninline函数和成员函数、noninline全局变量、静态数据成员在整个程序中都应当只定义一次。
●class类型(包括structs和unions)、模板(包括偏特化但是不包括全特化)、inline函数和inline变量在单个编译单元中最多定义一次,并且这些定义应该完全一样。
一个编译单元是源文件预处理后的结果,也就是说,它包含#include指令和宏拓展后的内容。与C语言一样,C++中所有的预处理指令都是以字符#开头,这些指令在编译之前进行处理。
本文将讨论一种违背ODR的典型情况:不同编译单元中包含同名结构体,结构体内函数定义相同,但数据成员不同
MyClass1.h

#ifndef MYCLASS1_H
#define MYCLASS1_Hclass MyClass1
{
public:MyClass1();
};#endif // MYCLASS1_H

MyClass1.cpp

#include "MyClass1.h"struct Point
{void setValue(int x, int y){this->x = x;this->y = y;}int z;int x, y;
};MyClass1::MyClass1()
{Point p;p.setValue(0, 0);
}

MyClass2.h

#ifndef MYCLASS2_H
#define MYCLASS2_Hclass MyClass2
{
public:MyClass2();
};#endif // MYCLASS2_H

MyClass2.cpp

#include "MyClass2.h"#include <iostream>struct Point
{void setValue(int x, int y){this->x = x;this->y = y;}int x, y;
};MyClass2::MyClass2()
{Point p;p.setValue(10, 10);std::cout << p.x << std::endl;
}

main.cpp

#include "MyClass1.h"
#include "MyClass2.h"int main()
{MyClass1 cl1;MyClass2 cl2;return 0;
}

显然,我们的预期打印结果是:10。
本人不同喜欢敲指令,这里IDE使用Qt Creator,Qt版本是5.12.6 MinGW32,编译器为g++。
在Debug模式下,且三个cpp文件的编译顺序是MyClass1.cpp->MyClass2.cpp->main.cpp,如下图所示

实际打印结果却是:0

依然在Debug模式下,编译顺序改为MyClass2.cpp->MyClass1cpp->main.cpp,如下图所示


实际打印结果是预期值:10

在Release模式下,且三个cpp文件的编译顺序是MyClass1.cpp->MyClass2.cpp->main.cpp
实际打印结果也是预期值:10
下面来分析为何和出现上述三种不同的情况,首先要明确以下四点:
1、直接在class {}中定义函数体的函数都是inline的。
2、inline在现代的意义并不是调用处展开函数(是否展开由编译器优化决定),而是允许在多个编译单元(obj文件)中出现相同的符号,链接时不会报符号重定义。如果在class外面定义非inline的函数体(A::A()这样的写法),链接是要报错的。
3、如果inline的符号有出现重复,链接器会随便选择一个。
4、inline的特性被广泛运用在纯hpp文件造轮子,将class的声明和实现都写在头文件中,哪里需要哪里include一下就好,非常方便,无需像原来那样又是h文件又是lib文件,还要保证各种编译条件匹配。
关于inline,详见:Effective C++笔记之十五:inline函数的里里外外
编译器如何决定是否将函数内联呢?
编译器决定是否将函数内联的过程称为内联函数优化。编译器会根据一定的规则和优化策略来决定是否将函数内联。以下是一些关键因素,可以影响编译器的决策:
●函数体积:如果函数体积较小,编译器更可能将其内联。内联函数可以减少函数调用的开销,提高代码执行效率。
●递归函数:递归函数通常不会被内联,因为递归调用可能导致大量的重复代码,从而增加程序的内存占用和执行时间。
●循环中的函数:在循环体内调用的函数也可能被内联。这样可以减少循环中的函数调用开销,提高代码执行效率。
●函数属性:编译器可能会根据函数的属性来决定是否内联。例如,如果函数具有“inline”属性,编译器可能更倾向于将其内联。
●编译器优化级别:编译器的优化级别也会影响其决策。较高的优化级别可能会导致编译器更倾向于内联函数,以提高代码执行效率。
●目标平台:编译器会根据目标平台的特性来决定是否内联函数。例如,在资源受限的平台上,编译器可能更倾向于减少内联,以减少程序的内存占用。
总之,编译器决定是否值得将函数内联取决于多种因素。编译器会根据这些因素以及优化策略来决定是否将函数内联,以提高代码执行效率和减少内存占用。
上面说过inline时是否展开取决于编译器优化,在Debug模式下,g++使用的优化级别是O0(默认选项):不开启优化,方便功能调试。可以明确的是,在O0等级下,内联不会真正发生。结合前面的现象,在Debug模式下,链接器都选择了较后参与编译的源文件中的setValue函数。
在Release模式下,g++使用的优化级别是O2,O2是常用的Release级别,该级别下几乎执行了所有支持的优化选项,它增加了编译时间,提高了程序的运行速度,会额外打开了一些优化标志,比如-finline-functions。结合前面的现象,在Release模式下,内联真正发生,函数在调用处展开,所以能得到正确结果,尽管如此,由于内联的非强制性,代码这样写依然是有隐患的。
如何判断内联函数有没有在调用处展开呢?方法见:[C++基础]016_内联函数到底有没有被嵌入到调用处呢?
除了自己写代码要遵循ODR,在使用第三方库时同样要注意,下面是一位网友反馈的情况。
为何同时用两个不同版本的RapidJSON会导致程序崩溃?
rapidjson是一个只包含.h文件就能用的库。意思是,它将所有的类定义写在了头文件里面。这种做法很常见。使得调用者非常方便,只要include 头文件就能玩耍了,不需要再包含.cpp/.lib或者.dll之类的东西。当你的项目里有2个cpp文件[通常遇到问题是因为这两个cpp文件只有一个是你写的,另一个是你引用的其他第三方库里的],A.cpp include了rapidjson_v1.h,B.cpp include了 rapidjson_v2.h。这下,在编译阶段时候,编译器发现:"咦?怎么有两个class rapidjson定义,一个在A.cpp里,一个在B.cpp里。用哪一个呢"。其实这是C++普遍存在的问题,在.h里面定义了一个class或者template等东东,这个头文件被include到多个cpp里,在这些cpp里原样展开,编译器在链接的时候,就会看到多个重复的定义,于是C++规定了ODR(One Definition Rule),简而言之:"看到这种重复定义的类,且这些类的代码又长得一模一样,编译器就随便选一个用就行了"。因为量重复的这些定义都长得一样,就随便选一个都行了。这模式一直正常工作。再回到rapidjson,原本你想要的结果是A.cpp 使用rapidjson_v1.h里的class rapidjson,B.cpp 使用 rapidjson_v2.h里的class rapidjson。结果现在编译器不管是A.cpp还是B.cpp,都给你用rapidjson_v1.h里的class rapidjson[也有可能是rapidjson_v2.h里的class rapidjson]。编译器以为长一样,随便选一个就能正常工作,结果却不能正常工作,应该是rapidjson不同版本间做了一些违背ODR的变动。
PS:
Debug版本和Release版本其实就是优化级别的区别,Debug称为调试版本,编译的结果通常包含有调试信息,没有做任何优化,方便开发人员进行调试,Release称为发布版本,不会携带调试信息,同时编译器对代码进行了很多优化,使代码更小,速度更快,发布给用户使用,给用户使用以更好的体验。但Release模式编译比Debug模式花的时间也会更多。

原文链接:Effective C++笔记之二十一:One Definition Rule(ODR)-CSDN博客 

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

钡铼4G无线RTU助力智慧能源发展实现电网远程调控

随着全球对清洁能源和高效能源管理的需求日益增长&#xff0c;智慧能源技术正逐渐成为推动可持续发展的重要驱动力。在这一背景下&#xff0c;钡铼4G无线远程终端单元正在为智慧能源的发展和电网的远程调控提供强有力的支持。 钡铼4G无线RTU&#xff1a;智慧能源的神经网络 钡…

顺序结构 ( 五 ) —— 数据输入输出 【互三互三】

文章目录 &#x1f341;序 &#x1f341;一、字符输入函数getchar &#x1f341;二、字符输出函数putchar &#x1f341;三、通过cout流输出数据 &#x1f341;四、通过cin流读入数据 &#x1f341;五、格式化输入函数scanf &#x1f341;六、格式化输出函数printf &…

【python】QWidget父子关系,控件显示优先级原理剖析与应用实战演练

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

【C语言】C语言编译链接和Win32API简单介绍

目录 翻译环境和运行环境翻译环境编译器预处理&#xff08;预编译&#xff09;编译链接 执行环境 Win32API是什么控制台程序控制台获取坐标COORDGetStdHandle函数GetConsoleCursorinfo函数CONSOLE_CURSOR_INFOSetConsoleCursorInfo函数SetConsoleCursorPostion函数GetAsyncKeyS…

数字化时代的供应链管理综合解决方案

目录 引言背景与意义供应链管理综合解决方案的目标 &#x1f4c4;供应链管理系统主要功能系统优势 &#x1f4c4;物流管理系统主要功能系统优势 &#x1f4c4;订单管理系统主要功能应用场景 &#x1f4c4;仓储管理系统系统亮点主要功能系统优势 &#x1f4c4;商城管理系统主要功…

【MyBatis】——入门基础知识必会内容

&#x1f3bc;个人主页&#xff1a;【Y小夜】 &#x1f60e;作者简介&#xff1a;一位双非学校的大二学生&#xff0c;编程爱好者&#xff0c; 专注于基础和实战分享&#xff0c;欢迎私信咨询&#xff01; &#x1f386;入门专栏&#xff1a;&#x1f387;【MySQL&#xff0…

MySql性能调优01-[数据结构和索引]

数据结构和索引 什么是索引索引的种类常见索引数据结构和区别二叉树 红黑树 什么是索引 索引的种类 在Mysql中索引是在存储引擎层实现的&#xff0c;而不是在服务层实现的 按数据结构分&#xff1a;Btree索引、Hash索引、Full-text索引按存储结构分&#xff1a;聚簇索引、非聚…

数据结构——约瑟夫环C语言链表实现

约瑟夫环问题由古罗马史学家约瑟夫&#xff08;Josephus&#xff09;提出&#xff0c;他参加并记录了公元66—70年犹太人反抗罗马的起义。在城市沦陷之后&#xff0c;他和40名死硬的将士在附近的一个洞穴中避难。起义者表示“宁为玉碎不为瓦全”&#xff0c;约瑟夫则想“留得青…

go语言Gin框架的学习路线(六)

gin的路由器 Gin 是一个用 Go (Golang) 编写的 Web 框架&#xff0c;以其高性能和快速路由能力而闻名。在 Gin 中&#xff0c;路由器是框架的核心组件之一&#xff0c;负责处理 HTTP 请求并将其映射到相应的处理函数上。 以下是 Gin 路由器的一些关键特性和工作原理的简要解释…

第十八章 Express multer 文件上传

本章将学习Express multer 文件上传 &#xff0c;因为Nest 的文件上传是基于 Express 的中间件 multer 实现的&#xff0c;所以在学习 Nest 文件上传之前&#xff0c;我们先学习下 multer 包 首先先创建 multer-test 文件夹执行下面代码 创建package.json npm init -y接着安装…

单例模式的简单理解

单例模式 前言一、单例模式是什么二、单例模式的使用饿汉模式单线程下的懒汉模式多线程下的懒汉模式&#xff08;优化懒汉模式&#xff09;加锁 三、总结 前言 设计模式是将一些经典的问题场景进行整合归纳&#xff0c;并提供一些解决方案&#xff0c;相当于一种“套路”。 熟…

数据仓库介绍_维度表(三)

维度表概述 维度表是维度建模的基础和灵魂。前文提到&#xff0c;事实表紧紧围绕业务过程进行设计&#xff0c;而维度表则围绕业务过程所处的环境进行设计。维度表主要包含一个主键和各种维度字段&#xff0c;维度字段称为维度属性。 表设计步骤 确定维度&#xff08;表&…

SQL 针对上面的salaries表emp_no字段创建索引idx_emp_no

系列文章目录 文章目录 系列文章目录前言 前言 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站&#xff0c;这篇文章男女通用&#xff0c;看懂了就去分享给你的码吧。 描述 针对salaries…

【开源合规】开源许可证风险场景详细解读

文章目录 前言关于BlackDuck许可证风险对比图弱互惠型许可证举个例子具体示例LGPL系列LGPL-2.0-onlyLGPL-2.0-or-laterLGPL-2.1-onlyLGPL-2.1-or-laterLGPL-3.0-onlyLGPL-3.0-or-laterMPL系列MPL-1.0MPL-1.1MPL-2.0EPL系列EPL-1.0EPL-2.0互惠型许可证GPL系列GPL-1.0GPL-2.0GPL-…

3.相机标定原理及代码实现(opencv)

1.相机标定原理 相机参数的确定过程就叫做相机标定。 1.1 四大坐标系及关系 &#xff08;1&#xff09;像素坐标系&#xff08;单位&#xff1a;像素&#xff08;pixel&#xff09;&#xff09; 像素坐标系是指相机拍到的图片的坐标系&#xff0c;以图片的左上角为坐标原点&a…

合合信息大模型加速器亮相WAIC大会:文档解析与文本识别新突破

合合信息大模型加速器亮相WAIC大会&#xff1a;文档解析与文本识别新突破 文章目录 合合信息大模型加速器亮相WAIC大会&#xff1a;文档解析与文本识别新突破前言合合信息TextIn平台&#xff1a;智能文档处理的领军者文档解析引擎&#xff1a;百页文档秒级处理大模型的发展背景…

【机器学习】独立成分分析(ICA):解锁信号的隐秘面纱

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 独立成分分析&#xff08;ICA&#xff09;&#xff1a;解锁信号的隐秘面纱引言I…

若依 ruoyi-vue SpringBoot highlight-textarea 输入框敏感词关键词高亮标红(二)

参考文章&#xff0c;非常感谢大佬的分享 实现可高亮的输入框 — HighlightTextarea GitHub:highlight-textarea 可看作者上一篇文章 若依 ruoyi-vue SpringBoot聊天敏感词过滤sensitive-word&#xff08;一&#xff09; 效果图 审核时&#xff0c;输入框高亮敏感词&#xff…

vue3 + tsx 表格 Action 单独封装组件用法

前言 先上图看右侧列 action 的 UI 效果&#xff1a; 正常来说&#xff0c;如果一个表格的附带 action 操作&#xff0c;我们一般会放在最右侧的列里面实现&#xff0c;这个时候有些UI 框架支持在 SFC 模板里面定义额外的 solt&#xff0c;当然如果不支持&#xff0c;更通用的…

LabVIEW实现LED显示屏视觉检测

为了满足LED显示屏在生产过程中的严格质量检测需求&#xff0c;引入自动化检测系统是十分必要的。传统人工检测方式存在检测强度高、效率低、准确性差等问题&#xff0c;自动化检测系统则能显著提高检测效率和准确性。视觉检测系统的构建主要包含硬件和软件两个部分。 视觉系统…