有关C++模板(template)的编译错误“error LNK2019: 无法解析的外部符号”的分析 - 小众知识

有关C++模板(template)的编译错误“error LNK2019: 无法解析的外部符号”的分析

2022-12-10 05:17:06 苏内容
  标签:
阅读:2255

模板(template)链接不到


转自:http://blog.csdn.net/fengyhack/article/details/39296411


按照通常的习惯,我们这样设计一个类或者结构(体):

在头文件(*.h  *.hh  *.hpp  *.hxx)中声明成员(或属性)和方法(假设为MyClass.hpp),

在源文件(*.c  *.cc  *.cpp  *.cxx)中包含该头文件(#include "MyClass.hpp")并实现类

或者结构(体)的方法

然后在调用方(比如main.cpp)包含该头文件(#include "MyClass.hpp")

这是一个很好的习惯,至少我是这么认为的


不愉快的事情时有发生。

如果坚持这个套路,我们编写一个模板,比如模板函数、模板类,哐当,在一个实际

应用中,出现了如下链接(LINK)错误:


这是一个用于测试的简化示例,具体代码结构如下


在MyTemplate中定义了一个testFunc模板函数,然后在主函数中调用了一个特化实例


声明如下:

[cpp]    
  1. // MyTemplate.h  
  2.   
  3. #ifndef MY_TMP_H  
  4. #define MY_TMP_H  
  5.   
  6. template <typename T>  
  7. void testFunc(T& x);  
  8.   
  9. #endif  

实现代码

[cpp] 
  1. // MyTemplate.cpp  
  2.   
  3. #include "MyTemplate.h"  
  4.   
  5. #include <iostream>  
  6. #include <typeinfo>  
  7. using namespace std;  
  8.   
  9. template <typename T>  
  10. void testFunc(T& x)  
  11. {  
  12.     cout <<"Type: "<< typeid(x).name() << endl;  
  13. }  


主函数引用

[cpp] 
  1. // Main.cpp  
  2.   
  3. #include "MyTemplate.h"  
  4.   
  5. #include <iostream>  
  6.   
  7. int main(void)  
  8. {  
  9.     double d = 0.0;  
  10.     testFunc(d);  
  11.     system("pause");  
  12.     return 0;  
  13. }  


分析以上代码,你可能会说:“没问题呀,怎么会出现链接错误呢?”

问题的根源在于编译器对于模板(template)的编译处理过程中,

大致是这样的(果真如此么?):

1、模板函数testFunc在编译(compile)期间并未生成具体二进制代码,

    在main函数中也没有嵌入这个函数的代码,可能只是包含了一句

    call testFunc之类的(稍后详述)


2、编译阶段,在main函数中发现了testFunc的引用,但是main.obj中没有相关的

     可执行代码(编译器认为该函数在别处定义,这就是为什么需要链接也就是

     LINK了,在main中虽然引用到testFunc但是只提供了一个call虚拟地址而没有

     实际的执行代码


3、链接阶段,将各个模块(编译期间生成的很多*.obj文件)组织起来

     形象的说就是,在LINK的时候把testFunc“嵌入”进来,就像是一个子过程,

     在main中从调用处jump到这里即可,执行完毕再跳出子模块,从“中断点”

     继续执行后续语句)


4、模板在编译期间是不生成具体代码的,除非有特化的引用,比如上述的      

     testFunc(double d),这里将参数实例化为double



详细分析

这个示例中MyTemplate.h中声明了模板函数但是具体实现放在了MyTemplate.cpp

文件中,然后主函数中引用到testFunc的一个特化实例,因为MyTemplate和Main

分别编译为MyTemplate.obj和Main.obj

(根据你编译器的设置可能会有不同,这是按照默认设置生成的中间文件名称)


在编译MyTemplate的过程中,没有找到任何特化实例(头文件为模板声明,

源文件亦为模板实现),因此不生成任何可执行实例代码

主模块Main中引用到testFunc,并且main.cpp中没有相关实现代码

(#include "MyTemplate.h"只是包含了声明)

因此只是给出了call testFunc的“字样”而不是具体执行代码,就是说寄希望于

链接阶段,在别的模块中找到testFunc的定义


于是在LINK阶段需要查找testFunc的实现定义,不幸的是,找不到了,于是出现

链接错误

error LNK2019: 无法解析的外部符号 "void __cdecl testFunc<double>(double &)"

 (??$testFunc@N@@YAXAAN@Z),该符号在函数 _main 中被引用


那么,如何解决这个问题呢?


至少有以下几种方法:

1、在一个文件中完成模板的声明及实现

2、在模板头文件末尾添加实现文件的包含 #include "MyTemnplate.cpp"

3、在调用方(main.cpp中)包含实现文件 #include "MyTemnplate.cpp"


第二种方式还不如第一种方式简洁,实际上就是一个东西,

第三种方法可能会造成而外开销(比如多个模块都调用了这个模板的某个特化实例的

情形)

但一般来说这种开销不算什么,除非你的要求很严格,那么请采用第一种方式吧


采用第三种方法进行测试


设置一个断点,如下:



启动调试然后打开反汇编窗口(VS2013下的默认快捷键是 Ctrl+Alt+D)

注意红色方框标注的那一行


切换到call地址0B31299h所在行

找到了testFunc的特化实例 testFunc<double>


发现jmp到0B33610h


这个地址就是testFunc特化实例 testFunction<double>()的入口


如此便验证了本文开头的解释

事实上,除了模板,抽象类等也不能被实例化,在这种情况下,建议采用上述的

第一种方法:

在一个文件中完成模板的声明及实现


扩展阅读
相关阅读
© CopyRight 2010-2021, PREDREAM.ORG, Inc.All Rights Reserved. 京ICP备13045924号-1