zmqNut

明知会散落, 仍不惧盛开

0%

C++模板特化和偏特化

taehoon-kang-16

概念

函数模版与类模版

C++中模板分为函数模板和类模板

  • 函数模板:是一种抽象函数定义,它代表一类同构函数。

  • 类模板:是一种更高层次的抽象的类定义。

所谓特化,就是将泛型搞得具体化一些,就是为已有的模板参数进行一些使其特殊化的指定,使得以前不受任何约束的模板参数,或受到特定的修饰或完全被指定了下来。

分类

针对特化的对象不同,分为

  • 函数模板的特化 – 当函数模板需要对某些类型进行特化处理,称为函数模板的特化。
  • 类模板的特化 – 当类模板内需要对某些类型进行特别处理时,使用类模板的特化。

特化整体上分为全特化偏特化

  • 全特化 – 模板中模板参数全被指定为确定的类型。 全特化也就是定义了一个全新的类型,全特化的类中的函数可以与模板类不一样

  • 偏特化 – 模板中的模板参数没有被全部确定,需要编译器在编译时进行确定。

我们只能偏特化类模板,而不能偏特化函数模板,因为函数偏特化的功能可以通过函数的重载完成。

函数模板的特化

编写单一模板,使之对任何可能的模板实参都是最适合的,都能实例化。但是在某些情况下,通用模板的定义对特定类型是不适合的。即使用模板时会遇到一些特殊的类型需要特殊处理,不能直接使用当前的模板函数,所以此时我们就需要对该类型特化出一个模板函数(就是写出一个模板函数专门给该类型使用)。

例1

当使用一个比较大小的模板函数时

1
2
3
4
5
template<typename T> 
T compare(T &t1, T &t2) {
cout <<"in template<typename T> ..." <<endl;
return (t1 > t2) ? t1 : t2;
}

但是该模板函数在对于字符串进行比较时就不能使用了,对于字符串我们不能直接比较,因此直接特化出一个专门供字符串使用的模板参数。

1
2
3
4
5
template<> 
const char* compare<const char*>(const char* &s1, const char* &s2) {
cout <<"in special template< >..." <<endl;
return (strcmp(s1,s2) > 0) ? s1:s2;
}

或者

1
2
3
4
5
template<> 
const char* compare(const char* &s1, const char* &s2) {
cout <<"in special template< >..." <<endl;
return (strcmp(s1,s2) > 0) ? s1:s2;
}

函数模版的特化,当函数调用发现有特化后的匹配函数时,会优先调用特化的函数,而不再通过函数模版来进行实例化。

注意:

  • 使用模板特例化时,必须要先有基础的模板函数。即原模板的声明必须在作用域中。

  • 使用特例模板函数时格式有要求:

  1. 特例化一个函数模板时,必须为原模板中的每个模板参数类型都提供实参。使用关键字template后跟一个空尖括号对<>,其指出我们将为原模板的所有模板参数提供实参;
  2. 函数名<特化类型>(特化类型 参数1, 特化类型 参数2 , …) 在函数名后跟<>其中写要特化的类型;
  3. 当我们定义一个特例化版本时,函数参数类型必须与一个先前声明的模板中对应的类型匹配,即特化的函数的函数名,参数列表要和原基础的模板函数想相同。

在实际使用中,为了实现简单,对于一些模板函数处理有问题的特殊类型,我们将其直接写出

1
2
3
4
5
6
//  这个其实本质是函数重载
int compare(const char* &t1, const char* &t2)
{
cout <<"in overload function..." <<endl;
return strcmp(t1, t2);
}

当有可以直接匹配的函数时,即使有特化出的函数,都优先使用直接匹配的函数

例2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <iostream>
#include<cstring>
using namespace std;

template<typename T>
T compare(T &t1, T &t2) {
cout <<"in template<typename T> ..." <<endl;
return (t1 > t2) ? t1 : t2;
}

template<>
const char* compare<const char*>(const char* &s1, const char* &s2) {
cout <<"in special template< >..." <<endl;
return (strcmp(s1,s2) > 0) ? s1:s2;
}

// template<>
// const char* compare(const char* &s1, const char* &s2) {
// cout <<"in special template< >..." <<endl;
// return (strcmp(s1,s2) > 0) ? s1:s2;
// }

