为什么模板声明定义不能分离?

2024-09-01 21:44

本文主要是介绍为什么模板声明定义不能分离?,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言

模板函数一般不能声明定义分开,但是普通函数函数就可以,为什么呢?那就要先介绍一下在软件开发过程中,从源代码到可执行程序,通常会经历预处理、编译、汇编、链接的这四个主要步骤。

过渡过程干了什么

  1. 预处理(Preprocessing)

    • 任务:预处理器的任务是处理源代码中的预处理指令,如宏定义#define)、文件包含#include)、条件编译#ifdef#ifndef#endif等)指令。它会删除所有注释,展开所有宏定义,处理条件编译指令,并插入包含文件的内容。
    • 生成文件:预处理后的文件通常以 .i 或 .ii 结尾(在C/C++中),这是预处理后的文本文件,仍然保持高级语言的形式。
  2. 编译(Compilation)

    • 任务:编译器将预处理后的源代码转换成汇编语言。这个过程包括词法分析、语法分析、语义分析、中间代码生成、代码优化等步骤。编译器检查源代码中的错误,如语法错误、类型错误等,并将源代码转换成汇编指令
    • 生成文件:编译阶段生成的文件通常称为目标文件(Object File),以 .s结尾。这个文件包含机器代码,但是它还不能直接执行(计算机无法识别),因为它可能包含未解析的符号引用。
  3. 汇编(Assembly)

    • 任务:汇编器将汇编语言转换成机器语言。它将汇编指令转换成对应的二进制代码,并处理与特定硬件平台相关的指令。
    • 生成文件:汇编阶段生成的是机器码,它通常被直接存储在目标文件中,所以这个步骤可能不会生成新的文件,而是更新之前编译阶段生成的 .o 或 .obj 文件
  4. 链接(Linking)

    • 任务:链接器将一个或多个目标文件以及所需的库文件组合成一个完整的可执行程序。这个过程包括地址和空间分配、符号决议、重定位等。链接器确保所有外部引用的函数和变量都有正确的地址,并解决不同目标文件之间的依赖关系。
    • 生成文件:链接阶段生成的最终文件是可执行文件,在Unix/Linux系统中通常以 .out 或 .exe 结尾,在Windows系统中通常以 .exe 结尾。

普通函数为什么支持分离定义

编译过程

  1. 编译时只看声明: 当编译器编译一个源文件(比如 main.cpp)时,它只需要知道函数的声明,不需要知道函数的实现。因此,编译器可以生成对 myFunction 的调用指令,而无需查看 my_function.cpp 中的实际代码。

链接过程

  1. 符号引用: 编译器在编译源文件时,会为函数调用生成一个符号引用(通常是函数名)。这个符号引用将被放在生成的目标文件(.o 或 .obj 文件)中。

  2. 符号定义: 链接器在链接阶段会查找这些符号引用对应的符号定义。如果 myFunction 的定义在 my_function.cpp 中,链接器会在链接时找到这个定义,并将调用指令与实际函数代码连接起来。

而正常函数编译阶段只需要看到声明;链接时,才回去寻找定义!这样可以大大提高编译效率!

为什么模板函数不支持分离

这主要与模板函数的编译、链接过程与普通函数的差异性有关。

  1. 实例化发生在编译时: 对于模板函数,实例化是在编译时(也就是头文件展开之后)进行的而不是在链接时。编译器需要模板函数的定义来生成特定类型的函数实例。

  2. 实例化的多样性: 由于模板可以针对任意类型进行实例化,编译器无法在编译时预先知道所有可能的实例化。因此,它不能像普通函数那样在链接时查找定义。

  3. 编译器优化: 编译器在编译时对模板函数进行实例化,允许它针对特定类型进行优化。如果模板函数的定义在 .cpp 文件中,编译器在编译时看不到这些定义,因此无法进行优化。

通俗的说

模板函数是一个模板,只有实例化时,才能得到函数的定义。假如我们将声明放在头文件,将定义放在源文件。这就会出现链接错误。

对于编译器而言,他需要在头文件展开之后(也就是编译阶段)需要找到模板函数的定义,然后才能在链接阶段进行绑定,完成程序。如果找不到模板函数的定义,就可能出现编译阶段的错误,但是最终还是体现在了链接部分。

解决方式

1.显式实例化

将模板函数在源文件中定义之后,需要在源文件额外进行模板函数的显式实例化。

template <typename T>
void swap(T& a, T& b) {T temp = a;a = b;b = temp;
}// 显式实例化模板函数swap用于int类型
template void swap<int>(int& a, int& b);

编译器就只会为int类型的swap函数生成代码,而不会为其他类型生成。这可以减少编译时间和可执行文件的大小。

当我们调用swap函数时,如果我们只交换int类型的变量,编译器就会使用上面显式实例化的版本:

