C++模板引起外部符号无法解析错误的深入解析

前言

在写模板化的单链表时,采用了头文件和源文件分离的方式,在编译时无论什么函数,只要是模板化的都报错。

报错内容:
LNK2019:无法解析的外部符号 ... 该符号在函数 ... 中被引用

花了一晚上时间,总结了一些解决方案和背后原因。如果不了解相关知识,可能比较难以理解。

前置知识:

  • 了解模板实例化
  • 了解分离式编译

什么是外部符号?

我们首先需要知道,生成可执行文件经过:编译 -> 链接 -> 生成
分离式编译将我们写的代码分成一个个的编译单元,每一个编译单元由头文件(.h)和源文件(.cpp)组成。具体可见:C++头文件和源文件深入解析
当所有编译单元都进行独立的编译之后,由链接器负责将各个独立的编译单元链接起来,成为一个可执行文件。

我们继续了解链接器的工作内容。
程序在编译的过程中会生成三个表:

  • 重定向表
  • 导出符号表
  • 未解决符号表

我们重点了解导出符号表,它将程序中出现的所有符号与之对应的实际地址连接起来。
例如我在A文件中编写了函数a,在B文件中进行调用,导出符号表负责记录函数a对应的入口地址,从而将两个文件链接起来,实现跨文件的函数调用。
因为B文件内部无法得知函数a的具体实现,所以此时叫做外部符号,这种链接类型就叫做外部连接类型

我们了解了什么是外部符号,接下来了解模板实例化的过程。

模板实例化过程

我们知道,模板化的函数只有在使用时才会进行实例化,生成对应的代码。因为编译器不可能生成所有可能类型的函数代码。
当我们调用类模板中的某个模板化的函数,假设是在另一个编译单元,可以理解为在main函数中调用。当前文件只有该函数的声明(通过#include),具体实现内容在定义模板的文件中,所以我们交由链接器去寻找函数的定义。

但此时模板的文件并没有生成对应类型的代码,因为模板没有被使用,所以不会生成。这里的使用指的是类模板所在的编译单元内没有使用。

但为什么在main中调用不行呢?

因为每个编译单元是进行独立编译之后才进行链接工作,我们在main中调用某个模板函数时,我们只知道它是个函数,不知道具体的实现代码,它被看作是一个外部符号。
此时链接器去定义模板的文件中寻找具体定义,但由于模板没有被使用,在模板文件编译过程中没有生成相应的代码,链接器也只能报出无法解析外部符号的错误了。

解决方案

明白了报错的原因和过程,我们谈谈如何解决该问题。
我们上述分析了,当分离式编译类模板时,为什么调用模板化的函数会报错。
简单来说,当我们只引入头文件中的函数声明时,在main所在的编译单元并不知道函数的具体实现,只能将任务交给链接器,而模板所在的文件又没有实例化后的具体代码,只能报错。

但如果我们将函数的定义也引入main所在的编译单元中,就可以解决。
因为main所在的编译单元不仅有函数的声明,还有函数的定义,此时在编译过程即可生成对应的实例化代码,而不需要链接器寻找。

所以我们可以有如下几种解决方案:

  • 引入头文件的同时也引入源文件:#include ".h" and #include ".cpp"
  • 将模板的声明和定义都写在头文件中
  • 在模板头文件中调用模板,使该编译单元生成对应的实例化代码(未实际验证)
上一篇 下一篇

评论 | 0条评论