C++模板声明与实现分开--由此想到的编译,链接原理
参考了以下两篇文章:
问题来源:当模板文件的实现与声明分开在不同文件中时,链接时会提示找不到相应模板函数,如下

一,编译和链接的大概原理:
1,编译,遍历工程的所有代码文件,进行文件分析,这里的分析与文件后缀无关,并不是说以CPP文件为依据,源文件后缀名可以改为任何名字。
编译以文件为单位,将此文件#include的所有文件拿进来,写进此文件中,包含进来的东西可能是函数声明,也可能是函数的实现体。
如果#include "test.h",则包含进来的是一些函数和变量的声明,如果 #include "test.cpp",则其中的函数实现代码也被包含进来了。
编译的结果是一个obj文件,如test.cpp编译后是一个test.obj文件,里面是二进制的汇编指令。
此时,每个编译单元编译完成后,会提供三个表用于后面链接,这三个表分别是【未解决符号表】,【已解决符号表】,【重定向表】
已解决符号表是本编译单元中定义的所有符号,包括变量和函数。
未解决符号表是本编译单元中用到的外部符号,包括变量和函数。
重定向表是用于计算每个编译单元在最终链接完成的EXE中的偏移地址。
2,链接,对所有的obj文件进行拼接。
为什么要拼接?对于每个obj文件,其中若调用了其它文件的函数(外部调用),就需要知道此外部函数的具体实现,这在编译时是不关心的。
这时候去查找所有obj文件的【已解决符号表】中查找此外部函数的实现体,若有两个以上的obj都有此实现,则链接出错,因为函数实现不唯一了,这不允许。此错误就是常见的
XXX 已经在 xxx.obj中定义了,如下:

