c++ 模板编程
C++中模板分为函数模板和类模板
函数模板:是一种抽象函数定义,它代表一类同构函数。
类模板:是一种更高层次的抽象的类定义。
优缺点
优点
- 代码复用
模板允许编写与具体数据类型无关的代码,从而实现代码复用。你可以针对不同的数据类型使用相同的模板函数或模板类,而无需为每种数据类型重复编写代码。 - 类型安全
模板是在编译时生成代码的,编译器会对模板实例化的代码进行严格的类型检查,从而确保类型安全,避免运行时类型错误。 - 性能优化
模板在编译时会生成具体类型的代码,相当于为每种使用的类型生成了定制的函数或类。这种机制避免了运行时多态的开销,性能接近手动为每种类型编写的代码。 - 支持泛型编程
模板是 C++ 泛型编程的核心工具,使得开发者能够以声明性和抽象的方式定义算法和数据结构。例如,标准模板库(STL)中的容器和算法就是基于模板实现的。 - 灵活性强
模板支持元编程(template metaprogramming),允许在编译时执行复杂的计算和逻辑。这为高级编程提供了强大的能力,比如静态多态和编译时优化。 - 消除冗余代码
使用模板可以大大减少重复代码,降低维护成本。例如,可以为不同类型的数据结构(如 std::vector和 std::vector )使用相同的模板定义,而无需单独实现。
缺点
- 编译时间长
模板会在每次实例化时生成具体的代码,这可能导致编译时间显著增加,尤其是在大型项目中使用了大量模板时。 - 可读性和可维护性差
模板代码往往较为复杂,尤其是涉及到嵌套模板、模板元编程或 SFINAE(Substitution Failure Is Not An Error)时,代码的可读性和可调试性会大大降低。 - 代码膨胀(Code Bloat)
模板会为每种使用的类型生成一份实例化代码,可能导致最终的二进制文件体积显著增加。虽然现代编译器可以通过模板共享(template pooling)来优化,但这个问题仍然存在。 - 错误信息复杂
模板相关的编译错误通常非常复杂且难以理解,尤其是当涉及到深度嵌套的模板或复杂的模板元编程时。这会增加调试和排错的难度。 - 二进制接口(ABI)兼容性问题
模板代码是基于实例化生成的,不同编译器或编译器版本可能会生成不同的二进制代码,导致模板类或函数难以在动态链接库(DLL/so)之间共享。 - 限制动态多态
模板实例化的代码在编译时就确定了具体的类型,因此不支持运行时的动态类型分派(如虚函数机制)。这使得模板更适合静态多态,而不适合动态多态。 - 缺乏显式约束(在 C++20 前)
在 C++20 之前,模板参数的约束依赖隐式的接口契约,编译器只在实例化时检查是否满足接口要求,这可能导致错误信息迟滞且难以定位。
C++20 引入了 Concepts,显著改善了这一问题,但在旧版本中,约束模板的机制仍显不足。 - 调试困难
模板代码在调试工具中可能表现为一大堆展开后的代码片段,使得调试过程变得复杂且耗时。 - 潜在的库升级问题
如果一个库的大量接口通过模板实现,那么升级库可能导致用户代码的重新编译,因为模板实例化通常是内联的。
模版特化
需要对某些参数的模版进行特殊处理,此时可以用特化
全特化
通过全特化一个模板,可以对一个特定参数集合自定义当前模板,类模板和函数模板都可以全特化。 全特化的模板参数列表应当是空的,并且应当给出"模板实参"列表:
// 全特化类模板
template <>
class A<int, double>{
int data1;
double data2;
};
// 函数模板
template <>
int max(const int lhs, const int rhs){
return lhs > rhs ? lhs : rhs;
}
注意类模板的全特化时在类名后给出了"模板实参",但函数模板的函数名后没有给出"模板实参"。 这是因为编译器根据int max(const int, const int)的函数签名可以推导出来它是T max(const T, const T)的特化。
特化的歧义
上述函数模板不需指定"模板实参"是因为编译器可以通过函数签名来推导,但有时这一过程是有歧义的:
template <class T>
void f(){ T d; }
template <>
void f(){ int d; }
此时编译器不知道f()是从f
error: no function template matches function template specialization 'f'
这时我们便需要显式指定"模板实参":
template <class T>
void f(){ T d; }
template <>
void f<int>(){ int d; }
偏特化
类似于全特化,偏特化也是为了给自定义一个参数集合的模板,但偏特化后的模板需要进一步的实例化才能形成确定的签名。 值得注意的是函数模板不允许偏特化
template <class T2>
class A<int, T2>{
...
};
函数模板是不允许偏特化的,下面的声明会编译错:
template <class T1, class T2>
void f(){}
template <class T2>
void f<int, T2>(){}
但函数允许重载,声明另一个函数模板即可替代偏特化的需要:
template <class T2>
void f(){} // 注意:这里没有"模板实参"
多数情况下函数模板重载就可以完成函数偏特化的需要,一个例外便是std命名空间。 std是一个特殊的命名空间,用户可以特化其中的模板,但不允许添加模板(其实任何内容都是禁止添加的)。 因此在std中添加重载函数是不允许的,在Effective C++: Item 25中给出了一个更详细的案例。
使用总结
- 模板类的成员函数要和类放在同一个文件内
继承模板类调用父类的函数要加 this 或者 base
模板函数要声明为inline,可以节省内存
事实上类型T::const_iterator依赖于模板参数T, 模板中依赖于模板参数的名称称为从属名称(dependent name), 当一个从属名称嵌套在一个类里面时,称为嵌套从属名称(nested dependent name)。 其实T::const_iterator还是一个嵌套从属类型名称(nested dependent type name)。
嵌套从属名称是需要用typename声明的,其他的名称是不可以用typename声明的。
标签:函数,int,代码,编程,c++,template,模板,特化 From: https://www.cnblogs.com/AngleLin/p/18595904