【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 湿敏电容数字温湿度模块是一款含有已校准数字信号输出的温湿度复合传感器。它应用专用的数字模块采集技术和温…...
浅谈 React Hooks
React Hooks 是 React 16.8 引入的一组 API,用于在函数组件中使用 state 和其他 React 特性(例如生命周期方法、context 等)。Hooks 通过简洁的函数接口,解决了状态与 UI 的高度解耦,通过函数式编程范式实现更灵活 Rea…...
变量 varablie 声明- Rust 变量 let mut 声明与 C/C++ 变量声明对比分析
一、变量声明设计:let 与 mut 的哲学解析 Rust 采用 let 声明变量并通过 mut 显式标记可变性,这种设计体现了语言的核心哲学。以下是深度解析: 1.1 设计理念剖析 安全优先原则:默认不可变强制开发者明确声明意图 let x 5; …...
业务系统对接大模型的基础方案:架构设计与关键步骤
业务系统对接大模型:架构设计与关键步骤 在当今数字化转型的浪潮中,大语言模型(LLM)已成为企业提升业务效率和创新能力的关键技术之一。将大模型集成到业务系统中,不仅可以优化用户体验,还能为业务决策提供…...
React 第五十五节 Router 中 useAsyncError的使用详解
前言 useAsyncError 是 React Router v6.4 引入的一个钩子,用于处理异步操作(如数据加载)中的错误。下面我将详细解释其用途并提供代码示例。 一、useAsyncError 用途 处理异步错误:捕获在 loader 或 action 中发生的异步错误替…...
Oracle查询表空间大小
1 查询数据库中所有的表空间以及表空间所占空间的大小 SELECTtablespace_name,sum( bytes ) / 1024 / 1024 FROMdba_data_files GROUP BYtablespace_name; 2 Oracle查询表空间大小及每个表所占空间的大小 SELECTtablespace_name,file_id,file_name,round( bytes / ( 1024 …...
Java如何权衡是使用无序的数组还是有序的数组
在 Java 中,选择有序数组还是无序数组取决于具体场景的性能需求与操作特点。以下是关键权衡因素及决策指南: ⚖️ 核心权衡维度 维度有序数组无序数组查询性能二分查找 O(log n) ✅线性扫描 O(n) ❌插入/删除需移位维护顺序 O(n) ❌直接操作尾部 O(1) ✅内存开销与无序数组相…...
Docker 运行 Kafka 带 SASL 认证教程
Docker 运行 Kafka 带 SASL 认证教程 Docker 运行 Kafka 带 SASL 认证教程一、说明二、环境准备三、编写 Docker Compose 和 jaas文件docker-compose.yml代码说明:server_jaas.conf 四、启动服务五、验证服务六、连接kafka服务七、总结 Docker 运行 Kafka 带 SASL 认…...
实现弹窗随键盘上移居中
实现弹窗随键盘上移的核心思路 在Android中,可以通过监听键盘的显示和隐藏事件,动态调整弹窗的位置。关键点在于获取键盘高度,并计算剩余屏幕空间以重新定位弹窗。 // 在Activity或Fragment中设置键盘监听 val rootView findViewById<V…...
根据万维钢·精英日课6的内容,使用AI(2025)可以参考以下方法:
根据万维钢精英日课6的内容,使用AI(2025)可以参考以下方法: 四个洞见 模型已经比人聪明:以ChatGPT o3为代表的AI非常强大,能运用高级理论解释道理、引用最新学术论文,生成对顶尖科学家都有用的…...
招商蛇口 | 执笔CID,启幕低密生活新境
作为中国城市生长的力量,招商蛇口以“美好生活承载者”为使命,深耕全球111座城市,以央企担当匠造时代理想人居。从深圳湾的开拓基因到西安高新CID的战略落子,招商蛇口始终与城市发展同频共振,以建筑诠释对土地与生活的…...
