【c++】因模板类导致的 LNK2019 错误:“ 无法解析的外部符号 ” 详解_lnk2019无法解析的外部符号-程序员宅基地

技术标签: C/C++  C++  模板类  编程语言  LNK2019 错误:“ 无法解析的外部符号 ”  

最近在做项目的时候用到了模板类

果不其然出现了那个错误

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.在实现模板类的文件中调用一下模板类。原因:调用一下让模板类函数得到具现化。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_41884002/article/details/99816073

智能推荐

while循环&CPU占用率高问题深入分析与解决方案_main函数使用while(1)循环cpu占用99-程序员宅基地

文章浏览阅读3.8k次,点赞9次,收藏28次。直接上一个工作中碰到的问题,另外一个系统开启多线程调用我这边的接口,然后我这边会开启多线程批量查询第三方接口并且返回给调用方。使用的是两三年前别人遗留下来的方法,放到线上后发现确实是可以正常取到结果,但是一旦调用,CPU占用就直接100%(部署环境是win server服务器)。因此查看了下相关的老代码并使用JProfiler查看发现是在某个while循环的时候有问题。具体项目代码就不贴了,类似于下面这段代码。​​​​​​while(flag) {//your code;}这里的flag._main函数使用while(1)循环cpu占用99

【无标题】jetbrains idea shift f6不生效_idea shift +f6快捷键不生效-程序员宅基地

文章浏览阅读347次。idea shift f6 快捷键无效_idea shift +f6快捷键不生效

node.js学习笔记之Node中的核心模块_node模块中有很多核心模块,以下不属于核心模块,使用时需下载的是-程序员宅基地

文章浏览阅读135次。Ecmacript 中没有DOM 和 BOM核心模块Node为JavaScript提供了很多服务器级别,这些API绝大多数都被包装到了一个具名和核心模块中了,例如文件操作的 fs 核心模块 ,http服务构建的http 模块 path 路径操作模块 os 操作系统信息模块// 用来获取机器信息的var os = require('os')// 用来操作路径的var path = require('path')// 获取当前机器的 CPU 信息console.log(os.cpus._node模块中有很多核心模块,以下不属于核心模块,使用时需下载的是

数学建模【SPSS 下载-安装、方差分析与回归分析的SPSS实现(软件概述、方差分析、回归分析)】_化工数学模型数据回归软件-程序员宅基地

文章浏览阅读10w+次,点赞435次,收藏3.4k次。SPSS 22 下载安装过程7.6 方差分析与回归分析的SPSS实现7.6.1 SPSS软件概述1 SPSS版本与安装2 SPSS界面3 SPSS特点4 SPSS数据7.6.2 SPSS与方差分析1 单因素方差分析2 双因素方差分析7.6.3 SPSS与回归分析SPSS回归分析过程牙膏价格问题的回归分析_化工数学模型数据回归软件

利用hutool实现邮件发送功能_hutool发送邮件-程序员宅基地

文章浏览阅读7.5k次。如何利用hutool工具包实现邮件发送功能呢?1、首先引入hutool依赖<dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.7.19</version></dependency>2、编写邮件发送工具类package com.pc.c..._hutool发送邮件

docker安装elasticsearch,elasticsearch-head,kibana,ik分词器_docker安装kibana连接elasticsearch并且elasticsearch有密码-程序员宅基地

文章浏览阅读867次,点赞2次,收藏2次。docker安装elasticsearch,elasticsearch-head,kibana,ik分词器安装方式基本有两种,一种是pull的方式,一种是Dockerfile的方式,由于pull的方式pull下来后还需配置许多东西且不便于复用,个人比较喜欢使用Dockerfile的方式所有docker支持的镜像基本都在https://hub.docker.com/docker的官网上能找到合..._docker安装kibana连接elasticsearch并且elasticsearch有密码

随便推点

Python 攻克移动开发失败!_beeware-程序员宅基地

文章浏览阅读1.3w次,点赞57次,收藏92次。整理 | 郑丽媛出品 | CSDN(ID:CSDNnews)近年来,随着机器学习的兴起,有一门编程语言逐渐变得火热——Python。得益于其针对机器学习提供了大量开源框架和第三方模块,内置..._beeware

Swift4.0_Timer 的基本使用_swift timer 暂停-程序员宅基地

文章浏览阅读7.9k次。//// ViewController.swift// Day_10_Timer//// Created by dongqiangfei on 2018/10/15.// Copyright 2018年 飞飞. All rights reserved.//import UIKitclass ViewController: UIViewController { ..._swift timer 暂停

元素三大等待-程序员宅基地

文章浏览阅读986次,点赞2次,收藏2次。1.硬性等待让当前线程暂停执行,应用场景:代码执行速度太快了,但是UI元素没有立马加载出来,造成两者不同步,这时候就可以让代码等待一下,再去执行找元素的动作线程休眠,强制等待 Thread.sleep(long mills)package com.example.demo;import org.junit.jupiter.api.Test;import org.openqa.selenium.By;import org.openqa.selenium.firefox.Firefox.._元素三大等待

Java软件工程师职位分析_java岗位分析-程序员宅基地

文章浏览阅读3k次,点赞4次,收藏14次。Java软件工程师职位分析_java岗位分析

Java:Unreachable code的解决方法_java unreachable code-程序员宅基地

文章浏览阅读2k次。Java:Unreachable code的解决方法_java unreachable code

标签data-*自定义属性值和根据data属性值查找对应标签_如何根据data-*属性获取对应的标签对象-程序员宅基地

文章浏览阅读1w次。1、html中设置标签data-*的值 标题 11111 222222、点击获取当前标签的data-url的值$('dd').on('click', function() { var urlVal = $(this).data('ur_如何根据data-*属性获取对应的标签对象

推荐文章

热门文章

相关标签