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_H


class 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_H


class 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.mfbz.cn/a/788409.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

leetcode--验证二叉搜索树

leetcode地址&#xff1a;验证二叉搜索树 给你一个二叉树的根节点 root &#xff0c;判断其是否是一个有效的二叉搜索树。 有效 二叉搜索树定义如下&#xff1a; 节点的左 子树 只包含 小于 当前节点的数。 节点的右子树只包含 大于 当前节点的数。 所有左子树和右子树自身必…

基于与STM32的加湿器之雾化片驱动

基于与STM32的加湿器之雾化片驱动 加湿器是一种由电力驱动&#xff0c;用于增加环境湿度的家用电器。加湿器通过特定的方式&#xff08;如蒸发、超声波振动或加热&#xff09;将水转化为水蒸气&#xff0c;并将这些水蒸气释放到空气中&#xff0c;从而增加空气中的湿度。主要功…

java通过poi-tl导出word实战详细步骤

文章目录 与其他模版引擎对比1.引入maven依赖包2.新建Word文档exportWprd.docx模版3.编写导出word接口代码4.导出成果 poi-tl是一个基于Apache POI的Word模板引擎&#xff0c;也是一个免费开源的Java类库&#xff0c;你可以非常方便的加入到你的项目中&#xff0c;并且拥有着让…

自动化数据集成的BI工具,为你提供决策洞察力

传统的商业智能&#xff08;BI&#xff09;报表系统采用的是“业务提报表需求&#xff0c;IT进行开发”的模式。决策管理者和业务人员提出用报表等来展示经营管理数据的需求&#xff1b;接着IT响应需求&#xff0c;进行需求沟通、数据处理加工、报表开发等主体工作&#xff1b;…

企业化运维(7)_Zabbix企业级监控平台

官网&#xff1a;Zabbix :: The Enterprise-Class Open Source Network Monitoring Solution ###1.Zabbix部署### &#xff08;1&#xff09;zabbix安装 安装源 修改安装路径为清华镜像 [rootserver1 zabbix]# cd /etc/yum.repos.d/ [rootserver1 yum.repos.d]# vim zabbix.r…

PageDTO<T>,PageQuery,BeanUtils,CollUtils的封装

一、PageDTO<T> import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.fasterxml.jackson.annotation.JsonIgnore; import com.tianji.common.utils.BeanUtils; import com.tianji.common.utils.CollUtils; import com.tianji.common.utils.…

计算机网络——网络层(概念及IP地址划分)

目录 网络层概念 网络层向上层提供的两种服务 虚电路 网络提供数据报服务 虚电路服务与数据报服务的对比 网络层的两个层面 分组传送到路由器的运作 对网络层进行分层 网际协议IP 虚拟互联网络 IP地址 IP地址及其表示方法 IP地址的计算方式 IP地址的结构 …

【目标跟踪】CoTracker 环境配置

配置 CoTracker 环境 首先下载 conda&#xff0c;然后安装虚拟环境。 1.创建环境&#xff1a;如果环境不存在&#xff0c;你需要创建一个新的 conda 环境。可以使用以下命令创建名为 cotracker 的环境&#xff1a; conda create -n cotracker python3.x 其中 3.x 是你想要安…

欧姆龙安全PLC及周边产品要点指南

电气安全、自动化设备作业安全&#xff0c;向来是非常非常之重要的&#xff01;越来越多的客户在规划新产线、改造既有产线的过程中&#xff0c;明确要求设计方和施工方将安全考虑进整体方案中进行考虑和报价&#xff01;作为一名自动化电气工程师&#xff0c;尤其是高级工程师…

大话光学原理:4.散射:瑞利、拉曼、米氏和布里渊

这是一缕柔和的光&#xff0c;在空气的舞台上轻盈地跳跃。它悠然自得&#xff0c;在宁静的空间中缓缓前行。然而&#xff0c;一片细薄透明的介质挡住了它的脚步&#xff0c;它毫无预兆地撞上了这片障碍。在这短暂的接触中&#xff0c;它被分解成无数微小的粒子&#xff0c;被迫…

Python30 使用Gensim库实现Word2Vec对文本进行处理

