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

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

相关文章

Python程序的文件头部声明小结

《Python程序的文件头部声明小结》在Python文件的顶部声明编码通常是必须的,尤其是在处理非ASCII字符时,下面就来介绍一下两种头部文件声明,具有一定的参考价值,感兴趣的可以了解一下... 目录一、# coding=utf-8二、#!/usr/bin/env python三、运行Python程序四、

ShardingSphere之读写分离方式

《ShardingSphere之读写分离方式》:本文主要介绍ShardingSphere之读写分离方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录ShardingSphere-读写分离读写分离mysql主从集群创建 user 表主节点执行见表语句项目代码读写分

spring security 超详细使用教程及如何接入springboot、前后端分离

《springsecurity超详细使用教程及如何接入springboot、前后端分离》SpringSecurity是一个强大且可扩展的框架,用于保护Java应用程序,尤其是基于Spring的应用... 目录1、准备工作1.1 引入依赖1.2 用户认证的配置1.3 基本的配置1.4 常用配置2、加密1. 密

Java如何根据word模板导出数据

《Java如何根据word模板导出数据》这篇文章主要为大家详细介绍了Java如何实现根据word模板导出数据,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... pom.XML文件导入依赖 <dependency> <groupId>cn.afterturn</groupId>

电脑软件不能安装到C盘? 真相颠覆你的认知!

《电脑软件不能安装到C盘?真相颠覆你的认知!》很多人习惯把软件装到D盘、E盘,刻意绕开C盘,这种习惯从哪来?让我们用数据和案例,拆解背后的3大原因... 我身边不少朋友,在使用电脑安装软件的时候,总是习惯性的把软件安装到D盘或者E盘等位置,刻意避开C盘。如果你也有这样的习惯,或者不明白为什么要这么做,那么我

vscode不能打开终端问题的解决办法

《vscode不能打开终端问题的解决办法》:本文主要介绍vscode不能打开终端问题的解决办法,问题的根源是Windows的安全软件限制了PowerShell的运行,而VSCode默认使用Powe... 遇到vscode不能打开终端问题,一直以为是安全软件限制问题,也没搜到解决方案,因为影响也不大,就没有管

Python中Flask模板的使用与高级技巧详解

《Python中Flask模板的使用与高级技巧详解》在Web开发中,直接将HTML代码写在Python文件中会导致诸多问题,Flask内置了Jinja2模板引擎,完美解决了这些问题,下面我们就来看看F... 目录一、模板渲染基础1.1 为什么需要模板引擎1.2 第一个模板渲染示例1.3 模板渲染原理二、模板

利用Python打造一个Excel记账模板

《利用Python打造一个Excel记账模板》这篇文章主要为大家详细介绍了如何使用Python打造一个超实用的Excel记账模板,可以帮助大家高效管理财务,迈向财富自由之路,感兴趣的小伙伴快跟随小编一... 目录设置预算百分比超支标红预警记账模板功能介绍基础记账预算管理可视化分析摸鱼时间理财法碎片时间利用财

如何在 Spring Boot 中实现 FreeMarker 模板

《如何在SpringBoot中实现FreeMarker模板》FreeMarker是一种功能强大、轻量级的模板引擎,用于在Java应用中生成动态文本输出(如HTML、XML、邮件内容等),本文... 目录什么是 FreeMarker 模板?在 Spring Boot 中实现 FreeMarker 模板1. 环

C 语言中enum枚举的定义和使用小结

《C语言中enum枚举的定义和使用小结》在C语言里,enum(枚举)是一种用户自定义的数据类型,它能够让你创建一组具名的整数常量,下面我会从定义、使用、特性等方面详细介绍enum,感兴趣的朋友一起看... 目录1、引言2、基本定义3、定义枚举变量4、自定义枚举常量的值5、枚举与switch语句结合使用6、枚