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

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

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

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

或者结构(体)的方法

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

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


不愉快的事情时有发生。

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

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


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

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

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


声明如下:

// MyTemplate.h

#ifndef MY_TMP_H
#define MY_TMP_H

template 
void testFunc(T& x);

#endif

实现代码

// MyTemplate.cpp

#include "MyTemplate.h"

#include 
#include 
using namespace std;

template 
void testFunc(T& x)
{
	cout <<"Type: "<< typeid(x).name() << endl;
}


主函数引用

// Main.cpp

#include "MyTemplate.h"

#include 

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 &)"

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


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


至少有以下几种方法:

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

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

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


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

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

情形)

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


采用第三种方法进行测试


设置一个断点,如下:

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


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

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

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

切换到call地址0B31299h所在行

找到了testFunc的特化实例 testFunc


发现jmp到0B33610h

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

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


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

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

第一种方法:

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


这里的分析尚未涉及到,深层次的内容(能力有限),更多内容可以参考相关技术文章

本文原创,转载请注明出处

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

转载于:https://www.cnblogs.com/fengyhack/p/10603677.html

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