模板

template大汇总

范型

如何实现泛型?

  • 函数重载
  • 模板
    • 类模板
    • 函数模板
1
2
3
4
5
//函数模板
template<typename T1, typename T2,......,typename Tn>
返回值类型 函数名(参数列表){}

//类模板
CPP

上述代码中,中typename是用来定义模板参数关键字,也可以使用class

重载存在的问题

  1. 重载的函数仅仅是类型不同,代码复用率比较低,只要有新类型出现时,就需要用户自己增加对应的函数。

  2. 代码的可维护性比较低,一个出错可能所有的重载均出错。

函数模板

函数模板是编译器用使用方式产生特定具体类型函数的模具。所以其实模板就是将本来应该我们本该重复的事情交给了编译器

函数模板的实例化

  • 隐式实例化——让编译器根据实参推演模板参数的实际类型。

  • 显式实例化——在函数名后的<>中指定模板参数的实际类型。例如:

    1
    my_FuncName<type>(date, date);
    CPP

类模板

允许以一个类当作蓝本,无需重复代码,创建管理着不同类型数据的类

1
2
3
4
5
template<class T1, class T2, ..., class Tn>
class 类模板名
{
// 类内成员定义,使用T当作类型的占位符
};
CPP

类模板的实例化

1
MyTemplateClassName<type> classNumber;
CPP

模板参数的匹配原则

  • 非模板函数可以和一个同名的函数模板同时存在。并且在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数,那么将选择模板。

  • 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换。

  • 非类型形参,就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用

函数模板特化

函数模板特化

目的,函数模板对于特殊的类型,生成特殊的方法。但对于一些参数类型复杂的函数模板,特化时特别给出,因此函数模板不建议特化。

使用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
1.初级的函数模板
template<class T>
bool Less(T left, T right)
{
return left < right;
}
2.空尖括号
template<>
3.函数名后跟一对尖括号,尖括号中指定需要特化的类型
bool Less<Date*>(Date* left, Date* right)/*4.函数形参表,必须要和模板函数的基础参数类型完全相同*/
{
return *left < *right;
}
CPP
  1. 必须要先有一个基础的函数模板
  2. 关键字template后面接一对空的尖括号<>
  3. 函数名后跟一对尖括号,尖括号中指定需要特化的类型
  4. 对于函数形参表,必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。

类模板特化

  • 全特化:全特化即是将模板参数列表中所有的参数都确定化。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //全特化
    //假设已有一个Date函数模板
    template<>
    class Date<int, char> {
    public:
    Date() {cout << "Data<int, char>, 全特化" << endl;}
    private:
    int _d1;
    char _d2;
    };
    CPP
  • 偏特化:任何针对模版参数进一步进行条件限制设计的特化版本。
    • 部分特化
    • 参数限制
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
//偏特化中的部分特化
template<class T1>
class Date<T1,int> {
public:
Date() { cout << "Date<T1, int>,偏特化" << endl; }
private:
T1 _d1;
int _d2;
};
Date<char, int> a3;//调用

---
//参数限制
template<class T1,class T2>
class Date<T1&,T2&>{
public:
Date(const T1& d1,const T2& d2)
:_d1(d1)
,_d2(d2)
{
cout << "Data<&, &>, 引用特化" << endl;
}
private:
const T1& _d1;
const T2& _d2;
};
Date<int&,int&> a4(1,2);//调用
CPP

完整实验代码

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

template<class T1, class T2>
class Date {
public:
Date() {cout << "Data<T1, T2>,普通类模板" << endl;}
private:
T1 _d1;
T2 _d2;
};

template<class T1>
class Date<T1,int> {
public:
Date() { cout << "Date<T1, int>,偏特化" << endl; }
private:
T1 _d1;
int _d2;
};

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

template<class T1,class T2>
class Date<T1&,T2&>{
public:
Date(const T1& d1,const T2& d2)
:_d1(d1)
,_d2(d2)
{
cout << "Data<&, &>, 引用特化" << endl;
}
private:
const T1& _d1;
const T2& _d2;
};

template<class T1,class T2>
class Date<T1*,T2*>{
public:
Date(){cout << "Data<*, *>, 指针特化" << endl;}
};

int main()
{
Date<int, short> a1;
Date<int, char> a2;
Date<char, int> a3;
Date<int&,int&> a4(1,2);
Date<int*,int*> a5;

return 0;
}
CPP

模板分离编译

须知:

  1. 模板函数的代码其实并不能直接编译成二进制代码,需要被实例化

  2. 如果有一个项目涉及多个源文件,将所有目标文件链接起来形成单一的可执行文件的过程称为分离式编译模式

  3. 模板类和模板函数的实现必须在实例化模板的地方可见
    如果对于编译过程有想更多了解,这里提供另一篇博客。GCC编译过程

如果当一个模板的声明在一个test.h文件中,但是其实例化在一个test.cpp文件中。并且在main.cpp中进行实例化,那么会出现如下的情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//-------------test.h----------------//
template<class T>
class A
{
public:
void f(); //这里只是个声明
};
//---------------test.cpp-------------//
#include"test.h"
template <class T>
void A<T>::f() {
// 具体的实现
// do something
}
//---------------main.cpp---------------//
#include"test.h"
int main()
{
A<int> a;//实例化
a.f();
return 0;
}
CPP
  1. 对于test.cpp文件来说,由于它内部没有模板实例化的过程,(test.obj中)所有A模板的信息并不可见,即不会记录有关A::f的信息。

  2. 对于main.cpp来说,需要关于A::f的具体实现,但在test.obj中找不到,在test.h中也找不到。

  3. 故报错

通常解决方案:

  1. 将声明和定义放到一个文件。

指定位置实例化

  • 显式实例化是一种机制,用于确保模板的实例化只在一个地方发生,从而避免重复定义的问题。 MyTemplate.cpp 文件中生成一次,而在其他源文件中不会重复生成,从而避免了代码膨胀和重复定义问题

例如:

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
<________MyTemplate.h___________>
// 在头文件中定义模板
template<typename T>
class MyTemplate {
public:
void doSomething(T value);
};

template<typename T>
void MyTemplate<T>::doSomething(T value) {
// 实现代码
}

template<typename T>
void myFunction(T value) {
// 实现代码
}

<_________MyTemplate.cpp___________>
// 在一个源文件中进行显式实例化
#include "MyTemplate.h"

// 显式实例化 MyTemplate 类和 myFunction 函数
template class MyTemplate<int>;
template void myFunction<int>(int);

<_________OtherFile.cpp___________>
// 在其他源文件中声明显式实例化
#include "MyTemplate.h"

// 声明 MyTemplate 类和 myFunction 函数的显式实例化
extern template class MyTemplate<int>;
extern template void myFunction<int>(int);


CPP

注意

  • 注意:类模板中函数放在类外进行定义时,需要加模板参数列表
  • 浮点数、类对象以及字符串是不允许作为非类型模板参数的。
  • 非类型的模板参数必须在编译期就能确认结果

参考资料

为什么C++编译器不能支持对模板的分离式编译

c++11中指定位置实例化模板


模板
https://weihehe.top/2024/07/10/模板/
作者
weihehe
发布于
2024年7月10日
许可协议