【C++】模板进阶 —— 非类型模板参数 | 特化 | 模板的分离编译

简介: 【C++】模板进阶 —— 非类型模板参数 | 特化 | 模板的分离编译

一. 非类型模板参数


模板参数分类类型形参与非类型形参

类型形参即:出现在模板参数列表中,跟在class或者typename之类的参数类型名称。


比如我们要实现一个静态的数组


#define N 100;
template<class T>
class array
{
  //....
private:
  T _a[N];
};


但是,这样无法灵活控制栈的大小 —— 不是泛型化


int main()
{
  array<int> a1;   //只能存100
  array<double> a2;  //存1000不可以
  return 0;
}


🧐这就要引入非类型模板参数


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


template<class T, size_t N =10>
class array
{
  //....
private:
  T _a[N];
};
int main()
{
  array<int> a0;   //可以给缺省参数:从右向左缺,且连续
  array<int, 100> a1;   //100
  array<double, 1000> a2;  //1000
  return 0;
}


注意:


浮点数、类对象以及字符串是不允许作为非类型模板参数的(只支持整形)

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

🥑吐槽array——>不受欢迎


array1<int> a0;   //C++11
  int a1[10];       //C
  //真正的区别:越界的检测
  //函数调用[]:assert检测有没有越界
  a0[10];
  //指针的解引用 --- 抽查是否越界,只针对越界写,越界读不检查
  a1[10] =10;


二. 模板的特化


🌈函数模板的特化


🌊函数模板的特化步骤:


必须要先有一个基础的函数模板

关键字template后面接一对空的尖括号<>

函数名后跟一对尖括号,尖括号中指定需要特化的类型

函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误

struct Date
{
  Date(int year, int month, int day)
  :_year(year)
  ,_month(month)
  ,_day(day)
  {}
  bool operator>(const Date& d)const
  {
  return (_year > d._year) ||
    (_year == d._year && _month > d._month) ||
    (_year == d._year && _month == d._month && _day > d._day);
  }
private:
  int _year;
  int _month;
  int _day;
};
// 函数模板 -- 参数匹配
template<class T>
bool Greater(T left, T right) 
{
  return left > right;
}
//特化--针对某些类型进行特殊化处理
template<>
bool Greater<Date*>(Date* left, Date* right)
{
  return *left > *right;
}
int main()
{
  cout << Greater(1, 2) << endl; // 可以比较,结果正确
  Date d1(2022, 7, 7);
  Date d2(2022, 7, 8);
  cout << Greater(d1, d2) << endl; // 可以比较,结果正确
  Date* p1 = &d1;
  Date* p2 = &d2;
  cout << Greater(p1, p2) << endl; // 可以比较,结果错误
  return 0;
}


一般情况下如果函数模板遇到不能处理或者处理有误的类型,为了实现简单通常都是将该函数直接给出,有点鸡肋的😂


🌈类模板的特化


特化不能单独存在,有鸡才有蛋


🎨全特化

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


0a2653c851af460fa595bd959398a8f1.png


如果数据类型T 是Date* 默认是按照地址比较的,那这样直接比较大小的结果不是我想要的,我想要按对象比较


void test_priority_queue3()
  {
  //priority_queue<Date*> pq; //默认比较地址大小
  priority_queue<Date*, vector<Date*>, ljj::lessPDate> pq;
  pq.push(new Date(2022, 10, 11));
  pq.push(new Date(2022, 11, 11));
  pq.push(new Date(2022, 12, 19));
  pq.push(new Date(2022, 4, 10));
  //默认比较地址大小,若想比较日期大小,自己写仿函数
  while (!pq.empty())
  {
    cout << *pq.top() << endl;
    pq.pop();
  }
  }


上篇博客我们知道了,可以借助模板的第三个参数Compare的口子,自己写一个仿函数来实现,现在还可以提供一个新方法:针对Date*特化


template<class T>
  struct Greater
  {
  bool operator()(const T& x1, const T& x2) const
  {
    return x1 > x2;
  }
  };
  template<>
  struct Greater<Date*>
  {
  bool operator()(Date*& x1,  Date*& x2) const
  {
    return *x1 > *x2;
  }
  };


注意不用仿函数了,对Date*类型进行了特殊化处理


priority_queue<Date*> pq;


🎨偏特化


偏特化,是对模板参数进一步进行条件限制的

比如下面的模板类:


