学习模板的时候,会产生一个疑问,模板函数既然可以适配很多类型,那是不是编译的时候,会生成匹配所有类型的函数版本?如果是这样的话,如果用了很多模板,编译产物岂不是会膨胀到很大?实则不然。

模板函数本身只是一张 “蓝图”,不会生成机器码;只有在编译期遇到具体类型实参、真的要用到它时,才会触发模板实例化 ,生成对应类型的专属函数版本,这才是真正可用的函数。

0.1 基本概念

0.1.1 模板定义

template<typename T> void func(T t){...}

只是语法模板,不生成任何函数代码,不占内存。

0.1.2 模板实例化

编译器根据具体类型,把模板 “翻译成” 普通函数,这才是真正的函数实体,能被调用、有地址、有机器码。

0.2 出发模板实例化(真正生成函数)的时机

0.2.1 直接调用模板函数

template<typename T> void f(T) {} 
 
int main() { 
	f<int>(10); // 显式指定类型 → 立即实例化 f<int> 
	f(20); // 编译器推导 T=int → 实例化 f<int> 
}

时机:编译期遇到调用,立刻生成对应类型函数。

0.2.2 取模板函数的地址

哪怕不调用,只要取地址,也会强制实例化:

auto p = &f<double>; // 没调用,但取地址 → 必须生成 f<double>

0.2.3 用作函数参数、模板实参

void foo(void(*pf)(int)) {} 
foo(f<int>); // 作为参数传进去 → 触发实例化

0.2.4 类模板里的成员模板函数

template<typename T> 
class A { 
public: 
	template<typename U> 
	void fun(U u) {} }; 
// 只有下面这行执行编译时,才实例化 A<int>::fun<double> 
A<int>{}.fun<double>(3.14);

0.3 总结

只写模板定义,不使用任何具体类型,编译器不会生成任何函数,无代码、无开销。

前边所说的这些会触发代码实例化的都是隐式实例化,即正常调用、推导、取地址时,编译器会自动帮你生成版本。还有一些显示实例化的方法,即强制提前生成:

// 强制编译器立刻生成 f<int> 版本,哪怕暂时不用 
template void f<int>(int);

模板函数只是蓝图,不生成代码;

编译期遇到「调用、类型推导、取地址、传参」时,才实例化出对应类型的真实函数,这就是它真正被 “生成 / 调用” 的时机。