参考了以下两篇文章:

C++编译链接原理简介 

语言程序编译过程 2

问题来源:当模板文件的实现与声明分开在不同文件中时,链接时会提示找不到相应模板函数,如下

一,编译和链接的大概原理:

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++模板声明与实现分开--由此想到的编译,链接原理的更多相关文章

  1. C++中重定义的问题——问题的实质是声明和定义的关系以及分离式编译的原理

    这里的问题实质是我们在头文件中直接定义全局变量或者函数,却分别在主函数和对应的cpp文件中包含了两次,于是在编译的时候这个变量或者函数被定义了两次,问题就出现了,因此,我们应该形成一种编码风格,即: ...

  2. c++函数模板声明与定义相分离

    最近在仿写stl,发现stl源码中将模板的声明与定义写在一起实在很不优雅.自己尝试用“传统”方法,及在.h文件里声明,在.cpp文件里定义,然后在main函数里包含.h头文件,这样会报链接错误.这是因 ...

  3. C++类模板声明与定义为何不能分开

    我们用C++写类的时候,通常会将.cpp和.h文件分开写,即实现和声明分开写了:但在C++的类模板中,这种写法是错误的. 在<C++编程思想>的第16章的"16.3模板语法&qu ...

  4. 面试问题之C++语言:类模板声明与定义为何不能分开

    C++中每个对象所占用的空间大小,是在编译的时候就确定的,在模板类没有真正的被使用之前,编译器是无法知道,模板类中使用模板类型的对象的所占用的空间的大小的.只有模板被真正使用的时候,编译器才知道,模板 ...

  5. c++模板使用及实现模板声明定义的分离

    c++模板是编译器构造具体实例类型的模型,使类型参数化,是泛型编程的基础,泛型就是独立于特定类型. 一.模板分为函数模板和类模板两种. 函数模板:template <class 形参名,clas ...

  6. 关于C++编译链接和模板函数

    一,关于编译链接编译指的的把编译单元生成目标文件的过程链接是把目标文件链接到一起的过程编译单元:可以认为是一个.c或者.cpp文件.每个编译单元经过预处理会得到一个临时的编译单元.预处理会间接包含其他 ...

  7. 模板函数(template function)出现编译链接错误(link error)之解析

    总的结论:    将template function 或者 template class的完整定义直接放在.h文件中,然后加到要使用这些template function的.cpp文件中. 1. 现 ...

  8. C和C指针小记(六)-基本声明、指针声明、typedef 、常量、作用域、链接属性、存储类型、static

    1.变量的声明 声明变量的基本形式: 说明符号(一个或者多个) 声明表达式列表 说明符 (specifier) 包含一些关键字,用于描述被声明的标识符的基本类型,它也可用户改变标识符的缺省存储类型和作 ...

  9. C++/CLI中class成员声明与实现分开在不同文件时必须添加namespace

    以下是我的代码: //TaskConfigFile.h #pragma once using namespace System::Collections::Generic; using namespa ...

随机推荐

  1. The kth great number

    The kth great number Problem Description Xiao Ming and Xiao Bao are playing a simple Numbers game. I ...

  2. HNCPC2019H 有向图

    题目 设\(f_i\)表示经过\(i\)的期望次数.那么显然答案\(ans_j=\sum\limits_{i=1}^nf_iP_{i,j}\). 我们可以轻松地列出转移式子: \[ f_1=\sum\ ...

  3. git命令?

    #文件及文件夹创建删除    mkdir  文件名称    (创建文件夹)    touch  文件名称    (创建文件)    rm -r  文件名称     (递归删除)    rm -rf 文 ...

  4. 模板 - SG函数

    https://scut.online/p/93 每次取走的石子是b的幂次.打表暴力发现规律. #include <bits/stdc++.h> using namespace std; ...

  5. 剑指offer-用两个栈来实现一个队列-队列与栈-python

    用两个栈来实现一个队列,完成队列的Push和Pop操作. 队列中的元素为int类型. 思路:使用两个栈,stackA 用来接收node stackB 用来接收 stackA 的出栈 # -*- cod ...

  6. SVN更新报错:Checksum mismatch for 解决办法

    问题: Checksum mismatch while updating '……'; expected: '3f9fd4dd7d1a0304d8020f73300a3e07', actual: 'cd ...

  7. mongo 数据库操作

    启动和关闭数据库 启动 # mongodb 默认使用执行 mongod 命令所处的盘的根目录下  /data/db 作为自己的数据存储目录 #   所以在第一次执行该命令之前先自己动手新建一个  /d ...

  8. Mongo--04 Mongo分片集群

    目录 一.分片的概念 二. 分片工作原理 三.IP端口目录规划 1.IP端口规划 2.目录规划 四.分片集群搭建副本集步骤 1.安装软件 2.创建目录 3.创建配置文件 4.优化警告 5.启动服务 6 ...

  9. Codeforces Round #426 (Div. 2) - B

    题目链接:http://codeforces.com/contest/834/problem/B 题意:一共有26个门(A-Z表示),然后现在有n个人要走的门和k个守卫.每当有人要经过某个门时,门要一 ...

  10. 实现自己的BeanFactory、AOP以及声明式事务

    实现自己的BeanFactory                                                                   在使用spring时,我们很少用& ...