深入浅出之STL源码分析6_模版编译问题

1.模版编译原理

当我们在代码中使用了一个模板,触发了一个实例化过程时,编译器就会用模板的实参(Arguments)去替换(Substitute)模板的形参(Parameters),生成对应的代码。同时,编译器会根据一定规则选择一个位置,将生成的代码插入到这个位置中,这个位置被称为 POI(point of instantiation)。由于要做替换才能生成具体的代码,因此 C++ 要求模板的定义对它的 POI 一定要是可见的。换句话说,在同一个翻译单元(Translation Unit)中,编译器一定要能看到模板的定义,才能对其进行替换,完成实例化。

2.包含模型

因此最常见的做法是,我们会将模板定义在头文件中,然后再源文件中 #include 头文件来获取该模板的定义。这就是模板编程中的包含模型(Inclusion Model)。

所以我们一般情况下,对于模版会把定义放在头文件中。但是这样操作会带来缺点。这个也对应着显示实例化的优点。

现在的一些 C++ 库,整个项目中就只有头文件,没有源文件,库的逻辑全部由模板实现在头文件中。而且这种做法似乎越来越流行,在 GitHub 和 boost 中能看到很多很多。我想原因一个是 C++ 缺乏一个官方的 package manager,这样发布的软件包更易使用(include就行了);另一个就是模板实例化的这种要求,使得包含模型成为泛型编程中组织代码最容易的方式。

但包含模型也有自身的问题。在一个翻译单元(Translation Unit)中,同一个模板实例只会被实例化一次。也就是对同一个模板传入相同的实参,编译器会先检查是否已实例化过,如果是则使用之前实例化的结果。但在不同的翻译单元中,相同实参的模板会被实例化多次,从而产生多个相同的类型、函数和变量

这带来两个问题:

2.1 链接时的重定义问题

如果不加以处理,这些相同的实体会被链接器认为是重定义的符号,这违反了ODR(One Definition Rule)。对这个问题的主流解决方案是为模板实例化生成的实体添加特殊标记,链接器在链接时对有标记的符号做特殊处理。例如在 GNU 体系下,模板实例化生成的符号都被标记为弱符号(Weak Symbol)。不需要我们参与,连接器已经为我们解决了这个问题。

对于普通的函数和类,连接器是不会处理的。会报重定义的错误。

同时这个因为每一个编译单元都有实例化,会带来代码膨胀。

2.2 编译时长的问题

同一个模板传入相同实参在不同的编译单元下被实例化了多次,这是不必要的,浪费了编译的时间。

3.分离模型

就是将声明放在头文件,实现放在.cpp中,并对其进行显示实例化,为什么要进行显示实例化,我在后面会详细的介绍。

显示实例化有如下的优点:

1. 降低编译器构建的时间

2.降低代码膨胀

3.针对发布lib,可以隐藏头文件

当然缺点也很明显,你要用到哪个模版类型,你提前事先都得很清楚,并且随着代码的累加,你都要动态的维护。

4.验证前面说的结论

1.如果在翻译单元的编译期间,能够看到模版的定义,就会在当前单元进行实例化。

也就是这个是一个多余的动作,就是能看见就顺便实例化了,调用函数正常。

2.如果在当前翻译单元看不到模版的定义,则只会调用这个函数,也就是认为声明在这个.h文件中,定义不在,链接的时候再去找。

3.正常的编译期就会生成对成员函数的调用,只是能看到模版的定义时,在本翻译单元的时候,才会进行实例化。如果在本翻译单元没有定义,则会在链接阶段再去找定义。

4.只要是实例化一定是在编译阶段,只是在你这个单元是否可见。

以上说的这几点,主要是针对包含模型和分离模型的对比来说的。

下面我们看下具体的例子。

我们先来解释第一句话:

