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

写在前面:

本文转载自 https://blog.csdn.net/fengyhack/article/details/39296411

自己的测试情况:自己是和在Visual Studio 2013 上进行C++编程,遇到了这样的错误,自己最终是在调用方(main.cpp)包含的一个头文件中加入了对应的#include a.cpp#include b.cpp,(比如main.cpp中包含#include A.h,然后自己在#include A.h 中加入了对应的#include a.cpp#include b.cpp ),然后再回到VS2013中就编译通过了,自己非常感谢原文博主~~
在这里插入图片描述
下面是原文内容:
按照通常的习惯,我们这样设计一个类或者结构(体):

在头文件(*.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 <typename T>
void testFunc(T& x);
 
#endif

实现代码

// 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;
}

主函数引用

// 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 &)”

(??$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()的入口

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

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

第一种方法:

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

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

  • 6
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值