最近在做项目的时候用到了模板类
果不其然出现了那个错误
LNK2019:无法解析的外部符号
网上百度到的结果繁花怒放,大多数不符合我的需求,故自己研究了下C++的模板类规则,试图找出问题所在
————————————下面是解决方法——————————————
首先将出现这个错误应该怎么处理的方法写出来,解一些急着知道结果的攻城狮们燃眉之急:
1.在主函数包含头文件时将实现模板类的函数也包含进来,如模板类在function1.cpp中实现,则在主函数中添加 #include "function.cpp"
2.将模板类的实现方法写在头文件里面,如模板类的头文件为:function1.h,那么就把实现要用到模板类的函数实现方法写在这 里
3.在实现模板类的文件中调用一下模板类
——————————————下面是详解———————————————
以下解析皆以一个模板类的文件:function1.h 和 function.cpp ,以及一个主函数文件:main.cpp 说明。
首先需要了解的是,一个项目文件从代码段到最终变成可以执行的.exe文件是经过了:替换—编译—连接—生成 这四个步骤的,想要了解具体的编译过程可以参照我以前写过的一篇文章:【c语言】从高级语言到可以执行的EXE程序的编译过程。在编译器中,一个编译单元(.obj文件)是由一个.cpp文件以及该头文件所 include 的头文件所组成.
如本篇文章中 function1.cpp 的代码如下:
#include "function.h"
#include <iostream>
void test()
{
//do anything~
}
function.h的代码如下:
#ifndef _FUNCTION_H_
#define _FUNCTION_H_
void test();
#endif
main.cpp的代码如下:
#include "fuction.h"
void main()
{
test();
}
那么在该项目中存在两个编译单元:
当一个项目中的所有编译单元(.obj文件)都已分离的形式独自进行编译之后,再由连接器将各个单独的编译单元进行连接,从而成为一个可以执行的.exe文件。那么,连接器是如何进行编译单元连接的呢?在C++ 的描述里,程序在编译的过程中会生成三个表:重定向表,导出符号表以及未解决符号表,而其中的导出符号表的作用是将程序中的所有符号与实际的地址联系在一起,而连接器要做的就是通过查找导出符号表中符号的实际地址将各个单独编译单元连接在一起,以本章项目为例:
在main.cpp这个编译单元里面调用了函数 test() , 但是main函数里面并没有test()函数的具体实现方法,所以编译器会在处于同一个编译单元的其他地方找,也就是在 “ function1.h ” 头文件:
而在头文件“function1.h” 中,编译器只发现了函数的定义,也没有找到函数的具体定义:
所以,连接器就会从其他编译单元的导出符号表中寻找与函数test()相同名字的符号,试图找出test()函数的具体实现方法,而在编译单元function.cpp中发现了函数的实现方法,所以连接器就将两个单独的编译单元连接在一起,而在内部无法知道函数具体是如何实现的,只能通过查找外部编译单元的符号,叫做“外部符号”,而这种类型就叫 “ 外部连接类型 ” :
而模板类又与普通情况不同,模板类因其本身具有 “ 不确定性 “ 特点,在C++的规则里,模板类在编译时需要一个具体化的过程。我们将以上的项目做一些修改:
fuction.h:
#ifndef _FUNCTION_H_
#define _FUNCTION_H_
template <class T_ELE>
class function
{
public:
void test();
}
#endif
function.cpp:
#include "function.h"
#include <iostream>
template <class T_ELE>
void function<T_ELE>::test()
{
//do anything~
}
main.cpp:
#include "function.h"
void main()
{
function<int>::test();
}
此时main.cpp 中的调用了使用类模板的test()函数,编译器在编译期间寻找编译单元内部并没有找到具体的实现方法,只找到了函数的声明,所以,寻找函数具体实现方法这个任务就交给了链接器,而连接器在外部的编译单元function.cpp 里找到了函数的具体实现方法:
但是,问题来了,在C++中由于模板类的自身不确定性,当一个模板没有被调用时,它就不会被具体化出来,也就是说在一个编译单元里,如果使用模板类的函数没有被其他函数调用的话,是没有生成二进制代码的,因为编译器并不知道这个函数是属于哪个类型(int,float,等等),所以无法给这个函数分配合适的内存空间,只有当其他函数调用模板类的函数时,才能确定类型,模板类才能被具现化。
这时,很多人就会有一个这样的误解:我不是在主函数里面调用了模板类的函数了嘛?为啥还是错误呢?请注意,我们再来回顾下这个错误:
该错误描述为:无法解析的外部符号。我们再回顾下上面说过的,本项目一共有两个编译单元,一个是main,一个是function,而编译过程是每个编译单元单独编译过后再交给连接器进行连接的,也就是说在连接器之前两个编译单元就已经进行了编译,而在主函数main里面调用外部编译单元时,由于另外一个编译单元function在编译时模板类没有被调用而没有得到具现化,从而导致了连接器在函数主函数里调用了模板类函数,但是找不到具体的实现方法的情况,从到就出现了开头的错误:LNK2019 无法解析的外部符号。
知道了错误的根源,我们就可以根治这种情况,总得来说就是模板类没有具现化,那我们就让它具现化就好了,所以总结了以下几个解决方案也得到了很好的解析:
1.在主函数包含头文件时将实现模板类的函数也包含进来。原因:一个编译单元内包含了.cpp文件以及被include 的头文件,如果将实现模板类的函数文件.cpp也包含进来,那么主函数调用就给了模板类函数一个具现化的机会。
2.将模板类的实现方法写在头文件里面。原因:同上,将实现写在头文件里面,那么主函数调用就给了模板类函数一个具现化的机会。
3.在实现模板类的文件中调用一下模板类。原因:调用一下让模板类函数得到具现化。