持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第6天,点击查看活动详情
显示实例化
前面说了,函数模版本身不是函数,在调用函数时会实例化,根据调用参数生成对应的函数。这种实例化方式叫做隐式实例化。
有些情况,不能等到调用的时候再实例化,需要在代码中显示实例化,具体场景后面会说明。这时就需要用显示实例化的声明,指定模版参数,告诉编译器需要生成这样的函数实例。实例代码如下:
// 模板定义
template <typename T>
void func(T t) {
...
}
// 显示实例化
template void func<int>(int i);
显示实例化只是一个声明,并没有定义,它的函数定义用的就是函数模版的定义。
显示实例化与显示具体化,其实都会实例化函数,它们的区别在哪?也很简单,显示具体化要有单独的定义,显示实例化只是一个声明,它的定义和模板相同。
当我们需要为一组模板参数写一个特殊的函数定义,与模板定义不同,就需要具体化,否则只需要显示实例化声明即可。
需要注意,具体化与实例化的语法也不同,具体化以template <> 开头,函数名后面直接跟这参数列表;显示实例化则是 template + 函数名 + <模版参数列表> + (函数参数列表);
模板定义分离
前面说到,函数模板不是函数,它没有函数地址,只有实例化之后才会有函数地址。而隐式实例化需要编译器发现有调用这个函数模板,才会根据模板生成具体的函数。
但是,前面讲过C++的编译单元的概念,在编译时,编译器时一个一个编译单元来编译,然后再交给链接器。如果要隐式实例化一个模板,就需要满足两个条件:
- 在这个编译单元内有调用这个函数,有调用才会有隐式实例化
- 在这个编译单元内有函数模板的定义,有定义才能根据定义生成具体函数
也就是说,一个编译单元内要同时包含模板的定义和使用,才能正常隐式实例化。所以在使用模板时,必须将模板的定义include进来,而不是仅仅将模板的声明include进来。所以模板定义往往要写作.h头文件中。
将定义写在头文件中,会在多个编译单元中重复include,有些编译器会重复生成函数实例,导致编译结果太大。另外,将定义和声明分开在不同文件中会让声明看起来更清晰。那么,有没有办法能把定义和声明分开呢?
如果只需要为模板生成特定的几种实例,这时就可以用到显示实例化了。将模板声明写在头文件中,定义写在.cpp源文件中,并在定义后加上显示实例化。这时,编译器在编译这个.cpp源文件时,就会根据显示实例化的声明来生成函数实例,这样一来,其他编译单元无需隐式实例化,到了链接的步骤可以直接找到显示实例化的函数了。