C++模板总结, 外加一些模板的特殊用法(语法), 比如非类型模板参数, 模板的特化全特化, 以及真正理解为何模板不可以分离编译

简介: C++模板总结, 外加一些模板的特殊用法(语法), 比如非类型模板参数, 模板的特化全特化, 以及真正理解为何模板不可以分离编译

一.模板的非类型参数

  • 用了那么久的模板,一般模板中都是类型参数, 但是想不到吧, 模板还存在非类型参数.也就是模板中的参数不一定是一个类型, 还可以是一个整形, 也只能传入一个整形数据. 整形常量....
  • 首先让我们来思考如下的一段代码      ( 抛出问题
#define N 100
template <class T>
class Array {
public:
  int getSize() {
    return N;
  }
private:
  T a[N];
};
int main() {
  //上述这样我们我们可以封装得到一个 可以管理 静态的 a[N]的数组的一个Array类....
  //但是这样我们要是需要其他容量的一个静态数组的类,不就每一次需要不停的重新定义N?????
  //这样使用起来会非常麻烦  
  Array<int> a1;
  a1.getSize();
    return 0;
}
  • 如何解决上述问题???   看STL库中的array源码如何解决问题的.....  
  • 上述这个  array类中就使用了非类型模板参数 size_t N..  然后如下代码可以解释好处:  
template <class T, size_t N>
class Array {
public:
  int getSize() {
    return N;
  }
private:
  T a[N];
};
int main() {
  Array<int, 10> a1;
  cout << a1.getSize() << endl;
  Array<int, 100> a2;
  cout << a2.getSize() << endl;
    return 0;
}

此时上述的  N  其实相当于是一个整形常量, 这样我们就不需要定义一个全局的宏了, 方便了指定静态数组的长度.

二.模板类和模板函数的特化   (全特化  +  偏特化)

模板特化官方解释 :     在泛化设计中提供一个特化版本 (也就是将泛化版本中的某些或者全部参数赋予明确的指定)  全部明确指定就是全特化, 部分明确指定就是偏特化....


我的理解  :模板的特化 就是为了在特殊的场景下 直接正常的让编译器进行模板的实例化之后无法获取我们想要达到的效果, 这个时候我们针对特殊的场景给与针对这个场景的一根特化实例版本. 来处理这个情况...


特殊场景的特化版本处理情景模拟如下, 比如我们在一个模板函数中想要实现字符串是否相同的比较工作, 直接写代码以及结果测试如下  (不提供特化版本)....

template<class T>
bool IsEqual(const T& a, const T& b) {
  return a == b;
}
int main() {
  cout << IsEqual(1, 1) << endl;
  cout << IsEqual(1.1, 1.1) << endl;
  char s1[] = "1234";
  char s2[] = "1234";
  cout << IsEqual(s1, s2) << endl;
  char *s3 = "1234";
  char *s4 = "1234";
  cout << IsEqual(s3, s4) << endl;
  while (1);
  return 0;
}

上述代码的结果需要读者您测试一下, 结果也很简单, 无法达到要求, 模板针对字符串的比较相等的功能没有办法达成,怎么办, 提供一份特化版本实例来支持达到我们想要的结果

提供特化版本结果如下  

template<class T>
bool IsEqual(const T& a, const T& b) {
  return a == b;
}
//针对字符串类型进行特殊处理 -- 写一份特例出来
bool IsEqual(const char* s1, const char* s2) {
  return strcmp(s1, s2) == 0;
}
int main() {
  cout << IsEqual(1, 1) << endl;
  cout << IsEqual(1.1, 1.1) << endl;
  char s1[] = "1234";
  char s2[] = "1234";
  cout << IsEqual(s1, s2) << endl;
  char *s3 = "1234";
  char *s4 = "1234";
  cout << IsEqual(s3, s4) << endl;
  while (1);
  return 0;
}

上述是函数模板的特化, 接着是类模板的特化....  首先是语法形式.  形如一下代码片段:

template <>               //<>不一定是空, 空代表全特化, 否则偏特化          
class testClass <type, type, ...> { // <type, type, ...> 就是具体的特化类型....
};

然后是全特化(specialization)实际案例:   (没有粘贴案例结果, 希望读者可以自行验证, 调试分析进入的哪个类)

//测试类模板
template <class T1, class T2> 
class TestClass {
public:
  TestClass() {
    cout << "T1, T2" << endl;
  }
};
//模板特化案例
template<>
class TestClass<int, int> {         //特化一个int,int 版本调用这个
public:
  TestClass() {
    cout << "int, int" << endl;
  }
};
//hash类模板
template <class Key>             
struct hash{};
//模板的特化案例
template<>
struct hash<char> {
  size_t operator ()(char x) const { return x; }
};
template<>
struct hash<int> {
  size_t operator ()(int x) const { return x; }
};
template<>
struct hash<long> {
  size_t operator ()(long x) const { return x; }
};
int main() {
  cout << hash<int>()(1000) << endl;
  cout << hash<int>()('a') << endl;//可通过 f9断点 + f10 f11测试进入类
  TestClass<int, int> obj1;
  TestClass<int, double> obj2;
}
  • partial specialization, 模板的偏特化 -- 第一种 ( 数量的偏 )
  • 如下 还是数量上的偏, 读者可自行测试
template <class T1, class T2> 
class TestClass {
public:
  TestClass() {
    cout << "T1, T2" << endl;
  }
};
template<class T1>
class TestClass<T1, int> {//只要T2是int走这个
public:
  TestClass() {
    cout << "T1, int" << endl;
  }
};
template<class T1>
class TestClass<T1, char> {//只要T2是char走这个
public:
  TestClass() {
    cout << "T1, char" << endl;
  }
};
//上述是仅仅针对T2做的特化处理
int main() {
  TestClass<char, char> t1;
  TestClass<char, int> t2;
  TestClass<char, double> t3;
  return 0;
}

partial speciation, 模板偏特化 -- 范围的偏特化   (还是希望读者自行测试分析一下)

//测试类模板
template <class T1, class T2> 
class TestClass {
public:
  TestClass() {
    cout << "T1, T2" << endl;
  }
};
//如下是范围上的偏特化, 数量上是全部, 但是范围进针对指针或者引用类型
template<class T1, class T2>
class TestClass<T1*, T2*> {
public:
  TestClass() {
    cout << "T1*, T2*" << endl;
  }
};
template<class T1, class T2>
class TestClass<T1&, T2&> {
public:
  TestClass() {
    cout << "T1&, T2&" << endl;
  }
};
int main() {
  TestClass<int*, char*> t4;
  TestClass<int&, char&> t5;
  return 0;
}

三. 深刻刨析模板为何不支持分离编译

  • 既是分析为何模板不支持分离编译, 那我们就尝试分离编译, 看看究竟报的什么错误.
  • 第一段   Vector.h代码:
#pragma once
#include <iostream>
using namespace std;
//只是放置申明....
//放置类申明
template <class T> 
class Vector {
public:
  Vector();
};
//放置函数申明
template <class T>
T add(const T& left, const T& right);

第二段是 Vector.cpp的代码

#include "Vector.h"
//实现....
template <class T>
Vector<T>::Vector() {
  cout << "构造" << endl;
}
//放置函数申明
template <class T>
T add(const T& left, const T& right) {
  return left + right;
}

第三段是 test.cpp的代码

#include "Vector.h"
int main() {
  return 0;
}
  • 发现就是没有进行对象构造或者调用任何函数的时候编译时没有报错, 是过了的, 说明没有语法层面上的错误....

 如上发现, 分离编译的时候在编译阶段是没有产生报错现象的.

  • 现在进行产生对象 或者调用模板函数实例化看看效果???
  • 单独测试一下 猜想...   两者均出现一致的报错现象,无法解析的外部指令, 说明什么, 分离编译错误造成的原因是不是没有函数定义实现, 只有函数声明。。。。  

  • 为何 出现了只有函数申明没有函数实现, 这种错误只有链接的时候才能够发现, 特别是在分离编译的时候  
  • 如下画出分离编译的过程....

上述仅仅只是猜测,   但是 其实  应该是对的,  验证  是否是 在 Vector.cpp中缺少了模板的实例化, 解决办法1. 也算是验证猜想, 我们手动的在 Vector.cpp中添加  模板的显式实例化.

上述做法可以解决模板函数的分离编译, 但是相当麻烦,  而且对于类显示实例化我并不清楚应该如何处理, 有知道的大佬可以帮助评论区告知,

  • 最合适的针对模板类和模板函数的处理, 不要进行分离编译, 申明定义都放在一个.hpp文件中完成

四. 针对模板 STL泛型编程--STL库的总结

优点  :


复用代码, 原理上脏活累活, 都交给编译器去做

模板让我们一些代码可以很灵活


缺点  :


模板看起来很节省代码, 但是在实际的编译后存在代码膨胀的问题

vector v1; vector v2, vector v3.... 当然为了尽可能减少代码膨胀, 编译器会按需实例化, 调用的成员函数

模板出现语法错误以后, 有时报错混乱且不准确, 很不容易定位错误位置, 一般只看第一个错误, 后面的大多不准确

模板不支持分离编译

五. 文章总结    

首先本文的四个点:


1.非类型模板参数    : 库中的 array    template 模板参数可以整形常量  


2. 模板特化             :  特化原因 (针对特殊场景下, 常规的模板实例化无法得到我们想要的结果, 这个时候给与特化实例),         特化类型(全特化,  偏特化: 数量上, 范围上)


3. 从本质分析模板为何不支持分离编译   :  从 分离 编译过程入手, 只有在链接时候, 两个.o文件才产生交互, 之前编译阶段, 没有交互, Vector.cpp 中的模板没法实例化...


4. 模板的优缺点总结    优点,便捷. 代码复用, 编译器帮助实例, 缺点 : 从代码膨胀入手


如上简单总结, 如果您可以回忆起来, 或者脑中清晰可以回答上述问题, 就很ok了喔, 如果哪点很懵逼, 可以回溯一下, 您可能刚刚分神了


最终, 本文存在针对侯杰老师部分课件的借鉴, 以及我自己的笔记和感悟, 仅作为个人笔记为了学习向大家分享共同进步...  如果有觉着本文不足之处, 欢迎大家评论区留言, 祝共同学习进步



相关文章
|
1月前
|
存储 算法 C++
C++ STL 初探:打开标准模板库的大门
C++ STL 初探:打开标准模板库的大门
91 10
|
22天前
|
编译器 程序员 C++
【C++打怪之路Lv7】-- 模板初阶
【C++打怪之路Lv7】-- 模板初阶
13 1
|
1月前
|
编译器 C语言 C++
C++入门6——模板(泛型编程、函数模板、类模板)
C++入门6——模板(泛型编程、函数模板、类模板)
38 0
C++入门6——模板(泛型编程、函数模板、类模板)
|
23天前
|
存储 编译器 对象存储
【C++打怪之路Lv5】-- 类和对象(下)
【C++打怪之路Lv5】-- 类和对象(下)
21 4
|
23天前
|
编译器 C语言 C++
【C++打怪之路Lv4】-- 类和对象(中)
【C++打怪之路Lv4】-- 类和对象(中)
20 4
|
22天前
|
存储 安全 C++
【C++打怪之路Lv8】-- string类
【C++打怪之路Lv8】-- string类
17 1
|
1月前
|
存储 编译器 C++
【C++类和对象(下)】——我与C++的不解之缘(五)
【C++类和对象(下)】——我与C++的不解之缘(五)
|
1月前
|
编译器 C++
【C++类和对象(中)】—— 我与C++的不解之缘(四)
【C++类和对象(中)】—— 我与C++的不解之缘(四)
|
1月前
|
编译器 C语言 C++
C++入门3——类与对象2-2(类的6个默认成员函数)
C++入门3——类与对象2-2(类的6个默认成员函数)
23 3
|
1月前
|
C++
C++番外篇——对于继承中子类与父类对象同时定义其析构顺序的探究
C++番外篇——对于继承中子类与父类对象同时定义其析构顺序的探究
51 1