C++模板进阶

C++
1.2k words

模板进阶

非类型模板参数

模板参数分为类型形参和非类型形参

类型形参是出现在模板参数中,跟在class或者typename之后的参数类型名称

非类型形参是使用常量作为模板的一个参数,在类或者函数中可以作为常量使用

例如

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
namespace xu
{
template<class T, size_t N = 10>
class array
{
public:
T& operator[](size_t index)
{
return _array[index];
}
const T& operator[](size_t index) const
{
return _array[index];
}

size_t size() const
{
return _size;
}
bool empty() const
{
return 0 == _size;
}
private:
T _array[N];
size_t _size;
};
}

注意:

  1. 浮点数、类对象、字符串不允许作为非类型模板参数
  2. 非类型的模板参数必须在编译期就是一个确定的结果(数值)

模板的特化

概念

模板特化可以解决一些,对于特殊类型的特殊模板化处理的问题,如果不采取这样的方式,就有可能会导致错误的结果

例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
template<class T>
bool Less(T left, T right)
{
return left < right;
}

int main()
{
cout << Less(1. 2) << endl; // 结果正确

Date d1(2024, 2, 23);
Date d2(2024, 2, 24);
cout << Less(d1, d2) << endl; // 结果正确

Date* p1 = &d1;
Date* p2 = &d1;
cout << Less(p1, p2) << endl; // 结果错误

return 0;
}

对于第三种情况,我们就需要对这种类型(指针)进行特化,由此就分为函数模板特化和类模板特化

函数模板特化

模板特化之前,需要有一个基础的函数模板,在template之后加一个空的尖括号,在函数名之后加一对尖括号,其中放入需要特化的类型

需要注意的是,特化的函数形参表必须和模板函数的基础参数类型完全相同

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
template<class T> // 模板函数
bool Less(T left, T right)
{
return left < right;
}

template<> // 模板函数特化
bool Less<Date*>(Date* left, Date* right)
{
return *left<*right;
}

int main()
{
cout << Less(1. 2) << endl; // 结果正确

Date d1(2024, 2, 23);
Date d2(2024, 2, 24);
cout << Less(d1, d2) << endl; // 结果正确

Date* p1 = &d1;
Date* p2 = &d1;
cout << Less(p1, p2) << endl; // 结果正确

return 0;
}

注意:通常在遇到不能处理的模板参数时,都是直接给出更加匹配的一般函数,而不采用函数模板特化的方法

类模板特化

全特化

全特化指所有的模板参数都确定

例如

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
template<class T1, class T2> // 模板类
class Data
{
public:
Date()
{
cout << "Data<T1, T2>" << endl;
}
private:
T1 _d1;
T2 _d2;
};

template<>
class Data<int, char> // 全特化
{
public:
Data()
{
cout << "Data<int, char>" << endl;
}
private:
int _d1;
char _d2;
}

void test1()
{
Data<int, int> d1;
Data<int, char> d2;
};
偏特化

偏特化就是针对模板参数进行条件限制的特化版本,分为部分特化和参数更进一步的限制,例如:

1
2
3
4
5
6
7
8
9
10
11
12
template<class T1, class T2>
class Data
{
public:
Data()
{
cout << "Data<T1, T2>" << endl;
}
private:
T1 _d1;
T2 _d2;
};
  • 部分特化

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    template<class T1>
    class Data<T1, int>
    {
    public:
    Data()
    {
    cout << "Data<T1, int>" << endl;
    }
    private:
    T1 _d1;
    int _d2;
    };
  • 参数限制

    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
    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()
    {
    cout << "Data<T1&, T2&>" << endl;
    }
    private:
    const T1& _d1;
    const T2& _d2;
    };

    void test2()
    {
    Data<double, int> d1; // 调用int特化
    Data<int, double> d2; // 调用基础模板类
    Data<int*. int*> d3; // 调用指针特化
    Data<int&, int&> d4; // 调用引用特化
    }

模板分离编译

分离编译

当一个项目由若干源文件共同实现,且每个源文件分别编译生成目标文件,最后将所有目标文件链接形成单一可执行文件的过程,称之为分离编译模式

模板分离编译

当模板的声明与定义分开,在头文件声明,源文件定义时会出现报错,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// tp.h
template<class T>
T Add(const T& left, const T& right);

// tp.cpp
template<class T>
T Add(const T& left, const T& right)
{
return left+right;
}

// main.cpp
#include"tp.h"
int main()
{
Add(1, 2);
Add(1.0, 2.0);

return 0;
}

屏幕截图 2024-02-23 183052.png

这种报错是属于链接时报错,C++程序运行需要经历四个阶段,分别是预处理、编译、汇编、链接

因为头文件不参与编译,不会对Add函数模板实例化,不会生成具体的加法函数,也就不存在所谓的函数地址,因此在obj文件链接时,main函数调用Add时寻找地址时找不到结果,因此链接时报错

解决方法

  1. 将声明和定义放到同一个文件中,例如hpp或者h文件,推荐这种方式
  2. 在模板定义的位置显示实例化,不推荐使用

模板优缺点

优点:提高了代码的复用性,节省资源,可以更快的迭代开发,例如STL,增强了代码的灵活性

缺点:会导致代码膨胀问题,导致编译时间边长;出现模板编译错误时,错误信息非常凌乱,难以定位

Comments