【C++】关于C++模板的分离编译问题
文章目录
- 1.阐述模板的实例化和重复定义问题
- 2.分离编译可能出现的问题
- 3.解决方法
- 将函数模板的定义放到头文件中
- 模板定义的位置显式实例化
- 模板总结
1.阐述模板的实例化和重复定义问题
C++模板是一种非常强大的工具,可以为我们提供通用的代码实现方式。然鹅,在使用模板时会涉及到模板的实例化和重复定义的问题。为了避免这些问题并提高编译效率,C++提供了模板分离编译的机制。
1.模板为什么会涉及到实例化和重复定义的问题?
C++的模板时一种通用的代码实现方式,可以根据不同的类型参数生成具体的代码实例。当程序使用一个模板时,编译器将根据其具体的类型参数生成对应的代码实例,这个过程称为模板的实例化。
在模板实例化时,编译器会根据模板定义生成对应的函数或类,并在程序中调用或实例化这些函数或类。然鹅,由于模板的定义通常都放在头文件中,当多个源文件包含相同的头文件时,就会出现重复定义的问题。
以下是一个简单的示例代码,演示了在两个源文件中包含相同的头文件时,引起的重复定义错误:且该头文件中定义了一个函数模板:
// add.h
template<typename T>
T add(T a, T b) {return a + b;
}
// main1.cpp
#include "add.h"int main() {int a = 1, b = 2;int c = add(a, b);return 0;
}
// main2.cpp
#include "add.h"int main() {double a = 1.5, b = 2.5;double c = add(a, b); // error: redefinition of 'add'return 0;
}
在上述示例代码中,我们定义了一个名为add的函数模板,并在两个不同的源文件中分别包含相同的头文件add.h。当编译器在对这两个源文件进行编译时,它会对同一个函数模板进行多次实例化,从而导致重复定义错误。
error: redefinition of 'add'
2.但是它们实例化出的是两个不同类型的add函数呀,为什么有重复定义问题呢?
是的,没错。实例化出来的两个add函数,一个是int类型的,另一个是double类型的。但是这并不是导致重复定义问题的根本原因。
在C++中,函数模板的定义通常都放在头文件中,而头文件可能被多个源文件包含,当多个源文件包含相同的头文件时,其中的函数模板定义也会被多次包含,从而引发重定义问题。
具体地说,在上述实例中,编译器会对头文件add.h进行两次编译,并生成两个不同的目标文件。然后,编译器试图将两个目标文件链接到一起时,就会发现它们之间存在重复定义的符号,从而导致连接错误。重复定义的符号指的是在多个目标文件中都存在,名称相同但实体不同的符号。在C++中,符号通常是函数名,变量名,类名。
在上述示例中,我们定义了一个名为add的函数模板,在两个不同的源文件对其进行了示例化。当编译器将这两个源文件编译成目标文件时,它们分别包含了一个名为add<int和add<double(<>这个符号打不出来,见谅)的符号。然鹅,当我们试图将这两个目标文件链接到一起时,就会发现这两个符号名称相同,但实体不同,所以会导致链接错误。
2.分离编译可能出现的问题
开头说过,在使用模板时会涉及到模板的实例化和重复定义的问题。为了避免这些问题并提高编译效率,C++提供了模板分离编译的机制。
1.什么是分离编译模式?
一个项目由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译。
2.模板的分离编译可能会出现的问题(分析)
在C++中,模板分离编译是指将模板的声明和定义分开存放在不同的文件中,以避免多个源文件中重复定义同一个模板的问题。然而,在实践中,模板分离编译常常会带来一些问题,,主要包括以下几个方面:
- 链接错误:由于模板被分成了多个文件,如果链接时遗漏了某个模板的定义,就会导致链接错误。
- 多次实例化:由于模板的定义通常都写在头文件中,如果多个源文件包含相同的头文件,就可能导致同一个模板被多次实例化,从而增加编译时间和代码大小。
- 可读性差:模板分离编译会导致模板的声明和定义分散在不同的文件中,使得代码的可读性变差。
我们都知道,程序运行起来一般要以下4个步骤:
- 预处理:头文件展开,去注释,宏替换,条件编译等。
- 编译:检查代码的规范性,是否有语法错误等,确定代码实际要做的工作,在检查无误后,将代码翻译成汇编语言。
- 汇编:把编译阶段生成的文件转成目标文件obj
- 链接:将生成的各个目标文件进行链接,生成可执行文件。
多次实例化的问题,我们在前面已经讲解的很清楚了,我们现在来认识一下在模板的分离编译中,可能会遇到的链接问题。
在C++程序设计中,在一个源文件中定义某个函数,然后在另一个源文件中使用该函数,这是一种非常普遍的做法。但是,如果定义和调用一个函数模板时也采用这种方式,会发生编译错误。下面的程序由三个文件组成:add.h用来对函数模板进行申明,add.cpp用来定义函数模板,main.cpp包含add.h头文件并调用相应的函数模板。
// a.h
template<class T>
T Add(const T& left, const T& right);// a.cpp
template<class T>
T Add(const T& left, const T& right)
{
return left + right;
}// main.cpp
#include"a.h"
int main()
{
Add(1, 2);
Add(1.0, 2.0);
return 0;
}
这是一个结构非常清晰的程序,但是它不能通过编译。在VS2017下的出错信息是:

