【C++】非类型模板参数、模板特化、模板的分离编译、模板总结

举报
平凡的人1 发表于 2023/01/19 20:26:25 2023/01/19
【摘要】 @[toc] 一、非类型模板参数模板参数分类类型形参与非类型形参。类型形参:出现在模板参数列表中,跟在class或者typename之类的参数类型名称。#define N 10;//静态数组template <class T>class Array{private: T _a[N];}如果是这样的话,我们无法去灵活控制大小int main(){ Array<int> a1; ...

@[toc]

一、非类型模板参数

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

#define N 10;
//静态数组
template <class T>
class Array
{
private:
    T _a[N];
}

如果是这样的话,我们无法去灵活控制大小

int main()
{
    Array<int> a1;
    Array<int> a2;
    return 0;
}

这都是固定的了,写死的了,所以这时候我们可以使用非类型模板参数

非类型形参:就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。注意是常量,可以给缺省

//非类型模板参数——常量
template<class T,size_t N>
class Array
{
private:
    T _a[N];
}
int main()
{
    Array<int,10> a1;
    Array<double,100>a2;
    return 0;
}

注意:

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

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

实际上库里面的array也是非类型模板:

image-20230110115311577

库里面的array与C语言的数组相比:

int main()
{
    int a1[10];
    array<int,10> a2;
    array<double,10> a3;
    return 0;
}

区别在于:array可以对越界进行检查,C语言数组对于越界读是不检查的,而对于越界写是抽查的(不同平台不一样)。而array可以assert检查是否越界。


二、模板特化

1.函数模板特化

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

我们来以日期类为例子:

class Date
{
public:
	Date(int year = 1900,int month = 1,int day = 1)
		:_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);
	}

	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);
	}

	friend ostream& operator<<(ostream& _cout, const Date& d)
	{
		_cout << d._year << "-" << d._month << "-" << d._day;
		return _cout;
	}

private:
	int _year;
	int _month;
	int _day;
};
template<class T>
bool Less(T left, T right)
{
	return left < right;
}

int main()
{
	cout << Less(1, 2) << endl;
	Date d1(2023, 1, 6);
	Date d2(2023, 1, 9);
	cout << Less(d1, d2) << endl;

	Date* p1 = &d1;
	Date* p2 = &d2;
	cout << Less(p1, p2) << endl;
	return 0;
}

image-20230110135553319

所以我们要去对Date*进行特殊化处理——Date*

函数模板的特化步骤:

  1. 必须要先有一个基础的函数模板
  2. 关键字template后面接一对空的尖括号<>
  3. 函数名后跟一对尖括号,尖括号中指定需要特化的类型
  4. 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误
//针对某些类型进行特殊处理——Date*
template<>
bool Less<Date*>(Date* left, Date* right)
{
	return *left < *right;
}

函数模板也可以不写成模板,直接写成函数也是可以的,因为函数模板支持重载

2.类模板特化

1.全特化

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

类模板的全特化将模板参数列表中的所有参数我们都将其写出来:

image-20230110142030654

如果此时的数据类型是我们自己定义的,比如我们之前所说的Date*之时,比较的是地址,所以我们之前是通过自己写一个仿函数来实现比较大小的,代码如下:

struct PDateLess
{
	bool operator()(const Date* d1, const Date* d2)
	{
		return *d1 < *d2;
	}
};

struct PDateGreater
{
	bool operator()(const Date* d1, const Date* d2)
	{
		return *d1>*d2;
	}
};
void TestPriorityQueue()
{
    //大堆
	priority_queue <Date*, vector<Date*>, PDateLess> q3;
	q3.push(new Date(2018, 10, 29));
	q3.push(new Date(2018, 10, 28));
	q3.push(new Date(2018, 10, 30));
	cout << *q3.top() << endl;
	//小堆
	priority_queue<Date*, vector<Date*>, PDateGreater> q4;
	q4.push(new Date(2018, 10, 29));
	q4.push(new Date(2018, 10, 28));
	q4.push(new Date(2018, 10, 30));
	cout << *q4.top() << endl;
}

现在,我们如果不写仿函数,这时候就可以通过针对Date*实现特化了:

template <class T>
class Greater
{
public:
    bool operator()(const T& x, const T& y) const
    {
        return x > y;
    }
};
//特化
template<>
class Greater<Date*>
{
    public:
    bool operator()(Date* const& d1, Date* const& d2) const
    {
        return *d1 > *d2;
    }
};
int  main()
{
    hwc::priority_queue<Date*, vector<Date*>, Greater<Date*>> q4;
	q4.push(new Date(2018, 10, 29));
	q4.push(new Date(2018, 10, 28));
	q4.push(new Date(2018, 10, 30));
	cout << *q4.top() << endl;
    return 0;
}

2.偏特化

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

template<class T1,class T2>
class Data
{
public:
	Data()
	{
		cout << "Data<T1,T2>" << endl;
	}
private:
	T1 _d1;
	T2 _d2;
};
//半特化、偏特化
template<class T1>
class Data<T1, char>
{
public:
	Data()
	{
		cout << "Data<T1,char>" << endl;
	}
};
int main()
{
    //模板
    Data<int,int>d;
    //偏特化
    Data<double,char> d1;
    Data<char,char> d2;
    return 0;
}

偏特化可以对参数进一步的限制:只要是指针,不管是什么类型的指针,针对指针,也可以针对引用:

//参数类型进一步限制
template<class T1,class T2>
class Data<T1*, T2*>
{
public:
	Data()
	{
		cout << "Data<T1*,T2*>" << endl;
	}
};

template<class T1,class T2>
class Data<T1&, T2&>
{
public:
	Data()
	{
		cout << "Data<T1&,T2&>" << endl;
	}
};

int main()
{
    
    return 0;
}
int main()
{
    //指针
	Data<char*, char*> d5;
	Data<double*, int*> d6;
    
    //引用
    Data<double&,int&> d7;
	return 0;
}

特化的本质体现的是编译器的参数匹配原则


三、模板的分离编译

模板的分离编译我们之前就有说过,这里重新说一遍:

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

而对于模板,链接之前并不会交互,分离编译就会导致用的地方.cpp没有实例化,没有实例化就会导致链接不上。

比如:a.h,a.cpp,test.cpp这三个文件,

编译链接过程:预处理——>编译——>汇编——>链接

预处理:去注释,宏替换,头文件展开,条件编译(a.i,test.i)

编译:生成汇编代码(a.s,test.s)、符号汇总

汇编:把汇编变成二进制目标文件(a.o,test.o),形成符号表

链接:符号表的合并与重定位,将多个obj文件合并成一个,形成可执行程序

image-20230110191314742

解决方案:

  1. 将声明和定义放到一个文件 “xxx.hpp” 里面或者xxx.h其实也是可以的。推荐使用这种。此时在编译阶段中,就有了模板的实例化。
  2. 模板定义的位置显式实例化。这种方法不实用,不推荐使用 。如果实例化的类型少那还是可行的,如果要针对的类型很多,那就太麻烦了
  3. image-20230110193321200

四、模板总结

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

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。