> 技术文档 > 【c++指南】模板VS手写代码:这场效率对决你站哪边?【下】

【c++指南】模板VS手写代码:这场效率对决你站哪边?【下】


🌟 各位看官好,我是egoist2023

🌍 种一棵树最好是十年前,其次是现在!

🚀 今天来学习非类型模板及特化的相关知识。

👍 如果觉得这篇文章有帮助,欢迎您一键三连,分享给更多人哦!

目录

非类型模版参数

特化

函数模版特化

类模版特化

全特化

偏特化

模版分离编译

解决方案

总结


非类型模版参数

在 模版【上】章节中模版参数当作类型来处理,实际上模版参数还有非类型模版参数

非类型形参,就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常 量来使用

#define N 10templateclass myvector{public://...private:T _arr[N];int _capacity;};int main(){myvector v1;myvector v2;return 0;}

在上面这段程序当中,N的大小是确定的,即如果要插入100个数据时,N是10是不够的,需要手动将N的大小进行修改;当要插入10个数据时,N是100又太多了,导致很多空间被浪费。因此槽点很多,如果使用非类型模版参数可以改善这种问题。

templateclass myvector{public://...private:T _arr[N];int _capacity;};

此时N的大小我们可以自行决定,此时一个double类型的vector要开100个空间

myvector v;

int类型的vector要开10个空间

myvector v;

 实际上,在C++库中的array(数组)也是这样处理的,那它又和int array[N]有什么区别呢?

C语言中的int array对越界问题检查实际是不严格的。

C++中array对越界问题的检查。

需要注意的是

  • 浮点数、类对象以及字符串是不允许作为非类型模板参数的

  • 非类型的模板参数必须在编译期就能确认结果 

特化

通常情况下, 使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些 错误的结果 ,需要特殊处理。

templatebool Less(T left, T right){return left < right;}int main(){cout << Less(1, 2) << endl;Date d1(2025, 3, 1);Date d2(2025, 3, 2);cout << Less(d1, d2) << endl;return 0;}

结合之前Date类实现,如上一段程序中,可以对日期类进行正确比较。但是也有一些情景是需要特殊处理的。

 在上面这段程序当中,明显d3>d4的,运行结果为1才对。但运行几次后发现有两个结果,为什么会出现0这个现象呢?

此时我们进行调试观察d3和d4。

可以发现d4的内存地址是大于d3的内存地址,这也解决了我们的疑惑:这里的比较是指针地址的比较,因此需要引入特化或提供函数支持Date比较 。

函数模版特化

函数模板的特化步骤: 1. 必须要先有一个基础的函数模板 2. 关键字template后面接一对空的尖括号 3. 函数名后跟一对,中指定需要特化的类型 4. 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇 怪的错误。 使用函数模版特化解决Date*按指针比较的方式。

//函数模版特化templatebool Less(Date* left, Date* right){return *left < *right;}

 普通函数同样可以解决问题,且这种方式简单明了,难道不是更香吗?

bool Less(Date* left, Date* right){return *left < *right;}

我们再来看看传右值的情况(右值指的是匿名对象、常量等)

templatebool Less(const T& left, const T& right) //传右值-->左值引用右值要加const{return left < right;}//特化templatebool Less(Date* const & left, Date* const & right) //const修饰本身{return *left < *right;}int main(){cout << Less(new Date(2025, 3, 1), new Date(2025, 2, 28)) << endl;return 0;}

 再针对右值的情况下,左值引用要引用右值就必须+const修饰,那在特化的时候,也需要进行对应修改。可以看到,模版函数特化在上面这段程序中显得特别坨,再来看看普通函数的实现。

bool Less(Date* const & left, Date* const & right) //const修饰本身{return *left < *right;}

因此,为了提高代码的可读性,容易书写,因为对于一些参数类型复杂的函数模板,特化时特别给出,因此函数模板不建议特化。这样看特化好像无用武之地,别着急我们继续往下看。

类模版特化

全特化

全特化即是将模板参数列表中所有的参数都确定化。

templateclass Date{public:Date(){cout << \"Date\" << endl;}};templateclass Date{public:Date(){cout << \"Date\" << endl;}};templateclass Date{public:Date() {cout << \"Date\" << endl;}};

偏特化

偏特化:任何针对模版参数进一步进行条件限制设计的特化版本。 部分模版参数特化

templateclass Date{public:Date(){cout << \"Date\" << endl;}};

参数更进一步的限制

templateclass Date{public:Date(){cout << \"Date\" << endl;}};templateclass Date{public:Date(){cout << \"Date\" << endl;}};

类模版特化的场景

针对日期进行排序 

// 对Less类模板按照指针方式特化templatestruct Less{ bool operator()(Date* x, Date* y) const { return *x < *y; }};

模版分离编译

为什么在 模版【上】章节说到不建议模版声明与定义分离呢?这里做下解释。

分离编译:一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程。

我们知道C/C++程序运行一般要经历以下步骤:

  1. 预处理 --> 展开头文件、宏替换、条件编译、去掉注释等;
  2. 编译 --> 检查语法、生成汇编代码;
  3. 汇编 --> 将汇编代码转成二进制机器码;
  4. 链接 --> 合成可执行程序,链接函数地址等;

运行时链接出现了报错,我们来具体分析下原因在哪。 

解决方案

  • 显示实例化(但是不建议如果有多个类型)
func2(1);func2(200.1);
  • 不做声明和定义分离(避开找地址,在声明处直接有地址了)

总结

优点远大于缺点

【优点】 1. 模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生 2. 增强了代码的灵活性 【缺陷】 1. 模板会导致代码膨胀问题,也会导致编译时间变长 2. 出现模板编译错误时,错误信息非常凌乱,不易定位错误