这是很典型的链接问题,那么原因就出在分离编译的模式上。在分离编译模式下,a.cpp会生成一个目标文件a.obj,由于在a,cpp文件中,并没有发生函数模板调用,所以不会把函数模板示例化成int或double类型,那么在a.obj中就找不到模板函数的实现代码,所以在链接时就会出现错误。
在main.cpp中,虽然函数模板被调用,但是由于没有模板代码,也不能将其实例化。在main.obj中找不到模板函数int add(…),在链接时就会出现函数未定义的错误。
3.解决方法
将函数模板的定义放到头文件中
将声明和定义放到一个文件 “xxx.hpp” 里面或者xxx.h其实也是可以的。推荐使用这种。这样的话,只要包含了这个头文件,就会把函数模板的代码包含进来,若发生函数调用,可以直接依据类型进行实例化。这种方法比较推荐,但是也有不足之处。
- 将函数定义写在头文件中,暴露了函数的实现细节。
- 不符合分离编译模式的规则。
模板定义的位置显式实例化
解决代码如下:
//Add.h
#include<iostream>
using namespace std;
//函数模板声明
template<class T>
T Add(const T& x,const T& y);//Add.cpp
template<class T>
T Add(const T& x,const T& y)
{return x+y;
}
//显示实例化
template int Add(const int& x,const int& y);
template double Add(const double& x,const double& y);//main.cpp
#include"Add.h"
int main()
{//调用函数模板实例化的函数cout<<Add(10,20)<<endl;cout<<Add(10.2,10.2)<<endl;return 0;
}
上述代码,在Add.cpp文件中对函数模板进行显示实例化,这样函数模板就可以生成对应的函数,这样再链接时就不会出错了。
除了这两种方法,还有其它方法,如:
1.使用模板库:通过将模板定义封装到库文件中,可以避免多个源文件中对同一模板的重复定义,并提高代码重用性。
2.模板导出(export):在模板声明时使用export关键字,可以告诉编译器只生成一个模板示例,避免多次实例化统一模板。
3.内联函数:将模板函数定义为内联函数可以避免链接错误,同时减少函数调用的开销。
模板总结
【优点】
- 模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库STL因此而产生。
- 增强了代码的灵活性。
【缺陷】
- 模板会导致代码膨胀问题,也会导致编译时间变长。
- 出现模板编译错误时,错误信息非常凌乱,不易定位。
相关文章:
【C++】关于C++模板的分离编译问题
文章目录1.阐述模板的实例化和重复定义问题2.分离编译可能出现的问题3.解决方法将函数模板的定义放到头文件中模板定义的位置显式实例化模板总结1.阐述模板的实例化和重复定义问题 C模板是一种非常强大的工具,可以为我们提供通用的代码实现方式。然鹅,在…...
小应用记账本-第2章-数据库设计
小应用记账本-第2章-数据库设计 在上一章《小应用记账本-第1章-需求分析》已经罗列了我们需要的功能,因为很简单,所以这一章就来设计数据库吧。 Account表:账户表 字段名类型说明取值idint账户idaccount_namevarchar账户名称remaining_sumd…...
Spring Boot+Vue前后端分离项目练习06之网盘项目创建vue项目
1.安装环境 构建vue项目,需要提前安装相应的环境,这里主要就是node,npm和Vue CLl。 #1、安装nodejs brew install nodejs #2、再执行下面命令来安装npm(npm是开发nodejs时所用的依赖库) brew install npm #3、安装vue cli npm install -g v…...
Python - 单元测试
python-单元测试1 Unittest2 Pytest3 两者区别断言方面用例执行编写规则前后置操作setUp, setUpclass, setUpmodule 区别4 实战操作unittest:pytest:1 Unittest unittest属于python的内置框架,支持多种自动化测试用例的编写,以及支持用例前置条件和后置…...
特权级那些事儿-实模式下分段机制首次出现的原因
前言: 操作系统的特权级模块在整个操作系统的学习中应该算的上是最难啃的了,提到特权级就要绕不开保护模式下的分段机制;如果想要彻底弄明白就要对比实模式下的分段机制有什么缺陷。这就衍生出很多问题如:什么是实模式?…...
详解Vue安装与配置(2023)
文章目录一、官网下载node.js二、安装Node.js三、环境配置四、idea导入vue项目五、IDEA添加Vue.js插件一、官网下载node.js Vue是前端开发框架。搭建框架,首先要搭建环境。搭建Vue的环境工具:node.js(JavaScript的运行环境)&…...
TypeScript深度剖析:Vue项目中应用TypeScript?
一、前言 与link类似 在VUE项目中应用typescript,我们需要引入一个库vue-property-decorator, 其是基于vue-class-component库而来,这个库vue官方推出的一个支持使用class方式来开发vue单文件组件的库 主要的功能如下: metho…...
linux面试高级篇
题目目录1.虚拟机常用有几种网络模式?请简述其工作原理或你个人的理解?2. Dockerfile中最常见的指令是什么?3.docker网络模式有哪些?4.Kubernetes有哪些核心组件这些组件负责什么工作?5. Pod是什么?6.描述一…...
java 4 (面向对象上)
java——面向对象(上) 目录java——面向对象(上)面向对象的思想概述类的成员(1-2):属性和方法对象的内存解析类中属性的使用类中方法的使用1.举例:2.声明方法:3.说明4.re…...
HTTP报头的2个方法
在采集网页信息的时候,经常需要伪造报头来实现采集脚本的有效执行 下面,我们将使用urllib2的header部分伪造报头来实现采集信息 方法1、 #!/usr/bin/python -- coding: utf-8 -- #encodingutf-8 #Filename:urllib2-header.py import urllib2 import…...
yolov5双目检测车辆识别(2023年+单目+双目+python源码+毕业设计)
行人识别yolov5和v7对比yolo车距源码:yolov5双目检测车辆识别(2023年单目双目python源码毕业设计)上盒岛APP,开线上盲盒商店http://www.hedaoapp.com/yunPC/goodsDetails?pid4132 为了提高传统遗传算法(genetic algorithm, GA)IGA优化BP网络迭代时间过长以及精度偏…...
华为OD机试题,用 Java 解【用户调度问题】问题
华为Od必看系列 华为OD机试 全流程解析+经验分享,题型分享,防作弊指南)华为od机试,独家整理 已参加机试人员的实战技巧华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典使用说明 参加华为od机试,一定要注意不…...
根据mybatis plus注解动态创建sqlite表和表字段
根据mybatis plus注解动态创建sqlite表和表字段 启动时动态创建sqlite数据库,根据mybatis plus注解动态创建表。如果有新增字段,动态创建字段。 文章目录根据mybatis plus注解动态创建sqlite表和表字段一、初始化数据库1.系统启动时初始化数据库2.初始化…...
同步、异步ETL架构的比较
背景介绍: 数据的抽取,转换和加载 (ETL, Extract, Transform, Load) 是构建数据仓库过程中最复杂也是至 关重要的一个步骤,我们通常用两种办法来处理 ETL 流程: 一种是异步(Asynchronous) ETL 方式, 也称为文本文件(Flat file)方式。 另外…...
【机会约束、鲁棒优化】具有排放感知型经济调度中机会约束和鲁棒优化研究【IEEE6节点、IEEE118节点算例】(Matlab代码实现)
💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...
用Python帮老叔选出好基金,大赚一笔,老叔专门提着茅台登门道谢
我有个老叔很喜欢买基金,因为不想被割韭菜,所以啥群都没进,全部自己精挑细选。 看着他的一个本子密密麻麻地写了一大堆东西,全是基金的数据分析,一大把年纪了挺不容易的,于是就决定帮他一把。 在跟他详谈…...
ZeroTier实现内网穿透详细教程,无需公网IP,实现异地组网
ZeroTier实现内网穿透详细教程,无需公网IP,实现异地组网ZeroTier1.官网注册账号,创建自己的局域网段2.点击创建好的网络,进入设置界面进行设置3.下载客户端,安装客户端,然后连接到网络中4.加入网络成功后&a…...
电商 SaaS 全渠道实时数据中台最佳实践
摘要:本文整理自聚水潭数据专家张成玉,聚水潭高级数据工程师应圣楚,在 FFA 2022 行业案例专场的分享。本篇内容主要分为四个部分:实时数仓的建设和发展数据中台的产品体系及架构实时计算的实践和优化对实时计算的未来展望Tips&…...
macos ncnn 安装踩坑记录···
安装真麻烦踩了无数坑,官方给的安装教程:macos安装ncnn, 安装过程老是报错,记录一下卡的比较久的,网上也不好找资料的错. 我的电脑: 1. 使用homebrew 的时候失败fatal: not in a git directory Error: Command failed…...
ESP32设备驱动-AM2301(DHT21)温度湿度传感器驱动
AM2301(DHT21)温度湿度传感器驱动 文章目录 AM2301(DHT21)温度湿度传感器驱动1、AM2301(DHT21)介绍2、硬件准备3、软件准备4、驱动实现1、AM2301(DHT21)介绍 AM2301 湿敏电容数字温湿度模块是一款含有已校准数字信号输出的温湿度复合传感器。它应用专用的数字模块采集技术和温…...
Java全栈工程师的实战面试:从技术细节到业务场景
Java全栈工程师的实战面试:从技术细节到业务场景 一、面试开始 面试官(微笑着):你好,很高兴见到你。我是负责技术面试的张工,今天我们会聊一些技术相关的问题。首先,请简单介绍一下你自己。 应聘…...
5个强力优化步骤:Win11Debloat让Windows系统性能提升显著
5个强力优化步骤:Win11Debloat让Windows系统性能提升显著 【免费下载链接】Win11Debloat 一个简单的PowerShell脚本,用于从Windows中移除预装的无用软件,禁用遥测,从Windows搜索中移除Bing,以及执行各种其他更改以简化…...
洛谷 P1833:樱花 ← 混合背包(01 + 完全 + 多重)
【题目来源】 https://www.luogu.com.cn/problem/P1833 【题目描述】 爱与愁大神后院里种了 n 棵樱花树,每棵都有美学值 Ci(0<Ci≤200)。爱与愁大神在每天上学前都会来赏花。爱与愁大神可是生物学霸,他懂得如何欣赏樱花:一种樱花树看一遍…...
douyin-downloader:智能抖音视频全流程管理工具,让内容收集效率提升90%
douyin-downloader:智能抖音视频全流程管理工具,让内容收集效率提升90% 【免费下载链接】douyin-downloader 项目地址: https://gitcode.com/GitHub_Trending/do/douyin-downloader douyin-downloader是一款开源的抖音视频批量下载与管理工具&am…...
用CODrone数据集训练YOLOv8-OBB:手把手教你搞定无人机旋转目标检测模型
从CODrone到YOLOv8-OBB:实战无人机旋转目标检测全流程指南 无人机航拍视角下的目标检测一直是计算机视觉领域的难点——倾斜视角带来的目标旋转、飞行高度变化导致的尺度差异、复杂背景干扰等问题,让传统水平框检测方法捉襟见肘。本文将带您完整实现从CO…...
CasRel模型LaTeX学术论文辅助工具:自动提取相关工作和贡献
CasRel模型LaTeX学术论文辅助工具:自动提取相关工作和贡献 每次打开一篇新的学术论文,尤其是那些动辄几十页的综述或顶会文章,你是不是也有点头大?密密麻麻的文字里,最关键的信息——“别人做了什么”、“他们有什么不…...
Qwen3.5-4B-Claude-Opus企业实操:数据治理元数据血缘关系推理补全工具
Qwen3.5-4B-Claude-Opus企业实操:数据治理元数据血缘关系推理补全工具 1. 平台概述 Qwen3.5-4B-Claude-4.6-Opus-Reasoning-Distilled-GGUF是基于Qwen3.5-4B架构的推理蒸馏模型,专门针对企业级数据治理场景中的元数据血缘关系分析任务进行了优化。该模…...
vLLM-v0.17.1实操手册:Prometheus监控指标接入与告警配置
vLLM-v0.17.1实操手册:Prometheus监控指标接入与告警配置 1. vLLM框架简介 vLLM是一个专为大型语言模型(LLM)设计的高性能推理和服务库,由加州大学伯克利分校的天空计算实验室(Sky Computing Lab)开发,现已发展为社区驱动的开源项目。这个框…...
Python实战:两步移动搜索法(2SFCA)在医疗资源可达性分析中的应用
1. 什么是两步移动搜索法(2SFCA)? 第一次听说两步移动搜索法(2SFCA)时,我完全被这个专业名词唬住了。后来在实际项目中用了才发现,它其实就是个"找资源"的聪明算法。想象一下你住在一…...
YOLO_World+SAM+GraspNet在mujoco中的抓取仿真实战:从环境搭建到代码运行
YOLO_WorldSAMGraspNet在MuJoCo中的抓取仿真实战:从环境搭建到代码运行 在机器人抓取仿真领域,结合YOLO_World、SAM(Segment Anything Model)和GraspNet三大前沿技术,能够在MuJoCo物理引擎中实现高度逼真的物体识别、分…...