//1.如果在翻译单元的编译期间,能够看到模版的定义,就会在当前单元进行实例化。
//也就是这个是一个多余的动作,就是能看见就顺便实例化了,调用函数正常。
//我们定义写一个main.cpp,再写一个function_template.h,然后在function_template.h中写一个实现的函数模版
// main.cpp
#include <iostream>
//#include "zhang.h"
#include "function_template.h"
int main() {int aa = 12;//Zhang temp;int num = system_latency_get_hardware_time(aa);std::cout<<num<<std::endl;return 0;
}
//function_template.h
template <typename Message>
int system_latency_get_hardware_time(const Message& message) {int a = 16;int b = 17;return a+b;
}
// 编译指令如下:
g++ -std=c++17 main.cpp   -o main
// 编译通过,执行./main
33
// 我们再来看下,分步操作会怎么样,我先生成汇编代码.
g++ -std=c++17 -S main.cpp -o main.s

查看对应的汇编代码,发现在汇编里已经出现了 system_latency_get_hardware_time

的定义

//我们修改 function_template.h的代码
// function_template.h
template <typename Message>
int system_latency_get_hardware_time(const Message& message);
// 然后再进行汇编操作
g++ -std=c++17 -S main.cpp -o main.s

这个时候我们汇编不会报错,正如我们第二点所说.

2.如果在当前翻译单元看不到模版的定义,则只会调用这个函数,也就是认为声明在这个.h文件中,定义不在,链接的时候再去找。

关于这个函数搜索只能找到这一条数据。

而我们现在继续向下,链接会怎么样,应该会报链接错误,我们试试.

// 为了少打几个指令,直接进行一步
g++ -std=c++17 main.cpp   -o main
这个时候,在链接的时候,就会报错g++ -std=c++17 main.cpp   -o main
/tmp/ccgzva3Q.o: In function `main':
main.cpp:(.text+0x26): undefined reference to `int system_latency_get_hardware_time<int>(int const&)'
collect2: error: ld returned 1 exit status

这个错误,我们是可以接受的,因为我们确实没有定义.

那我们想把,模版的定义,放到.cpp中可以吗?

// 在刚才的基础上
// function_template.h
template <typename Message>
int system_latency_get_hardware_time(const Message& message);
// add function_template.cpp
#include "function_template.h"
template <typename Message>
int system_latency_get_hardware_time(const Message& message) {int a = 16;int b = 17;return a+b;
}
// 然后我在编译代码的时候,链接下function_template.cpp是不是就可以了呢?
// g++ -std=c++17 main.cpp  function_template.cpp -o main
/tmp/ccsWDsr1.o: In function `main':
main.cpp:(.text+0x26): undefined reference to `int system_latency_get_hardware_time<int>(int const&)'
collect2: error: ld returned 1 exit status

我们发现还是不行,这个其实很好理解,因为在 另外一个编译单元(一个cpp文件就是一个编译单元), function_template.cpp中,这个不会生成这个函数,因为模版没有被实例话,那么方法来了,我们来进行下显示的实例话。

// function_template.cpp
#include "function_template.h"
template <typename Message>
int system_latency_get_hardware_time(const Message& message) {int a = 16;int b = 17;return a+b;
}
// 显示实例话  这个必须放在定义的后面,在.h和.cpp里无所谓.
template int system_latency_get_hardware_time<int> (const int&);

再进行编译,g++ -std=c++17 main.cpp function_template.cpp -o main

我们发现可以编译通过了,说明在 function_template.cpp中,会生成这个函数,那么我们没有给函数体,函数体是谁呢?很显然是是使用 primary template的函数体,因为执行程序的输出是 33.

到现在为止,1,2,3,4 点我都解释清楚了。

这里要主要一个细节,就是类模版的全特化和函数模版的全特化还有些不同,类模版如果全特化以后,还需要进行实例化才能生成代码,但是函数模版的全特化后,不需要实例化了,直接可以生成代码,具体可以参考我的下一篇博文 深入浅出之STL源码分析5_模版实例化与全特化-CSDN博客

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

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

