按照通常的习惯,我们这样设计一个类或者结构(体):
在头文件(*.h *.hh *.hpp *.hxx)中声明成员(或属性)和方法(假设为MyClass.hpp),
在源文件(*.c *.cc *.cpp *.cxx)中包含该头文件(#include "MyClass.hpp")并实现类
或者结构(体)的方法
然后在调用方(比如main.cpp)包含该头文件(#include "MyClass.hpp")
这是一个很好的习惯,至少我是这么认为的
不愉快的事情时有发生。
如果坚持这个套路,我们编写一个模板,比如模板函数、模板类,哐当,在一个实际
应用中,出现了如下链接(LINK)错误:
这是一个用于测试的简化示例,具体代码结构如下
在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
(??$testFunc@N@@YAXAAN@Z),该符号在函数 _main 中被引用
那么,如何解决这个问题呢?
至少有以下几种方法:
1、在一个文件中完成模板的声明及实现
2、在模板头文件末尾添加实现文件的包含 #include "MyTemnplate.cpp"
3、在调用方(main.cpp中)包含实现文件 #include "MyTemnplate.cpp"
第二种方式还不如第一种方式简洁,实际上就是一个东西,
第三种方法可能会造成而外开销(比如多个模块都调用了这个模板的某个特化实例的
情形)
但一般来说这种开销不算什么,除非你的要求很严格,那么请采用第一种方式吧
采用第三种方法进行测试
设置一个断点,如下:
启动调试然后打开反汇编窗口(VS2013下的默认快捷键是 Ctrl+Alt+D)
注意红色方框标注的那一行
切换到call地址0B31299h所在行
找到了testFunc的特化实例 testFunc
发现jmp到0B33610h
这个地址就是testFunc特化实例 testFunction
如此便验证了本文开头的解释
事实上,除了模板,抽象类等也不能被实例化,在这种情况下,建议采用上述的
第一种方法:
在一个文件中完成模板的声明及实现
这里的分析尚未涉及到,深层次的内容(能力有限),更多内容可以参考相关技术文章
本文原创,转载请注明出处
http://blog.csdn.net/fengyhack/article/details/39296411