在前面的博客中,详细介绍了模板的概念以及使用。模板分为函数模板和类模板,是代码复用的一种手段,是泛型编程的基础。
在这篇博客中,会介绍模板进阶的一些操作。
非类型模板参数
前面的模板介绍中,只介绍了类型形参,是跟在class或者typename后的参数类型名称。
模板还支持另一类非类型形参,就是用用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。
template <class T, size_t N = 10>
class array
{
public:
T& operator[](size_t index)
{
return _array[index];
}
size_t size() const
{
return _size;
}
bool empty() const
{
return _size == 0;
}
private:
T _array[N]; //使用非类型形参作为数组长度
size_t _size;
};
上面代码是一个静态数组类,在模板参数的部分,有一个数组类型的类型形参T,同时还有一个用于表示数组长度的非类型形参N。非类型形参可以用于这种场景。
注意:
1、浮点数、类对象以及字符串是不允许作为非类型模板参数的。
2、非类型的模板参数必须在编译期就能确认结果。
模板的特化
函数模板特化
使用模板可以写出与类型无关的代码,但是对于一些特殊的情况可能无法得到正确的或者想要的结果。
比如下面的代码:
template <class T>
bool IsEqual(T left, T right)
{
return num1 == num2;
}
int main()
{
cout << IsEqual(1, 2) << endl;
char p1[] = "hello world";
char p2[] = "hello world";
cout << IsEqual(p1, p2) << endl;
return 0;
}
给出了一个判断left和right是否相等的模板函数,分别判断1和2、p1和p2是否相等,得到如下结果:
可以看出第一个的结果是对的,1和2确实不相等;但是p1和p2表示的都是字符串"hello world",得到的结果却是不相等,这个结果是不正确的。究其根本,是因为在利用函数模板判断两个字符串时,内部比较的是这两个字符串的地址,而不是其内容,所以才会给出一个错误的答案。
这个例子告诉我们,模板虽然可以方便我们实现代码复用,但是对于一些特殊的类型会得到错误的结果,因此对于这类情况,需要使用模板的特化来解决。
template<class T>
bool IsEqual(const T& left, const T& right)
{
return left == right;
}
//函数模板的特化
bool IsEqual(const char* left, const char* right)
{
return strcmp(left, right) == 0;
}
int main()
{
cout << IsEqual(1, 2) << endl;
char p1[] = "hello world";
char p2[] = "hello world";
cout << IsEqual(p1, p2) << endl;;
return 0;
}
得到的结果如下:
此时可以看出,字符串常量类型调用IsEqual函数模板时,针对于字符串类型就会去调用特化的模板,从而得到正确的结果。
类模板特化
类模板特化和函数模板特化的概念是相似的,就是针对某些特殊类型,为了能够得到正确的结果或者正确实现某种功能,对模板进行特殊化。
全特化
全特化指的是将模板参数列表中的所有参数都确定化,也就是特化的模板中所有的参数类型都确定了。
template<class T1, class T2>
class A
{
public:
A() { cout << "A<T1, T2>" << endl; }
private:
T1 _d1;
T2 _d2;
};
template<>
class A<int, char>
{
public:
A() { cout << "B<int, char>" << endl; }
private:
int _d1;
char _d2;
};
void TestVector()
{
A<int, int> d1;
A<int, char> d2;
}
上面的代码中,A类的通用模板没有具体类型,可以具体指定模板参数的类型进行类模板的特化。
类模板的特化步骤如下:
1、有一个基础的类模板;
2、关键字template后面接一对<>;
3、类名后跟一对<>,括号内指定需要特化的参数类型;
从运行结果可以看出,非特化的对象调用通用类模板;特化的对象调用特化类模板。
偏特化
偏特化指的是任何针对模版参数进一步进行条件限制设计的特化版本。
随手写一个类进行说明
template<class T1, class T2>
class B
{
public:
B() {cout<<"B<T1, T2>" <<endl;}
private:
T1 _d1;
T2 _d2;
};
部分特化
部分特化指的是将模板参数的一部分参数特化
// 将第二个参数特化为int
template <class T1>
class B<T1, int>
{
public:
Data() {cout<<"B<T1, int>" <<endl;}
private:
T1 _d1;
int _d2;
};
定义这样两个对象: B<double, double> b1; B<double, int> b2;
运行结果如下:
部分参数特化也就是,只要创建的对象的参数类型满足部分特化后的模板参数类型即可调用特化后的模板。
参数更进一步的限制
偏特化并不仅仅是指特化部分参数,而是针对模板参数更进一步的条件限制所设计出来的一个特化版本。
//两个参数偏特化为指针类型
template <typename T1, typename T2>
class B<T1*, T2*>
{
public:
B() {cout<<"B<T1*, T2*>" <<endl;}
private:
T1 _d1;
T2 _d2;
};
//两个参数偏特化为引用类型
template <typename T1, typename T2>
class B<T1&, T2&>
{
public:
B(const T1& d1, const T2& d2)
: _d1(d1)
, _d2(d2)
{
cout<<"B<T1&, T2&>" <<endl;
}
private:
const T1 & _d1;
const T2 & _d2;
};
void test2 ()
{
B<double , int> d1; // 调用特化的int版本
B<int , double> d2; // 调用基础的模板
B<int *, int*> d3; // 调用特化的指针版本
B<int&, int&> d4(1, 2); // 调用特化的指针版本
}