当前位置: 首页 > news >正文

【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++中,模板分离编译是指将模板的声明和定义分开存放在不同的文件中,以避免多个源文件中重复定义同一个模板的问题。然而,在实践中,模板分离编译常常会带来一些问题,,主要包括以下几个方面:

  1. 链接错误:由于模板被分成了多个文件,如果链接时遗漏了某个模板的定义,就会导致链接错误。
  2. 多次实例化:由于模板的定义通常都写在头文件中,如果多个源文件包含相同的头文件,就可能导致同一个模板被多次实例化,从而增加编译时间和代码大小
  3. 可读性差:模板分离编译会导致模板的声明和定义分散在不同的文件中,使得代码的可读性变差。

我们都知道,程序运行起来一般要以下4个步骤:

  1. 预处理:头文件展开,去注释,宏替换,条件编译等。
  2. 编译:检查代码的规范性,是否有语法错误等,确定代码实际要做的工作,在检查无误后,将代码翻译成汇编语言。
  3. 汇编:把编译阶段生成的文件转成目标文件obj
  4. 链接:将生成的各个目标文件进行链接,生成可执行文件。

多次实例化的问题,我们在前面已经讲解的很清楚了,我们现在来认识一下在模板的分离编译中,可能会遇到的链接问题

在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其实也是可以的。推荐使用这种。这样的话,只要包含了这个头文件,就会把函数模板的代码包含进来,若发生函数调用,可以直接依据类型进行实例化。这种方法比较推荐,但是也有不足之处。

  1. 将函数定义写在头文件中,暴露了函数的实现细节。
  2. 不符合分离编译模式的规则。

模板定义的位置显式实例化

解决代码如下:

//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.内联函数:将模板函数定义为内联函数可以避免链接错误,同时减少函数调用的开销。

模板总结

【优点】

  1. 模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库STL因此而产生。
  2. 增强了代码的灵活性。

【缺陷】

  1. 模板会导致代码膨胀问题,也会导致编译时间变长。
  2. 出现模板编译错误时,错误信息非常凌乱,不易定位。

相关文章:

【C++】关于C++模板的分离编译问题

文章目录1.阐述模板的实例化和重复定义问题2.分离编译可能出现的问题3.解决方法将函数模板的定义放到头文件中模板定义的位置显式实例化模板总结1.阐述模板的实例化和重复定义问题 C模板是一种非常强大的工具&#xff0c;可以为我们提供通用的代码实现方式。然鹅&#xff0c;在…...

小应用记账本-第2章-数据库设计

小应用记账本-第2章-数据库设计 在上一章《小应用记账本-第1章-需求分析》已经罗列了我们需要的功能&#xff0c;因为很简单&#xff0c;所以这一章就来设计数据库吧。 Account表&#xff1a;账户表 字段名类型说明取值idint账户idaccount_namevarchar账户名称remaining_sumd…...

Spring Boot+Vue前后端分离项目练习06之网盘项目创建vue项目

1.安装环境 构建vue项目&#xff0c;需要提前安装相应的环境&#xff0c;这里主要就是node&#xff0c;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的内置框架&#xff0c;支持多种自动化测试用例的编写&#xff0c;以及支持用例前置条件和后置…...

特权级那些事儿-实模式下分段机制首次出现的原因

前言&#xff1a; 操作系统的特权级模块在整个操作系统的学习中应该算的上是最难啃的了&#xff0c;提到特权级就要绕不开保护模式下的分段机制&#xff1b;如果想要彻底弄明白就要对比实模式下的分段机制有什么缺陷。这就衍生出很多问题如&#xff1a;什么是实模式&#xff1f…...

详解Vue安装与配置(2023)

文章目录一、官网下载node.js二、安装Node.js三、环境配置四、idea导入vue项目五、IDEA添加Vue.js插件一、官网下载node.js Vue是前端开发框架。搭建框架&#xff0c;首先要搭建环境。搭建Vue的环境工具&#xff1a;node.js&#xff08;JavaScript的运行环境&#xff09;&…...

TypeScript深度剖析:Vue项目中应用TypeScript?

一、前言 与link类似 在VUE项目中应用typescript&#xff0c;我们需要引入一个库vue-property-decorator&#xff0c; 其是基于vue-class-component库而来&#xff0c;这个库vue官方推出的一个支持使用class方式来开发vue单文件组件的库 主要的功能如下&#xff1a; metho…...

linux面试高级篇

题目目录1.虚拟机常用有几种网络模式&#xff1f;请简述其工作原理或你个人的理解&#xff1f;2. Dockerfile中最常见的指令是什么&#xff1f;3.docker网络模式有哪些&#xff1f;4.Kubernetes有哪些核心组件这些组件负责什么工作&#xff1f;5. Pod是什么&#xff1f;6.描述一…...

java 4 (面向对象上)

java——面向对象&#xff08;上&#xff09; 目录java——面向对象&#xff08;上&#xff09;面向对象的思想概述类的成员&#xff08;1-2&#xff09;&#xff1a;属性和方法对象的内存解析类中属性的使用类中方法的使用1.举例&#xff1a;2.声明方法&#xff1a;3.说明4.re…...

HTTP报头的2个方法

在采集网页信息的时候&#xff0c;经常需要伪造报头来实现采集脚本的有效执行 下面&#xff0c;我们将使用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&#xff0c;开线上盲盒商店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数据库&#xff0c;根据mybatis plus注解动态创建表。如果有新增字段&#xff0c;动态创建字段。 文章目录根据mybatis plus注解动态创建sqlite表和表字段一、初始化数据库1.系统启动时初始化数据库2.初始化…...

同步、异步ETL架构的比较

