您的位置:首页 > 编程语言 > C语言/C++

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

2017-03-01 15:51 639 查看
转自:http://blog.csdn.NET/fengyhack/article/details/39296411

C++ primer 582页

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

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

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

或者结构(体)的方法

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

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

不愉快的事情时有发生。

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

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



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



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

声明如下:

[cpp] view
plain copy

 print?





// MyTemplate.h  

  

#ifndef MY_TMP_H  

#define MY_TMP_H  

  

template <typename T>  

void testFunc(T& x);  

  

#endif  



实现代码

[cpp] view
plain copy

 print?





// MyTemplate.cpp  

  

#include "MyTemplate.h"  

  

#include <iostream>  

#include <typeinfo>  

using namespace std;  

  

template <typename T>  

void testFunc(T& x)  

{  

    cout <<"Type: "<< typeid(x).name() << endl;  

}  



主函数引用

[cpp] view
plain copy

 print?





// Main.cpp  

  

#include "MyTemplate.h"  

  

#include <iostream>  

  

int main(void)  

{  

    double d = 0.0;  

    testFunc(d);  

    system("pause");  

    return 0;  

}  



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

问题的根源在于编译器对于模板(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>()的入口

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

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

第一种方法:

在一个文件中完成模板的声明及实现
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