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博客
相关文章:
Effective C++笔记之二十一:One Definition Rule(ODR)
ODR细节有点复杂,跨越各种情况。基本内容如下: ●普通(非模板)的noninline函数和成员函数、noninline全局变量、静态数据成员在整个程序中都应当只定义一次。 ●class类型(包括structs和unions)、模板&…...
探索未来:Transformer模型在智能环境监测的革命性应用
探索未来:Transformer模型在智能环境监测的革命性应用 在当今数字化时代,环境监测正逐渐从传统的人工检测方式转变为智能化、自动化的系统。Transformer模型,作为深度学习领域的一颗新星,其在自然语言处理(NLP&#x…...
Nginx中文URL请求404
这两天正在搞我的静态网站。方案是:从思源笔记Markdown笔记,用MkOcs build成静态网站,上传到到Nginx服务器。遇到一个问题:URL含有中文会404,全英文URL则正常访问。 比如: 设置了utf-8 ht…...
33. 动量法(Momentum)介绍
1. 背景知识 在深度学习的优化过程中,梯度下降法(Gradient Descent, GD)是最基本的方法。然而,基本的梯度下降法在实际应用中存在收敛速度慢、容易陷入局部最小值以及在高维空间中振荡较大的问题。为了解决这些问题,人…...
Python | Leetcode Python题解之第228题汇总区间
题目: 题解: class Solution:def summaryRanges(self, nums: List[int]) -> List[str]:def f(i: int, j: int) -> str:return str(nums[i]) if i j else f{nums[i]}->{nums[j]}i 0n len(nums)ans []while i < n:j iwhile j 1 < n …...
物联网应用,了解一点 WWAN全球网络标准
WWAN/蜂窝无线电认证,对跨地区应用场景,特别重要。跟随全球业务的脚步,我们像大唐先辈一样走遍全球业务的时候,了解一点全球化的 知识信息,就显得有那么点意义。 NA (北美):美国和加…...
如何指定多块GPU卡进行训练-数据并行
训练代码: train.py import torch import torch.nn as nn import torch.optim as optim from torch.utils.data import DataLoader, Dataset import torch.nn.functional as F# 假设我们有一个简单的文本数据集 class TextDataset(Dataset):def __init__(self, te…...
RK3568笔记三十三: helloworld 驱动测试
若该文为原创文章,转载请注明原文出处。 报着学习态度,接下来学习驱动是如何使用的,从简单的helloworld驱动学习起。 开始编写第一个驱动程序—helloworld 驱动。 一、环境 1、开发板:正点原子的ATK-DLRK3568 2、系统…...
【智能制造-14】机器视觉软件
CCD相机和COMS相机? CCD(Charge-Coupled Device)相机和CMOS(Complementary Metal-Oxide-Semiconductor)相机是两种常见的数字图像传感器技术,用于捕捉和处理图像。 CCD相机: CCD相机使用一种称为CCD的光电…...
MVC分页
public ActionResult Index(int ? page){IPagedList<EF.ACCOUNT> userPagedList;using (EF.eMISENT content new EF.eMISENT()){第几页int pageNumber page ?? 1;每页数据条数,这个可以放在配置文件中int pageSize 10;//var infoslist.C660List.OrderBy(…...
webGL可用的14种3D文件格式,但要具体问题具体分析。
hello,我威斯数据,你在网上看到的各种炫酷的3d交互效果,背后都必须有三维文件支撑,就好比你网页的时候,得有设计稿源文件一样。WebGL是一种基于OpenGL ES 2.0标准的3D图形库,可以在网页上实现硬件加速的3D图…...
HybridCLR原理中的重点总结
序言 该文章以一个新手的身份,讲一下自己学习的经过,大家更快的学习HrbirdCLR。 我之前的两个Unity项目中,都使用到了热更新功能,而热更新的技术栈都是用的HybridCLR。 第一个项目本身虽然已经集成好了热更逻辑(使用…...
昇思学习打卡-14-ResNet50迁移学习
文章目录 数据集可视化预训练模型的使用部分实现 推理 迁移学习:在一个很大的数据集上训练得到一个预训练模型,然后使用该模型来初始化网络的权重参数或作为固定特征提取器应用于特定的任务中。本章学习使用的是前面学过的ResNet50,使用迁移学…...
软件开发面试题C#,.NET知识点(续)
1.C#中的封装是什么,以及它的重要性。 封装(Encapsulation) 是面向对象编程(OOP)的一个基本概念。它指的是将对象的状态(属性)和行为(方法)绑定在一起,并且将…...
2019年美赛题目Problem A: Game of Ecology
本题分析: 本题想要要求从实际生物角度出发,对权力游戏中龙这种虚拟生物的生态环境和生物特性进行建模,感觉属于比较开放类型的题目,重点在于参考生物的选择,龙虽然是虚拟的但是龙的生态特性可以参考目前生物圈里存在…...
沙龙回顾|MongoDB如何充当企业开发加速器?
数据不仅是企业发展转型的驱动力,也是开发者最棘手的问题。前日,MongoDB携手阿里云、NineData在杭州成功举办了“数据驱动,敏捷前行——MongoDB企业开发加速器”技术沙龙。此次活动吸引了来自各行各业的专业人员,共同探讨MongoDB的…...
云端编码:将您的技术API文档安全存储在iCloud的最佳实践
云端编码:将您的技术API文档安全存储在iCloud的最佳实践 作为一名技术专业人士,管理不断增长的API文档库是一项挑战。iCloud提供了一个无缝的解决方案,允许您在所有设备上存储、同步和访问您的个人技术API文档。本文将指导您如何在iCloud中高…...
在Spring Boot项目中集成单点登录解决方案
在Spring Boot项目中集成单点登录解决方案 大家好,我是微赚淘客系统3.0的小编,也是冬天不穿秋裤,天冷也要风度的程序猿! 在现代的企业应用中,单点登录(Single Sign-On, SSO)解决方案是确保用户…...
Java-常用API
1-Java API : 指的就是 JDK 中提供的各种功能的 Java类。 2-Scanner基本使用 Scanner: 一个简单的文本扫描程序,可以获取基本类型数据和字符串数据 构造方法: Scanner(InputStream source):创建 Scanner 对象 Sy…...
Python从Excel表中查找指定数据填入新表
#读取xls文件中的数据 import xlrd file "原表.xls" wb xlrd.open_workbook(file) #读取工作簿 ws wb.sheets()[0] #选第一个工作表 data [] for row in range(7, ws.nrows): name ws.cell(row, 1).value.strip() #科室名称 total1 ws.cell(row, 2…...
[特殊字符] 智能合约中的数据是如何在区块链中保持一致的?
🧠 智能合约中的数据是如何在区块链中保持一致的? 为什么所有区块链节点都能得出相同结果?合约调用这么复杂,状态真能保持一致吗?本篇带你从底层视角理解“状态一致性”的真相。 一、智能合约的数据存储在哪里…...
使用VSCode开发Django指南
使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架,专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用,其中包含三个使用通用基本模板的页面。在此…...
SciencePlots——绘制论文中的图片
文章目录 安装一、风格二、1 资源 安装 # 安装最新版 pip install githttps://github.com/garrettj403/SciencePlots.git# 安装稳定版 pip install SciencePlots一、风格 简单好用的深度学习论文绘图专用工具包–Science Plot 二、 1 资源 论文绘图神器来了:一行…...
渗透实战PortSwigger靶场-XSS Lab 14:大多数标签和属性被阻止
<script>标签被拦截 我们需要把全部可用的 tag 和 event 进行暴力破解 XSS cheat sheet: https://portswigger.net/web-security/cross-site-scripting/cheat-sheet 通过爆破发现body可以用 再把全部 events 放进去爆破 这些 event 全部可用 <body onres…...
论文浅尝 | 基于判别指令微调生成式大语言模型的知识图谱补全方法(ISWC2024)
笔记整理:刘治强,浙江大学硕士生,研究方向为知识图谱表示学习,大语言模型 论文链接:http://arxiv.org/abs/2407.16127 发表会议:ISWC 2024 1. 动机 传统的知识图谱补全(KGC)模型通过…...
pikachu靶场通关笔记22-1 SQL注入05-1-insert注入(报错法)
目录 一、SQL注入 二、insert注入 三、报错型注入 四、updatexml函数 五、源码审计 六、insert渗透实战 1、渗透准备 2、获取数据库名database 3、获取表名table 4、获取列名column 5、获取字段 本系列为通过《pikachu靶场通关笔记》的SQL注入关卡(共10关࿰…...
安卓基础(aar)
重新设置java21的环境,临时设置 $env:JAVA_HOME "D:\Android Studio\jbr" 查看当前环境变量 JAVA_HOME 的值 echo $env:JAVA_HOME 构建ARR文件 ./gradlew :private-lib:assembleRelease 目录是这样的: MyApp/ ├── app/ …...
CSS设置元素的宽度根据其内容自动调整
width: fit-content 是 CSS 中的一个属性值,用于设置元素的宽度根据其内容自动调整,确保宽度刚好容纳内容而不会超出。 效果对比 默认情况(width: auto): 块级元素(如 <div>)会占满父容器…...
网站指纹识别
网站指纹识别 网站的最基本组成:服务器(操作系统)、中间件(web容器)、脚本语言、数据厍 为什么要了解这些?举个例子:发现了一个文件读取漏洞,我们需要读/etc/passwd,如…...
招商蛇口 | 执笔CID,启幕低密生活新境
作为中国城市生长的力量,招商蛇口以“美好生活承载者”为使命,深耕全球111座城市,以央企担当匠造时代理想人居。从深圳湾的开拓基因到西安高新CID的战略落子,招商蛇口始终与城市发展同频共振,以建筑诠释对土地与生活的…...
