目录
- 前言
- 分离编译模式
- 普通函数的分离编译(正常)
- 模板函数的分离编译(出错)
- 分析
- 解决方式
- 拓展--extern关键字
- extern"C"
- extern+变量
- extern+模板--控制实例化
前言
分离编译模式
一个项目如果有多个源文件.c组成,每个源文件单独编译,形成目标文件。最后通过链接器将所有的目标文件链接起来,形成一个可执行的文件。
这种就是.h头文件和.c源文件配合使用的模式;
分离式编译的优势
如果工程巨大(比如linux内核源码) , 当你只修改了其中一个工程文件.c的时候,重新编译只需要编译改动过的文件就可以了,不需要将全部工程文件.cpp全部重新编译。
普通函数的分离编译(正常)
各文件代码如下:
//"fun.h"
int fun(int a);
//"fun.cpp"
#include"fun.h"
int fun(int a)
{
return a;
}
//main.cpp
#include<iostream>
#include"fun.h"
int main()
{
cout<<fun(1)<<endl;
return 0;
}
结果:正常编译,正常运行;
模板函数的分离编译(出错)
各文件代码如下:
//"fun.h"
template<class T>
int fun(T a);
//"fun.cpp"
template<class T>
int fun(T a)
{
return a;
}
//main.cpp
#include<iostream>
#include"fun.h"
int main()
{
cout<<fun(1)<<endl;
return 0;
}
结果:编译出错
分析
实例化
实例化是指编译器使用函数(或者是类)模板为特定类型生成函数(类)定义。编译器不会为函数(或者类)模板生成定义,只有当我们为函数(或者类)模板指定了一个特定类型时,编译器才会生成。编译器为特定类型的函数(或者类)模板生成定义的行为被称为实例化。
普通函数在编译时就实例化,生成fun.obj目标文件;
之后main.cpp编译的时候,看到了fun.h的头文件中fun函数的声明,没有看到定义,链接器,在链接时就看到了fun.obj目标文件中fun函数实例出来的对象,进行链接;
C++规定模板函数只有在使用时实例化:
fun.cpp定义了模版方法,但是没有使用,因此目标文件fun.obj中不会有实例化的模版方法。
而main.cpp只能看到函数的声明,并且链接器链接时也找不到fun.obj中的fun函数对象, 因此报错;
解决方式
- 将模板的声明和定义放在一个.hpp文件里,或者像C语言一样,放在两个.h中 ,总之让编译器在一个源文件,比如main.c中能同时看到模板的定义和初始化就行;
- extern 控制实例化
拓展–extern关键字
extern"C"
extern “C”的作用就是告诉C++编译器,将指定的函数用C规则编译;
因为函数重载,函数名修饰规则不同的原因,如果C++中调用C文件,编译可能会出错;
extern "C"的主要功能是为了能够正确实现C++代码调用C语言代码。
extern+变量
声明某个变量或者函数在外部文件,直接拿来用;
extern+模板–控制实例化
上面说的模板使用时实例化,那工程很大,每个文件都需要用这个模板。都需要实例化一份?额外开销会非常严重。
这时候可以extern修饰他,意味着其他文件中的模板func也能在这里用,就是说公用一份,只需要在一个源文件中实例化这个模板即可,生成的obj其他文件能链接到!
提一嘴 新标准的显示实例化指定类型,也能解决上面模板多文件实例化太多造成额外开销的问题