背景介绍&#xff1a; 数据的抽取&#xff0c;转换和加载 (ETL, Extract, Transform, Load) 是构建数据仓库过程中最复杂也是至 关重要的一个步骤&#xff0c;我们通常用两种办法来处理 ETL 流程: 一种是异步(Asynchronous) ETL 方式, 也称为文本文件(Flat file)方式。 另外…...

【机会约束、鲁棒优化】具有排放感知型经济调度中机会约束和鲁棒优化研究【IEEE6节点、IEEE118节点算例】(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…...

用Python帮老叔选出好基金,大赚一笔,老叔专门提着茅台登门道谢

我有个老叔很喜欢买基金&#xff0c;因为不想被割韭菜&#xff0c;所以啥群都没进&#xff0c;全部自己精挑细选。 看着他的一个本子密密麻麻地写了一大堆东西&#xff0c;全是基金的数据分析&#xff0c;一大把年纪了挺不容易的&#xff0c;于是就决定帮他一把。 在跟他详谈…...

ZeroTier实现内网穿透详细教程,无需公网IP,实现异地组网

ZeroTier实现内网穿透详细教程&#xff0c;无需公网IP&#xff0c;实现异地组网ZeroTier1.官网注册账号&#xff0c;创建自己的局域网段2.点击创建好的网络&#xff0c;进入设置界面进行设置3.下载客户端&#xff0c;安装客户端&#xff0c;然后连接到网络中4.加入网络成功后&a…...

电商 SaaS 全渠道实时数据中台最佳实践

摘要&#xff1a;本文整理自聚水潭数据专家张成玉&#xff0c;聚水潭高级数据工程师应圣楚&#xff0c;在 FFA 2022 行业案例专场的分享。本篇内容主要分为四个部分&#xff1a;实时数仓的建设和发展数据中台的产品体系及架构实时计算的实践和优化对实时计算的未来展望Tips&…...

macos ncnn 安装踩坑记录···

安装真麻烦踩了无数坑&#xff0c;官方给的安装教程&#xff1a;macos安装ncnn, 安装过程老是报错&#xff0c;记录一下卡的比较久的&#xff0c;网上也不好找资料的错. 我的电脑&#xff1a; 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 湿敏电容数字温湿度模块是一款含有已校准数字信号输出的温湿度复合传感器。它应用专用的数字模块采集技术和温…...

第19节 Node.js Express 框架

Express 是一个为Node.js设计的web开发框架&#xff0c;它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用&#xff0c;和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...

linux之kylin系统nginx的安装

一、nginx的作用 1.可做高性能的web服务器 直接处理静态资源&#xff08;HTML/CSS/图片等&#xff09;&#xff0c;响应速度远超传统服务器类似apache支持高并发连接 2.反向代理服务器 隐藏后端服务器IP地址&#xff0c;提高安全性 3.负载均衡服务器 支持多种策略分发流量…...

线程同步:确保多线程程序的安全与高效!

全文目录&#xff1a; 开篇语前序前言第一部分&#xff1a;线程同步的概念与问题1.1 线程同步的概念1.2 线程同步的问题1.3 线程同步的解决方案 第二部分&#xff1a;synchronized关键字的使用2.1 使用 synchronized修饰方法2.2 使用 synchronized修饰代码块 第三部分&#xff…...

基于当前项目通过npm包形式暴露公共组件

1.package.sjon文件配置 其中xh-flowable就是暴露出去的npm包名 2.创建tpyes文件夹&#xff0c;并新增内容 3.创建package文件夹...

Spring AI 入门:Java 开发者的生成式 AI 实践之路

一、Spring AI 简介 在人工智能技术快速迭代的今天&#xff0c;Spring AI 作为 Spring 生态系统的新生力量&#xff0c;正在成为 Java 开发者拥抱生成式 AI 的最佳选择。该框架通过模块化设计实现了与主流 AI 服务&#xff08;如 OpenAI、Anthropic&#xff09;的无缝对接&…...

12.找到字符串中所有字母异位词

&#x1f9e0; 题目解析 题目描述&#xff1a; 给定两个字符串 s 和 p&#xff0c;找出 s 中所有 p 的字母异位词的起始索引。 返回的答案以数组形式表示。 字母异位词定义&#xff1a; 若两个字符串包含的字符种类和出现次数完全相同&#xff0c;顺序无所谓&#xff0c;则互为…...

【C++从零实现Json-Rpc框架】第六弹 —— 服务端模块划分

一、项目背景回顾 前五弹完成了Json-Rpc协议解析、请求处理、客户端调用等基础模块搭建。 本弹重点聚焦于服务端的模块划分与架构设计&#xff0c;提升代码结构的可维护性与扩展性。 二、服务端模块设计目标 高内聚低耦合&#xff1a;各模块职责清晰&#xff0c;便于独立开发…...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

day36-多路IO复用

一、基本概念 &#xff08;服务器多客户端模型&#xff09; 定义&#xff1a;单线程或单进程同时监测若干个文件描述符是否可以执行IO操作的能力 作用&#xff1a;应用程序通常需要处理来自多条事件流中的事件&#xff0c;比如我现在用的电脑&#xff0c;需要同时处理键盘鼠标…...

【Linux系统】Linux环境变量:系统配置的隐形指挥官

。# Linux系列 文章目录 前言一、环境变量的概念二、常见的环境变量三、环境变量特点及其相关指令3.1 环境变量的全局性3.2、环境变量的生命周期 四、环境变量的组织方式五、C语言对环境变量的操作5.1 设置环境变量&#xff1a;setenv5.2 删除环境变量:unsetenv5.3 遍历所有环境…...