c++ 模板编程

c++ 模板编程

C++中模板分为函数模板和类模板

函数模板:是一种抽象函数定义,它代表一类同构函数。

类模板:是一种更高层次的抽象的类定义。

优缺点

优点

  1. 代码复用
      模板允许编写与具体数据类型无关的代码,从而实现代码复用。你可以针对不同的数据类型使用相同的模板函数或模板类,而无需为每种数据类型重复编写代码。
  2. 类型安全
      模板是在编译时生成代码的,编译器会对模板实例化的代码进行严格的类型检查,从而确保类型安全,避免运行时类型错误。
  3. 性能优化
      模板在编译时会生成具体类型的代码,相当于为每种使用的类型生成了定制的函数或类。这种机制避免了运行时多态的开销,性能接近手动为每种类型编写的代码。
  4. 支持泛型编程
      模板是 C++ 泛型编程的核心工具,使得开发者能够以声明性和抽象的方式定义算法和数据结构。例如,标准模板库(STL)中的容器和算法就是基于模板实现的。
  5. 灵活性强
      模板支持元编程(template metaprogramming),允许在编译时执行复杂的计算和逻辑。这为高级编程提供了强大的能力,比如静态多态和编译时优化。
  6. 消除冗余代码
      使用模板可以大大减少重复代码,降低维护成本。例如,可以为不同类型的数据结构(如 std::vector 和 std::vector)使用相同的模板定义,而无需单独实现。

缺点

  1. 编译时间长
      模板会在每次实例化时生成具体的代码,这可能导致编译时间显著增加,尤其是在大型项目中使用了大量模板时。
  2. 可读性和可维护性差
      模板代码往往较为复杂,尤其是涉及到嵌套模板、模板元编程或 SFINAE(Substitution Failure Is Not An Error)时,代码的可读性和可调试性会大大降低。
  3. 代码膨胀(Code Bloat)
      模板会为每种使用的类型生成一份实例化代码,可能导致最终的二进制文件体积显著增加。虽然现代编译器可以通过模板共享(template pooling)来优化,但这个问题仍然存在。
  4. 错误信息复杂
      模板相关的编译错误通常非常复杂且难以理解,尤其是当涉及到深度嵌套的模板或复杂的模板元编程时。这会增加调试和排错的难度。
  5. 二进制接口(ABI)兼容性问题
      模板代码是基于实例化生成的,不同编译器或编译器版本可能会生成不同的二进制代码,导致模板类或函数难以在动态链接库(DLL/so)之间共享。
  6. 限制动态多态
      模板实例化的代码在编译时就确定了具体的类型,因此不支持运行时的动态类型分派(如虚函数机制)。这使得模板更适合静态多态,而不适合动态多态。
  7. 缺乏显式约束(在 C++20 前)
      在 C++20 之前,模板参数的约束依赖隐式的接口契约,编译器只在实例化时检查是否满足接口要求,这可能导致错误信息迟滞且难以定位。
      C++20 引入了 Concepts,显著改善了这一问题,但在旧版本中,约束模板的机制仍显不足。
  8. 调试困难
      模板代码在调试工具中可能表现为一大堆展开后的代码片段,使得调试过程变得复杂且耗时。
  9. 潜在的库升级问题
      如果一个库的大量接口通过模板实现,那么升级库可能导致用户代码的重新编译,因为模板实例化通常是内联的。

模版特化

需要对某些参数的模版进行特殊处理,此时可以用特化

全特化

  通过全特化一个模板,可以对一个特定参数集合自定义当前模板,类模板和函数模板都可以全特化。 全特化的模板参数列表应当是空的,并且应当给出"模板实参"列表:

// 全特化类模板
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:: 显示指定,或者用 using base::xxfunc; bast:: 如果是虚函数会破会多态性

模板函数要声明为inline,可以节省内存

事实上类型T::const_iterator依赖于模板参数T, 模板中依赖于模板参数的名称称为从属名称(dependent name), 当一个从属名称嵌套在一个类里面时,称为嵌套从属名称(nested dependent name)。 其实T::const_iterator还是一个嵌套从属类型名称(nested dependent type name)。

嵌套从属名称是需要用typename声明的,其他的名称是不可以用typename声明的。

posted @   卑以自牧lq  阅读(28)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
点击右上角即可分享
微信分享提示