相关文章

无人甘蔗小车履带式底盘行走系统的研究

1.1 研究背景与意义 1.1.1 研究背景 甘蔗作为全球最重要的糖料作物之一&#xff0c;在农业经济领域占据着举足轻重的地位。我国是甘蔗的主要种植国家&#xff0c;尤其是广西、广东、云南等地&#xff0c;甘蔗种植面积广泛&#xff0c;是当地农业经济的重要支柱产业。甘蔗不仅…

LVGL(lv_slider滑动条)

文章目录 一、lv_slider 是什么&#xff1f;二、创建一个滑块设置滑块的范围和初始值 三、响应滑块事件四、设置样式示例&#xff1a;更改滑块颜色和滑块按钮样式 五、纵向滑块&#xff08;垂直方向&#xff09;六、双滑块模式&#xff08;范围选择&#xff09;七、获取滑块的值…

每日算法-250511

每日算法 - 250511 记录一下今天刷的几道LeetCode题目&#xff0c;主要是关于贪心算法和数组处理。 1221. 分割平衡字符串 题目 思路 贪心 解题过程 我们可以遍历一次字符串&#xff0c;维护一个计数器 balance。当遇到字符 L 时&#xff0c;balance 增加&#xff1b;当遇…

Keepalived + LVS + Nginx 实现高可用 + 负载均衡

目录 Keepalived Keepalived 是什么&#xff08;高可用&#xff09; 安装 Keepalived LVS LVS 是什么&#xff08;负载均衡&#xff09; 安装 LVS Keepalived LVS Nginx 实现 高可用 负载均衡 Keepalived Keepalived 是什么&#xff08;高可用&#xff09; Keepaliv…

【杂谈】-DeepSeek-GRM:让AI更高效、更普及的先进技术

DeepSeek-GRM&#xff1a;让AI更高效、更普及的先进技术 文章目录 DeepSeek-GRM&#xff1a;让AI更高效、更普及的先进技术1、DeepSeek-GRM&#xff1a;先进的AI框架解析2、DeepSeek-GRM&#xff1a;AI开发的变革之力3、DeepSeek-GRM&#xff1a;广泛的应用前景4、企业自动化解…

【MySQL】页结构详解:页的大小、分类、头尾信息、数据行、查询、记录及数据页的完整结构

&#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/2301_779549673 &#x1f4e2;博客仓库&#xff1a;https://gitee.com/JohnKingW/linux_test/tree/master/lesson &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; &…

【FreeRTOS】基于G431+Cubemx自用笔记

系列文章目录 留空 文章目录 系列文章目录前言一、从头开始创建一个FreeRTOS工程1.1 在 "Timebase Source" 中&#xff0c;选择其他TIM1.2 配置FreeRTOS的参数1. 3 添加任务 二、动态任务的创建/删除2.1 函数介绍2.1.1 创建动态任务xTaskCreate()2.1.2 创建静态任务…

LVGL(lv_bar进度条)

文章目录 一、lv_bar 是什么&#xff1f;二、基本使用创建一个进度条设置进度值 三、条形方向与填充方向四、范围模式&#xff08;Range&#xff09;五、事件处理&#xff08;可选&#xff09;六、自定义样式&#xff08;可选&#xff09;七、综合示例八、配合 lv_timer 或外部…

AI对话小技巧

角色设定&#xff1a;擅于使用 System 给 GPT 设定角色和任务&#xff0c;如“哲学大师"指令注入&#xff1a;在 System 中注入常驻任务指令&#xff0c;如“主题创作"问题拆解&#xff1a;将复杂问题拆解成的子问题&#xff0c;分步骤执行&#xff0c;如&#xff1a…

C++ 核心基础:数字、数组、字符串、指针与引用详解

