前言
在 C++ 编程中,模板作为泛型编程的核心机制,不仅支持基础的类型参数化,还包含诸多进阶特性,这些特性是理解 STL 底层实现与复杂模板编程的关键。从typename与class的微妙差异,到非类型模板参数的编译期常量特性,再到模板特化的灵活应用,掌握这些知识能让你突破模板编程的瓶颈,深入理解 C++ 类型系统的强大表现力。
本章节将系统解析模板编程中的进阶概念:首先探讨typename在嵌套类型解析中的关键作用,明确其与class的本质区别;接着介绍非类型模板参数如何实现编译期配置,以及其在数组长度、缓冲区大小等场景中的应用;然后深入模板特化技术,通过函数模板特化与类模板的全特化 / 偏特化案例,展示如何针对特殊类型定制化实现;最后分析模板分离编译的困境与解决方案,理解模板实例化的编译期特性。
无论是解决模板编译错误时的困惑,还是优化复杂模板代码的性能,这些进阶知识都将成为你的有力工具。通过本章内容,你将从 “使用模板” 进阶到 “理解模板实现原理”,为深入 STL 源码阅读、模板元编程等高级主题奠定坚实基础。让我们一起揭开模板进阶特性的神秘面纱,感受 C++ 类型系统的精妙设计!
typename和class的唯一区别
在用类模板里面的类型(也就是嵌套类型)时,前面要加上typename或者在刚开始的类型形参那就用typename才行
原因:编译器不知道T::NestedType是静态成员变量还是类型,需要用
typename
来表示一下
非类型模板参数
模板参数分类类型形参与非类型形参。
类型形参即:出现在模板参数列表中,跟在class或者typename之类的参数类型名称。
非类型形参,就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用
但是非类型形参是有要求的:
1.必须要是整型的形参才行(比如:
size_t
和int
)2.这个形参必须是常量(在编译期间就能确定的),传参时必须直接传常数过去(在模板里面也不能对其改动)
eg:int a = 10; Stack<int,a>st1;这样是不行的
template<class T, size_t N> class Stack { public: void func() { _size = 1; } private: int_size; }; 在模板里面搞个func()的话,要调用,调用了里面的值才会改 比如: Stack<int,100>st1; st1.func();
模板的特化
概念:在原模板类的基础上,针对特殊类型所进行特殊化的实现方式。模板特化中分为函数模板特化与类模板特化
注意:必须要有主模板之后才能对模板进行特化
函数模板的特化
其实,函数模板特化的话,最好用重载那种方式替代
注意,特化后的函数形参表必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误(而且函数模板还不让偏特化)
eg:主模板的两个形参类型相同,那么特化后的也要一样
template<class T>
bool Less(T left, T right)
{
return left < right;
}
函数模板的特化
template<>
bool Less<int*>(int* left, int* right)
{
return *left < *right;
}
//注意写法:1.关键字template后面接一对空的尖括号<>
// 2.函数名后跟一对尖括号,尖括号中指定需要特化的类型
但是,一般都是用函数重载来替代函数模板的特化的
eg:bool Less(int* left, int* right)
{
return *left < *right;
}
引申:template<class T>
bool Less(T* left, T* right)
{
return *left < *right;
}这要搞的话,传入的参数就只能是指针(传入的是int*,那么T就是int这样)
类模板的特化
类模板的特化分为全特化和偏特化
全特化
概念:将模板参数列表中所有的参数都确定。
eg:template<class T1, class T2> class Data {}; template<> class Data<int,double> {};//全特化
偏特化
概念:部分特化或者对形参的类型做出进一步的限制(比如:
T*,T&
这样)eg:// 偏特化 : 特化部分参数 template<class T1> class Data<T1, double> {}; // 偏特化 : 可能是对某些类型的进一步限制 template<class T1, class T2> class Data<T1*, T2*> {};
模板分离编译
这里的分离是指的不在同一个文件里面
由于模板的实例化是在编译阶段,所以,如果把声明放在.h文件,定义放在另一个.cpp文件的话),是不会去实例化的,就会出错
解决方法1.将声明和定义放到一个文件 “xxx.hpp” 里面或者xxx.h里面
2.模板定义的位置显式实例化(不推荐)
第二种解决方法的举例: template<class T> void stack<T>::push(const T& x) { _con.push_back(x); } //注意:类模板里面的成员函数在外面定义时,类模板参数也要写到<>里去 在定义完模板之后再搞个 template class Stack<int>;
模板总结
优点:
1.模板复用了代码,节省资源,更快的迭代开发,STL因此而产生
2.增强了代码的灵活性
【缺陷】
1.模板会导致代码膨胀问题,也会导致编译时间变长
2.出现模板编译错误时,错误信息非常凌乱,不易定位错误