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

news2024/11/17 13:33:00

文章目录

  • 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. 出现模板编译错误时,错误信息非常凌乱,不易定位。

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

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

相关文章

Ajax和JSON的基本用法

局部请求页面不会变化&#xff0c;返回的响应我们要动态获取&#xff0c;获取后选择数据更新区域。<body> <input id"btnLoad" type"button" value"加载"> <div id"divContent"></div> <script>//获取点…

三天吃透Kafka面试八股文

本文已经收录到Github仓库&#xff0c;该仓库包含计算机基础、Java基础、多线程、JVM、数据库、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服务、设计模式、架构、校招社招分享等核心知识点&#xff0c;欢迎star~ Github地址&#xff1a;https://github.com/…

前端开发者必备的Nginx知识

nginx在应用程序中的作用 解决跨域请求过滤配置gzip负载均衡静态资源服务器…nginx是一个高性能的HTTP和反向代理服务器&#xff0c;也是一个通用的TCP/UDP代理服务器&#xff0c;最初由俄罗斯人Igor Sysoev编写。 nginx现在几乎是众多大型网站的必用技术&#xff0c;大多数情…

好用的电脑录屏工具有哪些?电脑好用的录屏工具

现如今很多人都渐渐对录屏有了需求&#xff0c;尤其是网课老师和网络主播的从业者&#xff0c;录屏工具可以帮助他们减轻很多工作量。好用的电脑录屏工具有哪些&#xff1f; 平时在工作学习中&#xff0c;我们往往会有录制视频的需求&#xff0c;比如录制游戏视频、录制网课视频…

设计UI - Adobe xd画板及参考线

画板新建画板a. 使用预设画板大小或创建自定义画板。操作步骤&#xff1a;打开xd软件&#xff0c;点击需要建立的画板模版&#xff0c;没有则选择自定义大小。b. 使用画板工具创建其它画板。操作步骤&#xff1a;选中画板工具&#xff0c;选择需要建立的画板模版&#xff0c;没…

STM32启动模式讲解与ICP下载电路

一、官方提供的启动模式说明硬件BOOT引脚接法表格从表格可以看出有三种启动模式&#xff0c;然后对应这不同的存储器启动&#xff0c;那我们现在疑问为啥有三种不能只有一种就好&#xff0c;还有存储器启动区域怎么区分&#xff0c;有些乱&#xff0c;带着这些疑问&#xff0c;…

npm install报错unable to resolve dependency tree

一、问题背景npm install安装项目依赖时报错PS D:\test> npm install npm ERR! code ERESOLVE npm ERR! ERESOLVE unable to resolve dependency tree npm ERR! npm ERR! While resolving: vue-admin-template4.2.1 npm ERR! Found: webpack5.74.0 npm ERR! node_modules/we…

【WebSocket】在SSM项目中配置websocket

在SSM项目中配置websocket 最近在ssm项目中配置了websocket&#xff0c;踩了很多坑&#xff0c;来分享一下 本文暂不提供发送消息等内容的代码逻辑&#xff08;后续也许会补充&#xff09;&#xff0c;如果你直接复制这类可能会对配置造成更大的麻烦&#xff08;博主就是复制…

单元测试、反射、注解、动态代理

&#x1f3e1;个人主页 &#xff1a; 守夜人st &#x1f680;系列专栏&#xff1a;Java …持续更新中敬请关注… &#x1f649;博主简介&#xff1a;软件工程专业&#xff0c;在校学生&#xff0c;写博客是为了总结回顾一些所学知识点 目录单元测试、反射、注解、动态代理单元测…

一篇文综合分析Fuse!

FUSE需求 究竟什么样的需求才能用到用户文件系统&#xff1f;来看一个小例子&#xff1a; 需求是这样的。在deepin的安装器中&#xff0c;安装器就会给多分出一个分区&#xff1a;数据盘。 数据盘的主要作用是让用户存放数据文件&#xff0c;也就是以前用Windows的时候D盘或者…

YoloV7