1.Word2Vec Word2Vec 是一种将词语表示为向量的技术&#xff0c;能够捕捉词语之间的语义关系。它由 Google 的 Tomas Mikolov 等人在 2013 年提出&#xff0c;广泛应用于自然语言处理任务中。其核心概念主要包括&#xff1a; 词嵌入&#xff08;Word Embeddings&#xff09; …

【重大消息】报告称OpenAI的产品可经由微软的服务提供给中国客户

尽管OpenAI正在采取措施限制中国用户访问其平台&#xff0c;但一份最新报告称&#xff0c;中国用户仍可通过微软的Azure云计算平台访问该公司的产品。微软和OpenAI有着密切的合作关系&#xff0c;前者通过人工智能功能获得了独家产品访问权以拓展企业计算。最新的报道来自《The…

拓展神经网络八股(入门级)

自制数据集 minst等数据集是别人打包好的&#xff0c;如果是本领域的数据集。自制数据集。 替换 把图片路径和标签文件输入到函数里&#xff0c;并返回输入特征和标签 只需要把图片灰度值数据拼接到特征列表&#xff0c;标签添加到标签列表,提取操作函数如下&#xff1a; def…

25.无源蜂鸣器驱动设计

相对于有源蜂鸣器&#xff0c;无源蜂鸣器的成本更低&#xff0c;声音频率可控。而有源蜂鸣器因其内部 自带振荡源&#xff0c;只要加上适当的直流电源即可发声&#xff0c;程序控制较为方便。 &#xff08;1&#xff09;设计定义&#xff1a;设计一个无源蜂鸣器的驱动程序&…

基于泰坦尼克号生还数据进行 Spark 分析

基于泰坦尼克号生还数据进行 Spark 分析 在这篇博客中&#xff0c;我们将展示如何使用 Apache Spark 分析著名的泰坦尼克号数据集。通过这篇教程&#xff0c;您将学习如何处理数据、分析乘客的生还情况&#xff0c;并生成有价值的统计信息。 数据解析 • PassengerId &#…

ctfshow-web入门-文件上传(web164、web165)图片二次渲染绕过

web164 和 web165 的利用点都是二次渲染&#xff0c;一个是 png&#xff0c;一个是 jpg 目录 1、web164 2、web165 二次渲染&#xff1a; 网站服务器会对上传的图片进行二次处理&#xff0c;对文件内容进行替换更新&#xff0c;根据原有图片生成一个新的图片&#xff0c;这样…

EasyCVR视频汇聚平台:存储系统怎么选?分布式存储vs.集中式存储的区别在哪?

在当今的数字化时代&#xff0c;安防监控已成为维护社会秩序和公共安全的重要手段。随着监控设备的普及和监控数据的不断增加&#xff0c;如何高效、安全地存储和管理这些视频数据&#xff0c;成为了安防行业面临的重要挑战。EasyCVR视频存储系统凭借其卓越的性能和灵活的架构&…

综合安全防护

题目 1,DMZ区内的服务器,办公区仅能在办公时间内(9:00-18:00)可以访问,生产区的设备全天可以访问. 2,生产区不允许访问互联网,办公区和游客区允许访问互联网 3,办公区设备10.0.2.10不允许访问DMz区的FTP服务器和HTTP服务器,仅能ping通10.0.3.10 4,办公区分为市场部和研发部,研…

pnpm workspace使用教程【Monorepo项目】

目录 前言一、pnpm简介特点&#xff1a;对比 二、 创建项目添加文件 pnpm-workspace.yaml目录结构pnpm workspace: 协议修改配置文件执行 安装 三、命令解析执行包命令所有包操作命令 四、实例代码 前言 前面两篇&#xff0c;我们讲了 yarn workspace 和 lerna &#xff0c; …

局域网远程共享桌面如何实现

在局域网内实现远程共享桌面&#xff0c;可以通过以下几种方法&#xff1a; 一、使用Windows自带的远程桌面功能&#xff1a; 首先&#xff0c;在需要被控制的电脑上右键点击“此电脑”&#xff0c;选择“属性”。 进入计算机属性界面后&#xff0c;点击“高级系统设置”&am…