本文主要是了解一下为什么模板的定义和声明不能分开,需要简单了解一下编译的四个阶段。
一、理解编译大致流程
整个编译流程分为:预处理、编译、汇编、链接,这里以源文件main.cpp文件为例。
- 预处理:对源文件进行宏替换、去注释、头文件展开,生成main.i文件。(此时的main.i文件依旧是cpp文件)
- 编译:对main.i文件进行语法语义检错,如果没有错误将其转换为汇编代码main.s文件。(此时的main.s文件是汇编代码)
- 汇编:将main.s文件解释为二进制文件main.o。
- 链接:在main.cpp中调用了函数,同时头文件展开时将函数的声明也引入到了main.cpp文件中,但是此时并没有函数的实现(也可以叫定义)。因此在函数的调用处其实还没有处理完,需要链接到源文件所使用的库文件中,去寻找该函数的实现,将函数的入口地址添加到函数调用处。这就是链接阶段要做的事情。
这里再提一嘴,同一个工程中的不同源文件的编译是独立的,互不干扰。
二、普通函数分离编译
结合下面这个场景来理解。
- 在头文件func.h中声明了一个普通函数p
- 在源文件func.cpp中引入func.h,对函数p进行定义
- 在源文件main.cpp文件中调用函数p
编译阶段编译器的流程。
- 预处理:在main.cpp和func.cpp中进行宏替换和头文件展开,生成main.i和func.i文件。
- 编译:对main.i和func.i进行语法检测,然后生成汇编文件main.s和func.s文件
- 汇编:将汇编文件main.s和func.s解释为二进制文件main.o和func.o
- 链接:在编译时,由于main.cpp中没有函数实现,因此函数调用还未处理,编译器认为这个函数p的实现在其他源文件中,在链接阶段会去其他源文件找,然后就在func.o中找到了,然后将函数地址填充。
三、模板分离编译
下面将普通函数换成模板函数来了解一下。
- 在头文件func.h中声明了一个函数函数p
- 在源文件func.cpp中引入func.h,对函数p进行定义
- 在源文件main.cpp文件中调用函数p
这里有个小知识,C++中模板在没有调用的时候不会实例化!!!
来看一下编译阶段流程。
- 预处理:在main.cpp和func.cpp中进行宏替换和头文件展开,生成main.i和func.i文件。
- 编译:对main.i和func.i进行语法检测,然后生成汇编文件main.s和func.s文件
- 汇编:将汇编文件main.s和func.s解释为二进制文件main.o和func.o
- 链接:在编译时,由于main.cpp中没有函数实现,因此函数调用还未处理,编译器认为这个函数p的实现在其他源文件中,在链接阶段会去其他源文件找。 但是,此时func.o中并没有p的实现!!!why? 因为func.cpp中并没有调用函数p,因此func.o中自然没有p函数的实现,因此,这时候编译器在别的源文件中就找不到p函数的实现,那么怎么可能链接成功呢?