C++模板特化、分离编译
模板基础看这篇 C++模板基础-CSDN博客
非类型模板参数
类、函数 的 模板参数:类型形参、非类型形参
类型形参:模板参数列表中,跟在 class、typename 后的参数类型名称
非类型形参:用一个常量作为模板参数,在模板中可将该参数当作 常量 使用
template<class T, size_t N, char ch>
class A
{T _arr[N];char _c = ch;
};int main()
{A<int, 100, 'a'> a;return 0;
}
注:非类型模板参数仅支持 整型家族(char、short、long、int...)
但是 从 C++20 开始,非类型模板参数(NTTP)正式放宽限制,不再局限于整型、枚举、指针/引用等传统类型
函数模板特化
1、必须有原模板的存在
2、特化的模板 在 关键字 template 后接一对 空的 尖括号 < >
3、函数名后跟 一对 尖括号 < >,其中指定需要特化的类型(显式)
4、特化模板的 形参表,必须和 基础函数模板(原)的参数类型 完全相同
template<class T>
T Add(const T& x, const T& y)
{cout << "T Add(const T& x, const T& y)" << endl;return x + y;
}template<> // 特化
double Add<double>(const double& x, const double& y)
{cout << "double Add(const double& x, const double& y)" << endl;return x + y;
}int main()
{cout << Add(1, 2) << endl;cout << Add(1.1, 2.2) << endl;return 0;
}
注意
函数模板能重载,就不要特化;否则会出现一些奇怪的的调用规则报错......
1、重载 和 特化:两套完全不同的匹配规则
重载决议 (Overload Resolution)——只看函数名、参数表、模板参数推导,无视特化;
特化决议 (Template Specialization)——在重载决议之后才发生,优先级最低。
template<typename T> void foo(T); // 主模板
template<> void foo<int>(int); // 特化
void foo(int); // 普通重载foo(3); // 调用的是普通重载,而非特化
2、函数模板只有全特化,没有偏特化
3、C++官方也多次说明:函数模板应重载,不应特化
类模板特化
类模板特化的 基本要求和 函数模板特化 一样,要有原模板存在、形参表相同、
类模板特化:
1、特化的模板 也在 关键字 template 后接一对 空的 尖括号 < >
2、类名后跟 一对 尖括号 < >,其中指定需要特化的类型(显式)
全特化
特化 全部参数
template<class T1, class T2> // 原模板
class A
{
public:A(){cout << "A(T1, T2)" << endl;}private:T1 _b;T2 _c;
};template<> // 全特化
class A<int, char>
{
public:A(){cout << "A(int, char)" << endl;}private:int _b;char _c;
};int main()
{A<int, int> a;A<int, char> aa;return 0;
}
偏特化
特化 部分 参数
template<class T1, class T2> // 原模板
class A
{
public:A(){cout << "A(T1, T2)" << endl;}private:T1 _b;T2 _c;
};template<class T1> // 偏特化
class A<T1, char>
{
public:A(){cout << "A(T1, char)" << endl;}private:T1 _b;char _c;
};int main()
{A<int, int> a;A<int, char> aa;return 0;
}
小结
调用时 最匹配的原则: 先找 全特化 —— 偏特化 —— 原模板
template<class T1, class T2> // 原模板
class A
{
public:A(){cout << "A(T1, T2)" << endl;}private:T1 _b;T2 _c;
};template<> // 全特化
class A<int, char>
{
public:A(){cout << "A(int, char)" << endl;}
};template<class T1> // 偏特化
class A<T1, char>
{
public:A(){cout << "A(T1, char)" << endl;}private:T1 _b;char _c;
};int main()
{A<int, int> a;A<int, char> aa;return 0;
}
模板的分离编译问题
分离编译:一个程序 或 项目 由多个 源文件 共同实现,而 每个源文件 单独编译生成目标文件,最后将所有目标文件 链接,形成单一的 可执行文件 的过程称为 分离编译模式。
通俗点:声明放 .h,定义放 .cpp,先各自编译成 .o,再一次性链接成可执行文件
但是模板不能分离编译,即 在 a.h / a.hpp 中声明,在 a.cpp 中定义(包含了a.h,有声明有定义,本源文件可使用),在 main.cpp 中调用(包含了a.h,有声明无定义,模板未实例化,本源文件不可使用);
分析原因:
1、分离编译中:模板本身 —— 只有声明,没有定义,实例化了才有定义(生成定义才会放进 .o 的符号表);如果别的 .o文件中 再遇到 相同类型的实例化,会再生成一份;链接器最后负责 去重/合并(ODR 规则);
编译期 把 .cpp 编译成 .o,只留符号表;
链接期:把各 .o 拼起来,重定位并解析符号。
这套流程要求 符号完全确定,而 因为分离编译的原因,在 模板定义的 .cpp 中,编译器看到本 .cpp 中没有模板需要实例化,所以并未实例化出具体的 函数;
所以不会生成 模板 函数/类 符号,符号表中自然就没有;
那么链接时,main.cpp 中如果要调用,就找不到需要的函数,因为编译器本来就没有生成。
2、总结模板分离编译 后果
别的翻译单元只 #include 了声明,没见到定义,于是无法实例化 → 链接时找不到符号 → 报错 undefined reference
解决办法
1、显式实例化
既然 是因为 包含定义的 那个 源文件 没有实例化出具体的函数,那我就给他强制实例化,告诉编译器现在就生成某个具体类型的函数体,并导出符号:
// template 返回类型 函数名<模板实参>(形参表);
template int Addd<int>(int, int);// template 返回类型 函数名(形参表); 让编译器自己推模板实参
// template int Addd(int, int);
2、头文件即实现:把模板定义写在 .h / .hpp,所有 .cpp 都 #include 同一份定义。
小结
模板优点:
1、提高代码复用,更快二次开发,STL 诞生的基础;
2、编译期间类型检查
3、完全内联
模板缺点:
1、导致编译时间变长,导致代码膨胀问题
2、模板编译错误不易定位