using namespace std;
template<class T1, class T2>
class Data
{
public:
  Data() { cout << "Data<T1, T2>" << endl; }
private:
  T1 _d1;
  T2 _d2;
};


🥑 部分特化

将模板参数类表中的一部分参数特化。第一个随意,第二个特化指定值


// 将第二个参数特化为int
template <class T1>
class Data<T1, int> {
public:
  Data() { cout << "Data<T1, int>" << endl; }
private:
  T1 _d1;
  int _d2;
};
int main()
{
  Data<int, int> d1; //模板
  // 偏特化
  Data<char, char> d3;
  Data<double, char> d4;
  return 0;
}


🔥进一步限制,只要你是指针,不管你什么类型


//两个参数偏特化为指针类型
template <typename T1, typename T2>
class Data <T1*, T2*>
{
public:
  Data() { cout << "Data<T1*, T2*>" << endl; }
private:
  T1 _d1;
  T2 _d2;
};
//两个参数偏特化为引用类型
template <typename T1, typename T2>
class Data <T1&, T2&>
{
public:
  Data(const T1& d1, const T2& d2)
  : _d1(d1)
  , _d2(d2)
  {
  cout << "Data<T1&, T2&>" << endl;
  }
private:
  const T1& _d1;
  const T2& _d2;
};
int main()
{
  Data<char*, char*> d5;
  Data<double*, char*> d6;
  Data<int&, int&> d7(1,2);
  return 0;
}


三. 模板的分离编译


复习地址传送门:编译链接

分离编译:一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式,此举可以帮助我们更好的维护项目,看.h了解框架设计功能,看.cpp了解实现细节


✅编译链接过程:


a.h  a.cpp  test.cpp


🔒预处理:宏替换、头文件展开、条件编译、去注释


a.i    test.i


🔒编译:C ➡️ 汇编


a.s    test.s


🔒汇编:汇编 ➡️ 可重定向二进制目标文件


a.o   test.o


🔒链接:将多个obj文件合并成一个,并处理没有解决的地址问题


普通函数分离编译没有问题,模板函数分离编译会出现链接不上错误,分析如下 ——


原因时:在链接之前,二者并不会交互,所以头文件只有模板,T无法确定所以没有实例化,insert等就没有进符号表,链接在符号表中找不到,自然就报错


0a2653c851af460fa595bd959398a8f1.png


➰解决方法


🔸1. 将声明和定义不要分离到.h和.cpp,放在同一个文件中(推荐)


那么,在编译阶段,test.i中,头文件展开后,直接就有模板的定义和实例化,可以直接填上调用地址,不需要链接时去找了


🔸2.显示实例化(不推荐)

缺点:用一个就得显示实例化一个,非常麻烦,泛型失去意义


#include"a.h"
void F1(int N)
{
  // 2.编译阶段:生成汇编代码
  cout << "void F1(int N)" << endl;
}
template<class T>
void F2(const T& N)
{
  // 2.编译阶段:不处理,没有实例化,无法生成汇编代码
  cout << "void F2(const T& N)" << endl;
}
// 显式实例化
template
void F2<int>(const int& N);


四. 总结


【优点】:


模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生

增强了代码的灵活性

【缺陷】:


模板会导致代码膨胀问题,也会导致编译时间变长

出现模板编译错误时,错误信息非常凌乱,不易定位错误


相关文章
|
1天前
|
C++
C++程序中的结构体类型
C++程序中的结构体类型
7 1
|
4天前
|
存储 安全 C语言
C++|多态性与虚函数(1)功能绑定|向上转换类型|虚函数
C++|多态性与虚函数(1)功能绑定|向上转换类型|虚函数
|
4天前
|
存储 编译器 C++
|
6天前
|
存储 算法 编译器
C++的模板与泛型编程探秘
C++的模板与泛型编程探秘
11 0
|
6天前
|
编译器 C++
【C++从练气到飞升】08---模板
【C++从练气到飞升】08---模板
|
1天前
|
C++
【C++基础】类class
【C++基础】类class
9 1
|
1天前
|
安全 程序员 编译器
C++程序中的基类与派生类转换
C++程序中的基类与派生类转换
8 1
|
1天前
|
C++
C++程序中的类成员函数
C++程序中的类成员函数
7 1
|
1天前
|
C++
C++程序中的类封装性与信息隐蔽
C++程序中的类封装性与信息隐蔽
8 1
|
1天前
|
C++
C++程序中的类声明与对象定义
C++程序中的类声明与对象定义
9 1