C++ 核心基础:数字、数组、字符串、指针与引用详解 1. C++ 基础语法1.1 标识符与保留字1.2 数据类型概述1.3 基本输入输出2.1 基本整数类型(int、short、long、long long)2.2 无符号整数类型(unsigned int、unsigned short、unsigned long、unsigned long long)2.3 整数类…

HarmonyOS运动开发:如何集成百度地图SDK、运动跟随与运动公里数记录

前言 在开发运动类应用时&#xff0c;集成地图功能以及实时记录运动轨迹和公里数是核心需求之一。本文将详细介绍如何在 HarmonyOS 应用中集成百度地图 SDK&#xff0c;实现运动跟随以及运动公里数的记录。 一、集成百度地图 SDK 1.引入依赖 首先&#xff0c;需要在项目的文…

如何理解k8s中的controller

一、基本概念 在k8s中&#xff0c;Controller&#xff08;控制器&#xff09;是核心组件之一&#xff0c;其负责维护集群状态并确保集群内的实际状态与期望状态一致的一类组件。控制器通过观察集群的当前状态并将其与用户定义的期望状态进行对比&#xff0c;做出相应的调整来实…

《Go小技巧易错点100例》第三十二篇

本期分享&#xff1a; 1.sync.Map的原理和使用方式 2.实现有序的Map sync.Map的原理和使用方式 sync.Map的底层结构是通过读写分离和无锁读设计实现高并发安全&#xff1a; 1&#xff09;双存储结构&#xff1a; 包含原子化的 read&#xff08;只读缓存&#xff0c;无锁快…

【MySQL】行结构详解:InnoDb支持格式、如何存储、头信息区域、Null列表、变长字段以及与其他格式的对比

&#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/2301_779549673 &#x1f4e2;博客仓库&#xff1a;https://gitee.com/JohnKingW/linux_test/tree/master/lesson &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; &…

LabVIEW多通道并行数据存储系统

在工业自动化监测、航空航天测试、生物医学信号采集等领域&#xff0c;常常需要对多个传感器通道的数据进行同步采集&#xff0c;并根据后续分析需求以不同采样率保存特定通道组合。传统单线程数据存储方案难以满足实时性和资源利用效率的要求&#xff0c;因此设计一个高效的多…

【Linux系列】bash_profile 与 zshrc 的编辑与加载

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

针对Mkdocs部署到Githubpages加速访问速度的一些心得

加速网站访问的一些心得 在使用 MkDocs 构建网站时&#xff0c;为了提高访问速度&#xff0c;我们可以采取以下一些措施&#xff1a; 1. 优化图片 使用合适的图片格式&#xff0c;如 WebP、JPEG2000 等&#xff0c;减少图片文件大小&#xff0c;从而加快加载速度。 可以使用…

Mysql中切割字符串作为in的查询条件

问题&#xff1a;需要将一个字符串切割成数组作为in的查询条件&#xff0c;如&#xff1a; select * from table_1 where name in (select slit(names) from table_2 where id 3); names 返回的格式是’name1,name2,name3…,需要将name按照逗号切割作为in的查询条件&#xff1b…

云计算中的虚拟化:成本节省、可扩展性与灾难恢复的完美结合

云计算中虚拟化的 4 大优势 1. 成本效益 从本质上讲&#xff0c;虚拟化最大限度地减少了硬件蔓延。团队可以将多个虚拟机整合到单个物理主机上&#xff0c;而不是为每个工作负载部署单独的服务器。这大大减少了前期硬件投资和持续维护。 结果如何&#xff1f;更低的功耗、更低…

Linux : 多线程【线程概念】

Linux &#xff1a; 多线程【线程概念】 &#xff08;一&#xff09;线程概念线程是什么用户层的线程linux中PID与LWP的关系 (二) 进程地址空间页表(三) 线程总结线程的优点线程的缺点线程异常线程用途 &#xff08;一&#xff09;线程概念 线程是什么 在一个程序里的一个执行…