跳转至

C++模板(高阶)

约 1227 个字 197 行代码 预计阅读时间 7 分钟

非类型模版参数

前面的模版中,使用的都是针对对象类型设计的模版参数,从而便于编译器针对不同类型推演出不同类型的函数或者类

但是有一种模版参数比较特殊,即非类型模版参数,有以下特点:

  1. 只可以定义为整型类型的常量
  2. 非类型的模板参数必须在编译期就能确认结果

示例代码:

C++
1
2
3
4
5
6
7
template<class T, size_t N = 10>
class A
{

private:
    T arr[N];
};

在上面的代码中,T即为类型模板参数,而N即为非类型模板参数,并且因为size_t代表无符号整型,所以属于整型系列,编译通过

非类型模板参数主要使用在为数组开辟空间,当需要使用该类中的数组时,可以使用默认的10作为数组大小,也可以自定义N的大小从而确定数组的大小

在C++11中,新增了一个容器名为array,底层就是对数组进行了一个封装,目的是方便处理数组的相关问题,比如越界访问

array容器的定义:

C++
1
template < class T, size_t N > class array;

原来的数组是C类型的数组,该数组对越界访问的控制并不严格,甚至有时并不能发现是越界访问,所以针对这个问题,C++11添加了array容器,从而更好地处理越界访问等问题

模板特化

在C++中,除了可以对任意类型使用模板以外,还可以使用模板特化来针对某一种类或者函数提供特殊的模板

模板特化分为全特化和偏特化,而对于类和函数来说,也分为类模板全特化和偏特化以及函数模板全特化和偏特化

函数模板的特化步骤:

  1. 必须要先有一个基础的函数模板
  2. 关键字template后面接一对空的尖括号<>
  3. 函数名后跟一对尖括号,尖括号中指定需要特化的类型
  4. 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。

类模板全特化

所谓全特化,即特化的模板参数类型全部用指定的类型替换普通模板中的类型

例如下面的代码:

C++
 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
#include <iostream>
using namespace std;

//普通模板
template<class T1, class T2>
class A
{
public:
    A(T1 val1, T2 val2)
        :num1(val1)
        , num2(val2)
    {}

private:
    T1 num1;
    T2 num2;
};

//全特化为int类型
template<>
class A<int, int>
{
public:
    A(int val1, int val2)
        :num1(val1)
        ,num2(val2)
    {}

private:
    int num1;
    int num2;
};

int main()
{
    A<double, int> a(1.9, 2);//调用普通模板
    A<int, int> a1(1, 2);//调用全特化模板
    return 0;
}

在上面的代码中,针对int类型使用了全特化的类,此时如果使用两个int类型的值创建对象,那么编译器会直接调用全特化的类进行构造

此时考虑下面的仿函数

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
//仿函数
template<class T>
class less
{
public:
    bool operator()(T val1, T val2)
    {
        return val1 < val2;
    }
};

对于int类型,double类型这种普通的数值类型来说,直接比较并不会有什么问题(此处不考虑浮点数精度问题),但是如果为指针类型,那么比较方式会有不同,因为比较指针除了比较二者地址以外,还有比较指针指向的内容,而对于上面的比较大小的仿函数,如果直接将指针类型作为模板参数,那么比较的就是指针本身存的地址,如果此时想比较指针指向的内容时就需要用到全特化,参考下面的代码:

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
//仿函数
//Date为自定义类型,并且已经重载*和<
template<>
class less<Date*>
{
public:
    bool operator()(Date* val1, Date* val2)
    {
        return *val1 < *val2;
    }
};

类模板偏特化

对比全特化,偏特化就是只有一部分是指定的类型,其余的部分还是普通的模板参数类型,例如下面的代码:

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
//偏特化为T和int类型
template<class T>
class A<T, int>
{
public:
    A(T val1, int val2)
        :num1(val1)
        , num2(val2)
    {}

private:
    T num1;
    int num2;
};

在上面代码中,只要第二个模板参数类型时int类型时,就会走偏特化构造函数

现在有了下面三种模板:

C++
 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
//普通模板
template<class T1, class T2>
class A
{
public:
    A(T1 val1, T2 val2)
        :num1(val1)
        , num2(val2)
    {}

private:
    T1 num1;
    T2 num2;
};

//全特化为int类型
template<>
class A<int, int>
{
public:
    A(int val1, int val2)
        :num1(val1)
        ,num2(val2)
    {}

private:
    int num1;
    int num2;
};

//偏特化为T和int类型
template<class T>
class A<T, int>
{
public:
    A(T val1, int val2)
        :num1(val1)
        , num2(val2)
    {}

private:
    T num1;
    int num2;
};

下面有三个对象:

C++
1
2
3
A<double, double> a1(1.2, 1.2);//调用普通模板
A<int, int> a2(1, 2);//调用全特化模板
A<double, int> a3(1.9, 2);//调用偏特化

因为doubledouble类型没有偏特化和全特化,所以走普通模板,而intint类型有全特化,所以走全特化模板,对于doubleint类型,因为有偏特化,所以走偏特化模板

所以在普通模板、偏特化模板和全特化模板中,匹配顺序依次是:

  1. 全特化
  2. 偏特化
  3. 普通模板

函数模板全特化与偏特化

函数模板的全特化与偏特化方式参考下面的代码:

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
//普通函数模板
template<class T1, class T2>
T1 add(T1 val1, T2 val2)
{
    return val1 + val2;
}

//全特化函数模板
template<>
int add<int, int>(int val1, int val2)
{
    return val1 + val2;
}

//偏特化函数模板
template<class T>
T add<int, T>(int val1, int val2)
{
    return val1 + val2;
}

但是对于函数模板来说,一般不需要用到特化,只需要用函数重载+最匹配原则即可

模板分离编译

在C++中本身是不支持模板的声明和定义分别放在两个文件中,所以一般的处理方式有以下两种:

  1. 不写声明直接定义放在.h文件中
  2. 将声明写在定义之前,一般放在.hpp文件中

Note

一般的.hpp文件即为声明和定义在一起,表示该文件中既有类和函数模板的声明,也有对应的定义

例如下面的.hpp文件

C++
 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
//函数模板声明
template<class T>
T Add(const T& left, const T& right);

//普通函数声明
void func();

//类模板声明
template<class T>
class Stack 
{
public:
    //成员函数声明
    void Push(const T& x);
    void Pop();
private:
    T* _a = nullptr;
    int _top = 0;
    int _capacity = 0;
};

//函数模板定义
template<class T>
T Add(const T& left, const T& right)
{
    cout << "T Add(const T& left, const T& right)" << endl;
    return left + right;
}

//普通函数定义
void func()
{
    cout << "void func()" << endl;
}

//成员函数定义
template<class T>
void Stack<T>::Push(const T& x)
{
    cout << "void Stack<T>::Push(const T& x)" << endl;
}

//成员函数定义
template<class T>
void Stack<T>::Pop()
{
    cout << "void Pop()" << endl;
}