二,实例分析
1,一个头文件被多个CPP包含时编译链接正确,一个CPP文件被多个其它CPP文件包含时编译正确,链接出错,报错为 XXX 已经在 xxx.obj中定义。
因为CPP中有函数的实现体,每被包含一次就多了一个实现,导致一个函数在不同CPP文件中被多次实现,重复了。
头文件被多次包含为什么没问题?关键是每个头文件开头都有宏 #pragma once,该宏确保了头文件只会被包含一次
2,模板文件的特殊性。
模板文件只有在实例化时才能确定其具体的实现体,所以如果将模板文件的声明和函数体分开在.h和.cpp中,当编译cpp时,并不会产生函数的具体实现体。当在其它文件中#include "template.h"时,会提示找不到函数的定义。
解决方法:在需要使用模板函数的地方,#include "template.cpp",即包含它的CPP文件,而不是.h文件。
原因:使用模板函数的地方,比如 addobj<cube>(),传了具体的模板类型给函数,这样模板函数就能到CPP文件中找到对应的实现体将cube传给模板参数而实例化了。
3,综合的例子,若一个类中既有模板函数,又有非模板函数,那么只能将模板函数的声明与定义写在一个文件中,分开到两个文件是不行的。
-------------------------------------------------------------------------------------------------------------------------------
编译是以CPP为单元进行的,各个编译单元之间相互独立,互不感知。这也是联合联系和多编程编译的基础。
重复包含是什么?
重复包含是一个文件多次包含了另一个文件。
而多个文件都包含了同一个文件不是重复包含。
#pragma once的作用是防止x.h被a.cpp重复包含,而不是防止x.h被b.cpp包含后不再被c.cpp包含
文件 x.h
#pragma once
class CA{};
文件 a.cpp
#include "x.h"
#include "x.h"
文件 b.cpp
#include "x.h"
文件 c.cpp
#include "x.h"
原因要从符号表说起,每个CPP编译时都会产生三个表,【已解决符号表】,【未解决符号表】,【重定向表】
1,如上面a.cpp的情况,包含了两次 x.h,这时候x.h中声明的相关符号就会在【已解决符号表】中重复,于是报错。
2,对于上面 b.cpp, c.cpp的情况,由于二者有相互独立的符号表,所以不会报重,不会报错。
在链接时,是从所有符号表中查找相关符号的定义(实现),符号在多个符号表中声明没关系
C++模板声明与实现分开--由此想到的编译,链接原理的更多相关文章
- C++中重定义的问题——问题的实质是声明和定义的关系以及分离式编译的原理
这里的问题实质是我们在头文件中直接定义全局变量或者函数,却分别在主函数和对应的cpp文件中包含了两次,于是在编译的时候这个变量或者函数被定义了两次,问题就出现了,因此,我们应该形成一种编码风格,即: ...
- c++函数模板声明与定义相分离
最近在仿写stl,发现stl源码中将模板的声明与定义写在一起实在很不优雅.自己尝试用“传统”方法,及在.h文件里声明,在.cpp文件里定义,然后在main函数里包含.h头文件,这样会报链接错误.这是因 ...
- C++类模板声明与定义为何不能分开
我们用C++写类的时候,通常会将.cpp和.h文件分开写,即实现和声明分开写了:但在C++的类模板中,这种写法是错误的. 在<C++编程思想>的第16章的"16.3模板语法&qu ...
- 面试问题之C++语言:类模板声明与定义为何不能分开
C++中每个对象所占用的空间大小,是在编译的时候就确定的,在模板类没有真正的被使用之前,编译器是无法知道,模板类中使用模板类型的对象的所占用的空间的大小的.只有模板被真正使用的时候,编译器才知道,模板 ...
- c++模板使用及实现模板声明定义的分离
c++模板是编译器构造具体实例类型的模型,使类型参数化,是泛型编程的基础,泛型就是独立于特定类型. 一.模板分为函数模板和类模板两种. 函数模板:template <class 形参名,clas ...
- 关于C++编译链接和模板函数
一,关于编译链接编译指的的把编译单元生成目标文件的过程链接是把目标文件链接到一起的过程编译单元:可以认为是一个.c或者.cpp文件.每个编译单元经过预处理会得到一个临时的编译单元.预处理会间接包含其他 ...
- 模板函数(template function)出现编译链接错误(link error)之解析
总的结论: 将template function 或者 template class的完整定义直接放在.h文件中,然后加到要使用这些template function的.cpp文件中. 1. 现 ...
- C和C指针小记(六)-基本声明、指针声明、typedef 、常量、作用域、链接属性、存储类型、static
1.变量的声明 声明变量的基本形式: 说明符号(一个或者多个) 声明表达式列表 说明符 (specifier) 包含一些关键字,用于描述被声明的标识符的基本类型,它也可用户改变标识符的缺省存储类型和作 ...
- C++/CLI中class成员声明与实现分开在不同文件时必须添加namespace
以下是我的代码: //TaskConfigFile.h #pragma once using namespace System::Collections::Generic; using namespa ...
随机推荐
- 小白学Python(20)—— Turtle 海龟绘图
Turtle库是Python语言中一个很流行的绘制图像的函数库,想象一个小乌龟,在一个横轴为x.纵轴为y的坐标系原点,(0,0)位置开始,它根据一组函数指令的控制,在这个平面坐标系中移动,从而在它爬行 ...
- 计算机系统结构总结_Scoreboard and Tomasulo
Textbook:<计算机组成与设计——硬件/软件接口> HI<计算机体系结构——量化研究方法> QR 超标量 前面讲过超标量的概念.超标量的目的就是实现指 ...
- 前端为什么要学习 Selenium
如果你正在阅读本文,那么可能希望从手动测试升级为自动化测试.你是对的,因为你需要学习 Selenium.我这样说是因为自动化测试已经风靡全球,而且业界正很缺少 Selenium 认证专家. 你可能还想 ...
- KVC,KVO详解
Key Value Coding Key Value Coding是cocoa的一个标准组成部分,它能让我们可以通过name(key)的方式访问property, 不必调用明确的property ac ...
- ELK集群搭建
基于5台虚拟机,搭建ELK集群. 方案: 1. ELK是日志分析平台,而不是一款软件,是一整套解决方案,是三个软件产品的首字母缩写,ELK分别代表: Elasticsearch:负责日志检索和储存 L ...
- 011-linux服务管理
linux服务管理 [root@zabbix lianxi]# chkconfig --list 注:该输出结果只显示 SysV 服务,并不包含 原生 systemd 服务.SysV 配置数据 可能被 ...
- HBase(三)——搭建Fully-distributed
HBase搭建--Fully-distributed 1.搭建方式说明 By default, HBase runs in standalone mode. Both standalone mode ...
- DDD领域驱动设计初探(六):领域服务
前言:之前一直在搭建项目架构的代码,有点偏离我们的主题(DDD)了,这篇我们继续来聊聊DDD里面另一个比较重要的知识点:领域服务.关于领域服务的使用,书中也介绍得比较晦涩,在此就根据博主自己的理解谈谈 ...
- VMmare下安装redhat
一.虚拟机必须安装在自定义的文件夹下,虚拟硬盘文件必须存放在自定义路径下(避免中文) 二.安装时选择linux类型时必须选择red hat enterprise linux 5 64位 三.操作系统名 ...
- Kali Linux的发展史,专为数字取证和渗透测试而设计
Kali Linux拥有非常强大的网络功能,Kali Linux是一个Debian衍生的Linux发行版,专为数字取证和渗透测试而设计.它是由OffensiveSecurityLtd维护和资助,是由M ...