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

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

相关文章

Golan中 new() 、 make() 和简短声明符的区别和使用

《Golan中new()、make()和简短声明符的区别和使用》Go语言中的new()、make()和简短声明符的区别和使用,new()用于分配内存并返回指针,make()用于初始化切片、映射... 详细介绍golang的new() 、 make() 和简短声明符的区别和使用。文章目录 `new()`

基于Java实现模板填充Word

《基于Java实现模板填充Word》这篇文章主要为大家详细介绍了如何用Java实现按产品经理提供的Word模板填充数据,并以word或pdf形式导出,有需要的小伙伴可以参考一下... Java实现按模板填充wor编程d本文讲解的需求是:我们需要把数据库中的某些数据按照 产品经理提供的 word模板,把数据

异构存储(冷热数据分离)

异构存储主要解决不同的数据,存储在不同类型的硬盘中,达到最佳性能的问题。 异构存储Shell操作 (1)查看当前有哪些存储策略可以用 [lytfly@hadoop102 hadoop-3.1.4]$ hdfs storagepolicies -listPolicies (2)为指定路径(数据存储目录)设置指定的存储策略 hdfs storagepolicies -setStoragePo

poj3468(线段树成段更新模板题)

题意:包括两个操作:1、将[a.b]上的数字加上v;2、查询区间[a,b]上的和 下面的介绍是下解题思路: 首先介绍  lazy-tag思想:用一个变量记录每一个线段树节点的变化值,当这部分线段的一致性被破坏我们就将这个变化值传递给子区间,大大增加了线段树的效率。 比如现在需要对[a,b]区间值进行加c操作,那么就从根节点[1,n]开始调用update函数进行操作,如果刚好执行到一个子节点,

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�

poj 1258 Agri-Net(最小生成树模板代码)

感觉用这题来当模板更适合。 题意就是给你邻接矩阵求最小生成树啦。~ prim代码:效率很高。172k...0ms。 #include<stdio.h>#include<algorithm>using namespace std;const int MaxN = 101;const int INF = 0x3f3f3f3f;int g[MaxN][MaxN];int n

uva 1342 欧拉定理(计算几何模板)

题意: 给几个点,把这几个点用直线连起来,求这些直线把平面分成了几个。 解析: 欧拉定理: 顶点数 + 面数 - 边数= 2。 代码: #include <iostream>#include <cstdio>#include <cstdlib>#include <algorithm>#include <cstring>#include <cmath>#inc

uva 11178 计算集合模板题

题意: 求三角形行三个角三等分点射线交出的内三角形坐标。 代码: #include <iostream>#include <cstdio>#include <cstdlib>#include <algorithm>#include <cstring>#include <cmath>#include <stack>#include <vector>#include <

poj 2104 and hdu 2665 划分树模板入门题

题意: 给一个数组n(1e5)个数,给一个范围(fr, to, k),求这个范围中第k大的数。 解析: 划分树入门。 bing神的模板。 坑爹的地方是把-l 看成了-1........ 一直re。 代码: poj 2104: #include <iostream>#include <cstdio>#include <cstdlib>#include <al

计算机毕业设计 大学志愿填报系统 Java+SpringBoot+Vue 前后端分离 文档报告 代码讲解 安装调试

🍊作者:计算机编程-吉哥 🍊简介:专业从事JavaWeb程序开发,微信小程序开发,定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事,生活就是快乐的。 🍊心愿:点赞 👍 收藏 ⭐评论 📝 🍅 文末获取源码联系 👇🏻 精彩专栏推荐订阅 👇🏻 不然下次找不到哟~Java毕业设计项目~热门选题推荐《1000套》 目录 1.技术选型 2.开发工具 3.功能