// 这个其实本质是函数重载
int compare(const char* &left, const char* &right)
{
cout <<"in overload function..." <<endl;
return strcmp(left, right);
}


int main( ){
int a = 1, b = 2;
compare(a, b);

const char *left = "gatieme";
const char *right = "jeancheng";
compare(left, right);

return 0;
}
/*输出:
in template<typename T> ...
in overload function...
*/

面试题

写一个模板特化函数printType,当T是int输出”I am int”,其他则输出”I am other”。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
using namespace std;

template<typename T>
void printType(const T &a) {
cout<<"I am other"<<endl;
}

template<>
void printType(const int &a) {
cout<<"I am int"<<endl;
}
int main() {
int a = 1;
string b = "hello";
printType(a);
printType(b);
return 0;
}
/*输出:
I am int
I am other
*/

类模板特化

类模板支持全特例化和偏特化,区别在于模板参数是否全部显示指定,或者全部参数性质指定。

类模板全特化

类模板特化类似于函数模板的特化,即类模板参数在某种特定类型下的具体实现。考察如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <iostream>
using namespace std;

template<typename T>
class A {
T num;
public:
A(){
num=T(6.6);
}
void print(){
cout<<"A num:"<<num<<endl;
}
};

template<>
class A<const char*> {
const char* str; //str是一个指针,指向一个字符串常量,所指向的变量的内容是不能变的
public:
A() {
str = "A special definition";
// *str = "i am boring"; //错误
}
void print() {
cout<<str<<endl;
}
};

int main() {
A<int> a1; //显示模板实参的隐式实例化
a1.print();
A<const char*> a2; //使用特化的类模板
a2.print();
}
/*输出:
A num:6
A special definition
*/

类模板偏特化

模板偏特化(Template Partitial Specialization)是模板特化的一种特殊情况,指显示指定部分模板参数而非全部模板参数,或者指定模板参数的部分特性分而非全部特性,也称为模板部分特化。与模板偏特化相对的是模板全特化,指对所有模板参数进行特化。模板全特化与模板偏特化共同组成模板特化。

模板偏特化主要分为两种,一种是指对部分模板参数进行全特化,另一种是对模板参数特性进行特化,包括将模板参数特化为指针、引用或是另外一个模板类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#include <vector>
#include <iostream>
using namespace std;

// 类模板
template<typename T, class N>
class TestClass {
public:
static bool compare(T num1, N num2) {
cout <<"standard class template"<< endl;
return (num1 < num2) ? true : false;
}
};

// 对部分模板参数进行特化
template<class N>
class TestClass<int, N> {
public:
static bool compare(int num1, N num2) {
cout << "partitial specialization" << endl;
return (num1 < num2) ? true : false;
}
};

// 将模板参数特化为指针
template<typename T, class N>
class TestClass<T*, N*> {
public:
static bool compare(T* num1, N* num2) {
cout << "new partitial specialization" << endl;
return (*num1 < *num2) ? true : false;
}
};

// 将模板参数特化为另一个模板类
template<typename T, class N> class TestClass<vector<T>,vector<N>> {
public:
static bool compare(const vector<T>& vecLeft, const vector<N>& vecRight) {
cout << "to vector partitial specialization" << endl;
return (vecLeft.size() < vecRight.size()) ? true : false;
}
};

int main() {
// 调用非特化版本
cout << TestClass<char, char>::compare('0', '1') << endl;

// 调用部分模板参数特化版本
cout << TestClass<int, char>::compare(30, '1') << endl; //字符'1'的ASCII码是49

// 调用模板参数特化为指针版本
int a = 30;
char c = '1';
cout << TestClass<int*, char*>::compare(&a, &c) << endl;

// 调用模板参数特化为另一个模板类版本
vector<int> vecLeft{0};
vector<int> vecRight{1,2,3};
cout << TestClass<vector<int>, vector<int>>::compare(vecLeft,vecRight) << endl;
}
/*
输出:
standard class template
1
partitial specialization
1
new partitial specialization
1
to vector partitial specialization
1
*/

模板类调用优先级

对主版本模板类、全特化类、偏特化类的调用优先级从高到低进行排序是:

全特化类 > 偏特化类 > 主版本模板类

这样的优先级顺序对性能也是最好的。

---------- End~~ 撒花ฅ>ω<*ฅ花撒 ----------