模板的分离编译

首先我们需要知道程序运行起来需要四个阶段:
1、预处理(进行宏替换,去注释,条件编译,加行号,头文件展开等一系列工作)
2、编译(把进行完预处理之后的程序进行翻译转换成汇编代码)
3、汇编(把对应的汇编代码转换成机器能够识别的二进制序列)
4、链接(把一个工程中所有的obj文件链接起来变成一个可执行的exe的程序)
接下来我们分析模板的分离编译会产生什么样的结果:
首先我们在工程里建立一个tem.h的头文件:

#include<iostream>
#include<stdlib.h>
using namespace std;
template<class T>
void FunTest(T a);

再建立一个tem.cpp的.文件

#include"tem.h"
template<class T>
void FunTest(T a)
{
    a = 10;
}

在建立一个test.cpp的文件

#include"tem.h"
int main()
{

    FunTest(10);
    system("pause");
    return 0;
}

运行这个程序它会报一个链接型的错误
这里写图片描述
分析为什么会出现链接型的错误
1、我们在tem.cpp文件里定义了这个模板函数,但是并没有对这个模板函数进行实例化,因此在tem.cpp里面并没有生成对应的代码。

2、我们需要知道根据C++标准我们可以知道每一个cpp文件都是一个编译单元,并且每个编译单元是相互独立的并且互相是不可见的,当编译器将所有的cpp文件分离编译完成之后才会使用链接器将这些obj文件链接在一起生成可执行的exe文件。
3、在test.cpp文件里我们在编译的时候只能看到tem.h这个头文件里面FunTest()这个函数的声明,因此编译器将这个FunTested()函数看成外部链接类型的(也就是说这个函数的具体实现是在另外一个.cpp文件中),在对它进行编译之后生成的test.obj文件中会看到对FunTest()函数调用是生成了调用指令 call [FunTest(int)…]

在编译的时候显然在test.obi里面是找不到的,因为在test.obj里是没有这个函数的实现体的,那么它通过连接器就要到别的obj里面去查找,然后在其他obj中找到这个函数实现的代码然后将它的地址去替换test.obj中call指令中的虚假地址。链接器将所有的obj文件链接起来生成exe文件。

但是在我们的tem.cpp文件中是没有FunTest()这个函数的实际代码的,因为没有实例化生成对应的代码。因此在链接过程中就会出错。
解决方案1:在tem.cpp文件里生成与test.cpp中类型对应的代码,也就是在tem.cpp文件里将函数模板实例化成与test.cpp一致类型

#include"tem.h"
template<class T>
void FunTest(T a)
{
    a = 10;
}
void FunTest1()
{
    FunTest(10);

}

这里写图片描述
解决方案2:使用.hpp文件将声明和定义封装在一起
将tem.cpp修改成tem.hpp

再简单剖析以下编译-链接
obj文件里不仅有自己的代码段和数据段和一些头信息之外,还有三个表(未解决符号表、导出符号表、地址重定向表)

未解决的符号表:里面是在本cpp文件里使用的符号,但是在本cpp文件里只能看到它的声明,因为该符号定义不在本cpp文件中。(也就是说这个未解决的符号表会高数编译器在这个编译单元偏移量为xxx处有一个符号x,但是该符号的地址不明确)

导出的符号表:该符号x在本cpp文件里面有明确的定义并且可以提供给其他编译单元这个符号,以及这个符号在本单元地址)

地址重定向表:为了解决地址冲突的问题(因为每个编译单元的地址都是从0x00000000开始的,那么链接器将多个obj文件链接在一起时地址可能会重复导致发生冲突)为了解决这种冲突我们加上它在相对于可执行程序exe文件中的偏移量也就是该编译单元相对可执行程序exe的位置)

<think>嗯,用户问的是模板为什么不支持分离编译。首先,我得回忆一下C++模板的基本机制。模板编译时需要进行实例化,也就是说编译器在看到模板定义的时候,必须知道具体的类型参数才能生成对应的代码。如果模板的声明和实现分开写在头文件和源文件里,那么当其他源文件包含这个头文件时,编译器只能看到声明而看不到实现,这时候实例化就会失败。 比如,用户可能写了一个模板类在头文件声明,在cpp文件实现。然后在另一个cpp文件中使用这个模板的不同类型参数,比如int和double。这时候编译器在编译使用这些参数的源文件时,无法找到对应的实现代码,导致链接错误。因为模板的实现没有被实例化到目标文件中。 然后,解决方法有几个。第一个是把模板的定义和声明都放在头文件里,这样包含头文件的时候,编译器就能看到完整的定义,进行实例化。第二个是显式实例化,也就是在源文件中预先实例化需要的类型,但这限制了只能使用那些预先实例化的类型。第三个是用export关键字,不过我记得这个关键字在C++11之后被移除了,所以可能不适用。 用户可能遇到的问题是在实际项目中,当模板代码分离到不同文件时出现的链接错误。这时候需要解释清楚原因,并给出可行的解决方案。例如,建议将模板的实现也放在头文件中,或者使用显式实例化来处理特定的类型。 另外,可能需要提醒用户注意模板代码组织的最佳实践,避免分离编译带来的问题。还有,现代C++中是否还有其他方法,比如使用inline或constexpr等,不过这些可能不直接解决分离编译的问题。</think><< 模板不支持分离编译的原因在于:C++编译模型要求模板的**完整定义**必须在实例化时可见。当模板的声明和实现分离到不同文件(.h和.cpp)时,编译器在编译调用模板的代码时无法生成具体实例化的代码,导致链接错误。 ### 具体原因: 1. **编译阶段限制** 模板的实例化发生在编译阶段。若模板的实现(成员函数/静态成员)在.cpp文件中,其他.cpp文件调用模板时仅能看到声明,无法生成具体类型的机器码。 2. **符号缺失问题** 若模板未被显式实例化,链接器在最终链接时找不到对应类型的实现代码(如`MyTemplate<int>::foo()`),导致`undefined reference`错误。 --- ### 解决方法: #### 1. 将模板定义放在头文件中(最常见) ```cpp // mytemplate.h #pragma once template <typename T> class MyTemplate { public: void foo() { // 实现直接写在头文件中 } }; ``` #### 2. 显式实例化模板(适用于已知类型) ```cpp // mytemplate.cpp #include "mytemplate.h" // 显式实例化需要的类型 template class MyTemplate<int>; // 仅支持int类型 template class MyTemplate<double>; // 仅支持double类型 ``` #### 3. 使用`export`关键字(C++11已废弃) ```cpp // 注意:主流编译器(如GCC、Clang)均不支持此特性 export template <typename T> class MyTemplate { /* ... */ }; ``` --- ### 示例:分离编译错误场景 ```cpp // mytemplate.h template <typename T> class MyTemplate { public: void foo(); // 只有声明 }; // main.cpp #include "mytemplate.h" int main() { MyTemplate<int> obj; obj.foo(); // 链接错误:找不到MyTemplate<int>::foo() } ``` --- ### 根本原理: C++标准规定模板是**编译期多态**机制,编译器需要根据调用处的具体类型生成代码。若模板实现不可见(如分离到.cpp文件),则无法完成实例化。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值