总体来说&#xff0c;YoLoV7主要可分为主干特征提取网络&#xff08;backbone&#xff09;&#xff0c;加强特征提取网络以及SPPCSPC三个部分&#xff0c;然后再加上RepConv和YoLoHead部分。输入图片640*640*3的RGB图片&#xff0c;然后卷积、标准化&#xff08;BN&#xff09;…

QT学习笔记-QT多项目系统中如何指定各项目的编译顺序

QT学习笔记-QT多项目系统中如何指定各项目的编译顺序背景环境解决思路具体操作背景 为了更好的复用程序功能以及更优雅的管理程序&#xff0c;有经验的程序员通常要对程序进行分层和模块化设计。在QT/C这个工具中同样可以通过创建子项目的方式对程序进行模块化&#xff0c;在这…

浅谈Linux下的shell--BASH

环境&#xff1a;centos7.6&#xff0c;腾讯云服务器Linux文章都放在了专栏&#xff1a;【Linux】欢迎支持订阅&#x1f339;shell的概念与作用我们已经学习并知道了操作系统实际上就是一款软件&#xff0c;一款用来管理计算机软硬件资源&#xff0c;为用户提供良好的执行环境的…

擎创喜报 | 名单公布!恭喜入选!

前言&#xff1a;企业数字化转型作为一种经营策略&#xff0c;指的是利用数字技术和数字化流程来改变企业的运营、管理、销售和服务方式&#xff0c;以适应数字化时代的发展趋势。一般来说&#xff0c;企业进行数字化出于以下几点考虑。提高效率&#xff1a;数字化改进了业务流…

人口老龄化背景下家政服务网络平台【附源码】

1 毕业论文&#xff08;设计&#xff09;版权使用授权书 本毕业论文&#xff08;设计&#xff09;作者同意学校保留并向国家有关部门或机构送交论文&#xff08;设计&#xff09;的复印件和电子版&#xff0c;允许论文&#xff08;设计&#xff09;被查阅和借阅。本人授权广西…

JAVA中比较对象是否相等的方式是什么?为什么重写equals就一定要重写hashcode?百天百题(3/100)

目录 JAVA中比较对象是否相等的方式是什么&#xff1f; 为什么重写equals就一定要重写hashcode&#xff1f; JAVA中比较对象是否相等的方式是什么&#xff1f; 在了解这个问题之前需要先知道&#xff1a; 1.如果对象相同&#xff0c;那么hashcode是一定相同的。 2.对象不同的…

哈希表

文章目录什么是哈希问题引入哈希函数直接定址法除留余数法 &#xff08;常用、重点&#xff09;哈希冲突哈希冲突的解决方法闭散列开散列unordered_map && unordered_set 封装实现哈希的应用位图布隆过滤器哈希经典面试题哈希切分位图应用布隆过滤器什么是哈希 在上一…

阿里巴巴商品详情爬虫数据字段解析 源代码分享 调用示例

返回数据代码段1"item": {"num_iid": "60840463360","title": "Slip-on Daily Urban Walking Shoes","desc_short": "","price": "$47.70","nick": "cn1522808546p…

TypeScript深度剖析:TypeScript 中类的理解?应用场景?

一、是什么 类&#xff08;Class&#xff09;是面向对象程序设计&#xff08;OOP&#xff0c;Object-Oriented Programming&#xff09;实现信息封装的基础 类是一种用户定义的引用数据类型&#xff0c;也称类类型 传统的面向对象语言基本都是基于类的&#xff0c;JavaScript …

好友管理系统--课后程序(Python程序开发案例教程-黑马程序员编著-第4章-课后作业)

实例3&#xff1a;好友管理系统 如今的社交软件层出不穷&#xff0c;虽然功能千变万化&#xff0c;但都具有好友管理系统的基本功能&#xff0c;包括添加好友、删除好友、备注好友、展示好友等。下面是一个简单的好友管理系统的功能菜单&#xff0c;如图1所示。 图1 好友管理系…