int x = 10, y = 20;
swap(x, y); // 使用显式实例化的swap<int>函数
显式实例化之后,意味着实例化了模板,这样就可以在编译阶段
确保了编译器在编译阶段生成特定类型的函数代码,从而使得链接器在链接阶段能够找到这些实例化的函数实现
显式实例化在编译阶段起到了模板函数定义的作用。在显式实例化的情况下,不需要模板函数的定义在编译时可见,因为编译器已经根据显式实例化指令(在另一个.cpp文件)创建了函数的实现。这与普通函数不同,普通函数需要在链接时找到其定义( 显式实例化之后,就相当于生成了一份模板函数定义的代码(对应着特定的类型),在链接阶段就可以找到定义)
方法2:定义在同一个头文件中
这样在头文件展开之后,可以直接将定义展开,就可以找到定义。通常这样的头文件被叫做.hpp或者.h

模板的优缺点

优点:

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

缺点:
1.模板会导致代码膨胀问题,也会导致编译时间变长(每种实例化都会产生额外的函数代码)
2.出现模板编译错误时,错误信息非常凌乱,不易定位错误

这篇关于为什么模板声明定义不能分离?的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1128124

相关文章

ShardingProxy读写分离之原理、配置与实践过程

《ShardingProxy读写分离之原理、配置与实践过程》ShardingProxy是ApacheShardingSphere的数据库中间件,通过三层架构实现读写分离,解决高并发场景下数据库性能瓶... 目录一、ShardingProxy技术定位与读写分离核心价值1.1 技术定位1.2 读写分离核心价值二

GO语言短变量声明的实现示例

《GO语言短变量声明的实现示例》在Go语言中,短变量声明是一种简洁的变量声明方式,使用:=运算符,可以自动推断变量类型,下面就来具体介绍一下如何使用,感兴趣的可以了解一下... 目录基本语法功能特点与var的区别适用场景注意事项基本语法variableName := value功能特点1、自动类型推

python中的显式声明类型参数使用方式

《python中的显式声明类型参数使用方式》文章探讨了Python3.10+版本中类型注解的使用,指出FastAPI官方示例强调显式声明参数类型,通过|操作符替代Union/Optional,可提升代... 目录背景python函数显式声明的类型汇总基本类型集合类型Optional and Union(py

Spring Security 前后端分离场景下的会话并发管理

《SpringSecurity前后端分离场景下的会话并发管理》本文介绍了在前后端分离架构下实现SpringSecurity会话并发管理的问题,传统Web开发中只需简单配置sessionManage... 目录背景分析传统 web 开发中的 sessionManagement 入口ConcurrentSess

MySQL中读写分离方案对比分析与选型建议

《MySQL中读写分离方案对比分析与选型建议》MySQL读写分离是提升数据库可用性和性能的常见手段,本文将围绕现实生产环境中常见的几种读写分离模式进行系统对比,希望对大家有所帮助... 目录一、问题背景介绍二、多种解决方案对比2.1 原生mysql主从复制2.2 Proxy层中间件:ProxySQL2.3

Django中的函数视图和类视图以及路由的定义方式

《Django中的函数视图和类视图以及路由的定义方式》Django视图分函数视图和类视图,前者用函数处理请求,后者继承View类定义方法,路由使用path()、re_path()或url(),通过in... 目录函数视图类视图路由总路由函数视图的路由类视图定义路由总结Django允许接收的请求方法http

在MySQL中实现冷热数据分离的方法及使用场景底层原理解析

《在MySQL中实现冷热数据分离的方法及使用场景底层原理解析》MySQL冷热数据分离通过分表/分区策略、数据归档和索引优化,将频繁访问的热数据与冷数据分开存储,提升查询效率并降低存储成本,适用于高并发... 目录实现冷热数据分离1. 分表策略2. 使用分区表3. 数据归档与迁移在mysql中实现冷热数据分

SpringBoot集成EasyPoi实现Excel模板导出成PDF文件

《SpringBoot集成EasyPoi实现Excel模板导出成PDF文件》在日常工作中,我们经常需要将数据导出成Excel表格或PDF文件,本文将介绍如何在SpringBoot项目中集成EasyPo... 目录前言摘要简介源代码解析应用场景案例优缺点分析类代码方法介绍测试用例小结前言在日常工作中,我们经

Knife4j+Axios+Redis前后端分离架构下的 API 管理与会话方案(最新推荐)

《Knife4j+Axios+Redis前后端分离架构下的API管理与会话方案(最新推荐)》本文主要介绍了Swagger与Knife4j的配置要点、前后端对接方法以及分布式Session实现原理,... 目录一、Swagger 与 Knife4j 的深度理解及配置要点Knife4j 配置关键要点1.Spri

MySQL主从复制与读写分离的用法解读

《MySQL主从复制与读写分离的用法解读》:本文主要介绍MySQL主从复制与读写分离的用法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、主从复制mysql主从复制原理实验案例二、读写分离实验案例安装并配置mycat 软件设置mycat读写分离验证mycat读