C++|【C++】模板进阶


文章目录

  • 前言
  • 一、非类型模板参数
    • 1.引出
    • 2.概念
    • 3.注意
      • (1)非类型模板参数必须是整型
      • (2)非类型模板参数是常量
      • (3)缺省模板参数
  • 二、模板的特化
    • 1.概念
    • 2.函数模板的特化
    • 3.类模板的特化
    • 4.偏特化
  • 三、模板分离编译
    • 1.什么是分离编译
    • 2.模板的分离编译
  • 四、模板的优缺点
    • 1.优点
    • 2.缺点
  • 感谢阅读,如有错误请批评指正

前言 这篇文章是【C++】模板初阶 的进阶部分。
一、非类型模板参数 1.引出 现在需要一个定长100的数组类,很容易设计出如下的类。
#define N 100template class Array { public: //... private: T _a[N]; };

如上设计是没有问题的,但如果还需要定长10、1000的数组呢?定长为10的数组虽然浪费内存,但勉强还可以用上面的类;定长为1000的数组就需要再重新一个逻辑完全同上、只是N的值不同的类。这显然代码很冗余。
对于上面的问题,用非类型模板参数可很好的解决。
2.概念 模板参数分为:类型模板参数、非类型模板参数。
  • 类型模板参数:出现在模板参数列表中,跟在class或typename之后的参数类型名称。
  • 非类型模板参数:用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。非类型模板参数只能是整型。
下面是非类型模板参数的使用示例:
//N是非类型模板参数 template class Array { public: //... private: T _a[N]; }; int main() { Array a10; //定长10的数组 Array a100; //定长100的数组 Array a1000; //定长1000的数组 return 0; }

3.注意 (1)非类型模板参数必须是整型
下面以double为例,传入其他类型也会报错。
C++|【C++】模板进阶
文章图片

(2)非类型模板参数是常量
如下代码通过Modify函数修改N的值,但由于N是常量,所以会报错。
template class Array { public: void Modify() { N = 50; } private: T _a[N]; }; int main() { Array a10; Array a100; Array a1000; a10.Modify(); return 0; }

编译结果如下:
C++|【C++】模板进阶
文章图片

(3)缺省模板参数
实际上,不管是类型模板参数还是非类型模板参数,都是可以给缺省值的。
代码如下(示例):
//默认是类型为int,大小为10的数组 template class Array { public: //... private: T _a[N]; }; int main() { Array<> a1; //用缺省值 Array a2; //类型为double,大小为10的数组 Array a3; //类型为int,大小为1000的数组 return 0; }

二、模板的特化 1.概念 一些情况下,函数模板或类模板并不能正确处理所需的逻辑,这时就需要对一些情况进行特殊的处理,这就是模板的特化。
2.函数模板的特化 以下面的比较相等函数为例。
代码如下(示例):
template bool IsEqual(const T& left, const T& right) { return left == right; }int main() { cout << IsEqual(1, 1) << endl; cout << IsEqual(1.5, 1.5) << endl; const char p1[] = "hello"; const char p2[] = "hello"; cout << IsEqual(p1, p2) << endl; return 0; }

如果传入两个数字,逻辑没有问题,但如果传入两个字符串,如果不加以修改就会默认是比较两个指针变量p1和p2的地址,这时字符串相同,但由于指针地址不同,就会输出不符合所需逻辑的判断结果。
C++|【C++】模板进阶
文章图片

这时用将函数模板特化即可解决这一问题。
代码如下(示例):
template bool IsEqual(const T& left, const T& right) { return left == right; }//指明参数的类型,当匹配时会调用这个函数而不是模板,可以看做是一种重载 bool IsEqual(const char* left, const char* right) { return strcmp(left, right) == 0; }int main() { cout << IsEqual(1, 1) << endl; cout << IsEqual(1.5, 1.5) << endl; const char p1[] = "hello"; const char p2[] = "hello"; cout << IsEqual(p1, p2) << endl; return 0; }

运行结果如下,两个相同的字符串判等时结果为真,符合需要的逻辑。
C++|【C++】模板进阶
文章图片

3.类模板的特化 代码如下(示例):
//除了int,int都用普通模板实例化 template class A { public: A() { cout << "A" << endl; } private: T1 _a1; T2 _a2; }; //将int,int特化 template<> class A { public: A() { cout << "A" << endl; } private: int _a1; int _a2; }; int main() { A a1; A a2; return 0; }

上述代码运行结果如下:
C++|【C++】模板进阶
文章图片

4.偏特化 前面讲到的都全特化,实际上特化的模板参数也可以有部分缺省。下面以类模板为例进行说明,函数模板同理。
//最普通的函数模板 template class A { public: A() { cout << "A" << endl; } private: T1 _a1; T2 _a2; }; //全特化的函数模板 template<> class A { public: A() { cout << "A" << endl; } private: int _a1; int _a2; }; //偏特化的函数模板 template class A { public: A() { cout << "A" << endl; } private: int _a1; int _a2; }; int main() { A a1; A a2; A a1; return 0; }

注意,编译器匹配时总是找最接近的,完全相同是最好的。a1的两个参数符合普通的函数模板,但更符合偏特化的函数模板,所以打印的是A。同理可得出其他两个的结果。
C++|【C++】模板进阶
文章图片

三、模板分离编译 1.什么是分离编译 一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来,再形成单一的可执行文件的过程称为分离编译模式。
2.模板的分离编译 图中Swap函数模板的声明在a.h中,实现在a.cpp中,调用在test.cpp中。
C++|【C++】模板进阶
文章图片

在a.cpp中,只有函数模板的定义,在编译时无法确定T是什么类型,无法实例化;在test.cpp中,只有函数模板的调用,虽然知道所需的参数类型,但是没有函数的实现,同样无法实例化。
这导致在链接前两个cpp文件中的函数模板都无法实例化,于是在链接时没有生成函数的具体代码,链接报错。
【C++|【C++】模板进阶】这种问题推荐的解决办法是:将模板的声明和定义放到同一个.h或.hpp文件中。
四、模板的优缺点 1.优点
  • 模板复用了代码,可以节省资源,更快的迭代开发。最重要的是,C++的标准模板库(STL)因此而产生
  • 增强了代码的灵活性
2.缺点
  • 模板会导致代码膨胀问题(每有一个新的实例化对象,模板的代码就会整个多出来一份),也会导致编译时间变长。
  • 出现模板编译错误时,错误信息非常凌乱,不易定位错误。
  • 模板不支持分离编译。
感谢阅读,如有错误请批评指正

    推荐阅读