C和C++语言的静态类型系统,在满足效率与安全性要求的同时,很大程度上也成为阻碍程序员编写通用代码的桎梏。它迫使人们不得不为每一种数据类型编写完全或几乎完全相同的实现,虽然它们在抽象层面上是一致的。
xxxxxxxxxx
671// typed.cpp
2
3// 针对不同的类型定义不同的函数
4
5
6
7using namespace std;
8
9// 整型版本
10
11int min_int(int x, int y) {
12 return x < y ? x : y;
13}
14
15int max_int(int x, int y) {
16 return x < y ? y : x;
17}
18
19// 双精度型版本
20
21double min_double(double x, double y) {
22 return x < y ? x : y;
23}
24
25double max_double(double x, double y) {
26 return x < y ? y : x;
27}
28
29// 字符串型版本
30
31string min_string(string x, string y) {
32 return x < y ? x : y;
33}
34
35string max_string(string x, string y) {
36 return x < y ? y : x;
37}
38
39// 测试用例
40
41int main(void) {
42 cout << "Enter two integer: " << flush;
43 int nx, ny;
44 cin >> nx >> ny;
45 cout << "min=" << min_int(nx, ny) << endl;
46 cout << "max=" << max_int(nx, ny) << endl;
47
48 cout << "Enter two double: " << flush;
49 double dx, dy;
50 cin >> dx >> dy;
51 cout << "min=" << min_double(dx, dy) << endl;
52 cout << "max=" << max_double(dx, dy) << endl;
53
54 cout << "Enter two string: " << flush;
55 string sx, sy;
56 cin >> sx >> sy;
57 cout << "min=" << min_string(sx, sy) << endl;
58 cout << "max=" << max_string(sx, sy) << endl;
59
60 cout << "Enter two string: " << flush;
61 char cx[256], cy[256];
62 cin >> cx >> cy;
63 cout << "min=" << min_string(cx, cy) << endl;
64 cout << "max=" << max_string(cx, cy) << endl;
65
66 return 0;
67}
宏定义只是在预处理器的作用下,针对源代码的文本替换,其本身不具备函数语义。因此借助于参数宏(又名宏函数)可以在某种程度上使程序的编写者摆脱那些源于类型的约束和限制,但同时也丧失了类型的安全性。
xxxxxxxxxx
421// untyped.cpp
2
3// 借助参数宏摆脱了类型限制,同时也因为失去类型检查而引入风险
4
5
6
7using namespace std;
8
9// 宏————去类型化
10
11
12
13
14// 测试用例
15
16int main(void) {
17 cout << "Enter two integer: " << flush;
18 int nx, ny;
19 cin >> nx >> ny;
20 cout << "min=" << min(nx, ny) << endl;
21 cout << "max=" << max(nx, ny) << endl;
22
23 cout << "Enter two double: " << flush;
24 double dx, dy;
25 cin >> dx >> dy;
26 cout << "min=" << min(dx, dy) << endl;
27 cout << "max=" << max(dx, dy) << endl;
28
29 cout << "Enter two string: " << flush;
30 string sx, sy;
31 cin >> sx >> sy;
32 cout << "min=" << min(sx, sy) << endl;
33 cout << "max=" << max(sx, sy) << endl;
34
35 cout << "Enter two string: " << flush;
36 char cx[256], cy[256];
37 cin >> cx >> cy;
38 cout << "min=" << min(cx, cy) << endl; // ?
39 cout << "max=" << max(cx, cy) << endl; // ?
40
41 return 0;
42}
利用宏定义构建通用代码的框架,让预处理器将其扩展为针对不同类型的具体版本。将宏的一般性和函数的类型安全性完美地结合起来。
xxxxxxxxxx
651// macro.cpp
2
3// 借助预处理器产生类似模板的功能
4
5
6
7using namespace std;
8
9// 模板宏
10
11
12
13
14
15
16
17
18
19
20
21// 将模板宏扩展为具体函数
22
23MIN(int)
24MAX(int)
25
26MIN(double)
27MAX(double)
28
29MIN(string)
30MAX(string)
31
32// 函数名宏
33
34
35
36
37// 测试用例
38
39int main(void) {
40 cout << "Enter two integer: " << flush;
41 int nx, ny;
42 cin >> nx >> ny;
43 cout << "min=" << min(int)(nx, ny) << endl;
44 cout << "max=" << max(int)(nx, ny) << endl;
45
46 cout << "Enter two double: " << flush;
47 double dx, dy;
48 cin >> dx >> dy;
49 cout << "min=" << min(double)(dx, dy) << endl;
50 cout << "max=" << max(double)(dx, dy) << endl;
51
52 cout << "Enter two string: " << flush;
53 string sx, sy;
54 cin >> sx >> sy;
55 cout << "min=" << min(string)(sx, sy) << endl;
56 cout << "max=" << max(string)(sx, sy) << endl;
57
58 cout << "Enter two string: " << flush;
59 char cx[256], cy[256];
60 cin >> cx >> cy;
61 cout << "min=" << min(string)(cx, cy) << endl;
62 cout << "max=" << max(string)(cx, cy) << endl;
63
64 return 0;
65}
xxxxxxxxxx
491// functmpl.cpp
2
3// 函数模板
4
5
6
7using namespace std;
8
9// 定义函数模板
10
11template<class T>
12T min(T x, T y) {
13 return x < y ? x : y;
14}
15
16template<typename T>
17T max(T x, T y) {
18 return x < y ? y : x;
19}
20
21// 使用函数模板
22
23int main (void) {
24 cout << "Enter two integer: " << flush;
25 int nx, ny;
26 cin >> nx >> ny;
27 cout << "min=" << ::min<int>(nx, ny) << endl;
28 cout << "max=" << ::max<int>(nx, ny) << endl;
29
30 cout << "Enter two double: " << flush;
31 double dx, dy;
32 cin >> dx >> dy;
33 cout << "min=" << ::min<double>(dx, dy) << endl;
34 cout << "max=" << ::max<double>(dx, dy) << endl;
35
36 cout << "Enter two string: " << flush;
37 string sx, sy;
38 cin >> sx >> sy;
39 cout << "min=" << ::min<string>(sx, sy) << endl;
40 cout << "max=" << ::max<string>(sx, sy) << endl;
41
42 cout << "Enter two string: " << flush;
43 char cx[256], cy[256];
44 cin >> cx >> cy;
45 cout << "min=" << ::min<string>(cx, cy) << endl;
46 cout << "max=" << ::max<string>(cx, cy) << endl;
47
48 return 0;
49}
模板的类型参数必须用如下形式的语法来声明:
xxxxxxxxxx
11template<typename 类型形参1, typename 类型形参2, ...>
例如:
xxxxxxxxxx
51template<typename A, typename b, typename _C>
2A function(b arg) {
3 _C var;
4 ...
5}
这里的typename关键字也可以换成class,但建议使用typename。
可以使用任何标识符作为类型参数的名称,但使用“T”已经成为了一种惯例。类型参数“T”表示的是,调用者调用这个函数时所指定的任意类型。
可以将任何具体的类型实参(基本类型、自定义类型、类类型等)传递给模板的类型形参,前提是所使用的类型实参必须能够满足该模板所需要的操作。比如将一个不支持小于操作符(<)的类型实参,传递给min或max模板的类型形参,将引发编译错误。
xxxxxxxxxx
91template<class T>
2T min(T x, T y) {
3 return x < y ? x : y; // 传递给函数模板min的类型形参T的类型实参,必须支持小于操作符(<)
4}
5
6template<typename T>
7T max(T x, T y) {
8 return x < y ? y : x; // 传递给函数模板max的类型形参T的类型实参,必须支持小于操作符(<)
9}
通常而言,并不是把模板编译成一个可以处理任何类型的单一实体,而是针对传递给模板类型形参的每一种类型实参,都从模板产生出一个以具体类型实现的独立实体。这种以具体类型代替类型参数的过程称为模板的实例化。每个以具体类型实现的独立实体都是该模板的一个实例。
函数模板min的三个实例:
xxxxxxxxxx
111int min(int x, int y) {
2 return x < y ? x : y;
3}
4
5double min(double x, double y) {
6 return x < y ? x : y;
7}
8
9string min(string x, string y) {
10 return x < y ? x : y;
11}
函数模板max的三个实例:
xxxxxxxxxx
111int max(int x, int y) {
2 return x < y ? y : x;
3}
4
5double max(double x, double y) {
6 return x < y ? y : x;
7}
8
9string max(string x, string y) {
10 return x < y ? y : x;
11}
函数模板的类型实参放在尖括号中,调用实参放在圆括号中,类型实参必须位于函数模板名和调用实参之间:
xxxxxxxxxx
11函数模板名<类型实参1, 类型实参2, ...>(调用实参1, 调用实参2, ...);
只要使用函数模板,编译器就会自动引发这样一个实例化过程,程序员并不需要额外地请求对模板实例化。例如:
min<int>(nx, ny)
实例化出int min(int x, int y) { ... }
函数,并调用之
max<int>(nx, ny)
实例化出int max(int x, int y) { ... }
函数,并调用之
min<double>(dx, dy)
实例化出double min(double x, double y) { ... }
函数并调用之
max<double>(dx, dy)
实例化出double max(double x, double y) { ... }
函数并调用之
min<string>(sx, sy)
实例化出string min(string x, string y) { ... }
函数并调用之
max<string>(sx, sy)
实例化出string max(string x, string y) { ... }
函数并调用之
min<string>(cx, cy)
直接调用已经实例化的string min(string x, string y) { ... }
函数
max<string>(cx, cy)
直接调用已经实例化的string max(string x, string y) { ... }
函数
每个函数模板事实上都被编译了两次:
一次是在实例化之前,先检查模板代码本身,查看语法是否正确
另一次是在实例化期间,结合传入的类型实参,再次检查模板代码,查看针对具体类型的所有操作是否都有效
只有在第二次编译时,才会真正产生针对具体函数的二进制机器指令,第一次编译仅仅在编译器内部形成的一个用于描述函数模板的内部表示。
如果函数模板调用参数的类型相关于该模板的类型参数,那么在调用该函数模板时,即使不显式指明类型实参,编译器也有能力根据调用参数的类型隐式推断出正确的类型实参,以获得与普通函数调用一致的语法表达。例如:
xxxxxxxxxx
271int main (void) {
2 cout << "Enter two integer: " << flush;
3 int nx, ny;
4 cin >> nx >> ny;
5 cout << "min=" << ::min(nx, ny) << endl;
6 cout << "max=" << ::max(nx, ny) << endl;
7
8 cout << "Enter two double: " << flush;
9 double dx, dy;
10 cin >> dx >> dy;
11 cout << "min=" << ::min(dx, dy) << endl;
12 cout << "max=" << ::max(dx, dy) << endl;
13
14 cout << "Enter two string: " << flush;
15 string sx, sy;
16 cin >> sx >> sy;
17 cout << "min=" << ::min(sx, sy) << endl;
18 cout << "max=" << ::max(sx, sy) << endl;
19
20 cout << "Enter two string: " << flush;
21 char cx[256], cy[256];
22 cin >> cx >> cy;
23 cout << "min=" << ::min<string>(cx, cy) << endl;
24 cout << "max=" << ::max<string>(cx, cy) << endl;
25
26 return 0;
27}
注意在cx和cy中获取最小和最大值的函数模板调用没有使用隐式推断:
xxxxxxxxxx
21cout << "min=" << ::min(cx, cy) << endl;
2cout << "max=" << ::max(cx, cy) << endl;
而依然使用显式实例化:
xxxxxxxxxx
21cout << "min=" << ::min<string>(cx, cy) << endl;
2cout << "max=" << ::max<string>(cx, cy) << endl;
因为cx和cy是字符数组,其类型为char*,隐式推断将以char*作为实例化函数模板的类型实参,实例化结果为:
xxxxxxxxxx
71char* min(char* x, char* y) {
2 return x < y ? x : y;
3}
4
5char* max(char* x, char* y) {
6 return x < y ? y : x;
7}
对由字符指针指向的空字符结尾的C风格字符串,使用小于号比较大小,显然是不合适的。
以下三种情况,不能隐式推断,必须显式指定模板参数:
不是全部模板参数都与调用参数的类型相关
xxxxxxxxxx
11template<typename V, typename A> void foo(A arg) { ... V var ... }
xxxxxxxxxx
31foo(1.23); // 错误
2foo<int, double>(1.23); // 正确
3foo<int>(1.23); // 正确
隐式推断的同时不允许隐式类型转换
xxxxxxxxxx
11template<typename T> void foo(T x, T y) { ... }
xxxxxxxxxx
31foo(123, 4.56); // 错误
2foo<double>(123, 4.56); // 正确
3foo(123, (int)4.56); // 正确
返回类型不能隐式推断
xxxxxxxxxx
11template<typename R, typename A> R foo(A arg) { ... }
xxxxxxxxxx
31int ret = foo(1.23); // 错误
2int ret = foo<int, double>(1.23); // 正确
3int ret = foo<int>(1.23); // 正确
xxxxxxxxxx
561// deduction.cpp
2
3// 函数模板参数的隐式推断
4
5
6
7
8using namespace std;
9
10template<typename T>
11void foo(T const& x, T const& y) {
12 cout << typeid(T).name() << endl;
13}
14
15template<typename R, typename A>
16R bar(A const& arg) {
17 R ret;
18 cout << typeid(R).name() << ' ' << typeid(A).name() << endl;
19 return ret;
20}
21
22int main(void) {
23 // 隐式实例化函数模板
24
25 int a, b;
26 foo(a, b);
27
28 double c, d;
29 foo(c, d);
30
31 char e[256], f[256];
32 foo(e, f);
33 foo("hello", "world");
34 // foo("hello", "tarena"); // char[6] != char[7]
35
36 string g, h;
37 foo(g, h);
38
39 // 隐式推断不允许隐式类型转换
40 // foo(a, c);
41 // 但可以显式类型转换
42 foo(a, (int)c);
43 foo(static_cast<double>(a), c);
44 // 显式实例化函数模板
45 foo<int>(a, c);
46 foo<double>(a, c);
47
48 int i;
49 double j;
50 // 返回类型不能隐式推断
51 // i = bar(j);
52 i = bar<int, double>(j);
53 i = bar<int>(j);
54
55 return 0;
56}
函数模板参数的隐式推断与缺省值之间存在矛盾,因此函数模板的模板参数不能带有缺省值。例如:
xxxxxxxxxx
11template<typename T = int> T min(T x, T y) { return x < y ? x : y; } // 错误
xxxxxxxxxx
11cout << ::min(1.23, 4.56) << endl; // T=double? T=int?
C++11以后,允许为函数模板的模板参数指定缺省值,但依然会优先选择隐式推断的结果。
xxxxxxxxxx
181// defargs.cpp
2
3// C++11以后,允许为函数模板的模板参数指定缺省值,但依然会优先选择隐式推断的结果
4
5
6
7using namespace std;
8
9template<typename T = int>
10T min(T x, T y) {
11 return x < y ? x : y;
12}
13
14int main(void) {
15 cout << ::min(1.23, 4.56) << endl; // T=double, 1.23
16
17 return 0;
18}
xxxxxxxxxx
661// overload.cpp
2
3// 函数模板重载
4
5
6
7
8
9
10using namespace std;
11
12// 求两个C风格字符串的最小值
13char const* min(char const* x, char const* y) {
14 cout << "<1" << typeid(char const*).name() << "> " << flush;
15 return strcmp(x, y) < 0 ? x : y;
16}
17
18// 求两个任意类型值的最小值
19template<typename T>
20T min(T x, T y) {
21 cout << "<2" << typeid(T).name() << "> " << flush;
22 return x < y ? x : y;
23}
24
25// 求两个任意类型指针所指向目标的最小值
26template<typename T>
27T* min(T* x, T* y) {
28 cout << "<3" << typeid(T).name() << "> " << flush;
29 return *x < *y ? x : y;
30}
31
32// 求三个任意类型值的最小值
33template<typename T>
34T min(T x, T y, T z) {
35 cout << "<4" << typeid(T).name() << "> " << flush;
36 return ::min(::min(x, y), z);
37}
38
39// 求两个C风格字符串的最小值
40// char const* min(char const* x, char const* y) {
41// cout << "<1" << typeid(char const*).name() << "> " << flush;
42// return strcmp(x, y) < 0 ? x : y;
43// }
44
45int main(void) {
46 // 编译器优先选择普通函数
47 cout << ::min("hello", "world") << endl; // <1PKc> hello
48 // 除非函数模板能够产生具有更好匹配性的函数实例
49 cout << ::min(123, 456) << endl; // <2i> 123
50
51 // 在参数传递过程中如需隐式类型转换,编译器将优先选择普通函数
52 char s[] = "hello";
53 cout << ::min(s, "world") << endl; // <1PKc> hello
54
55 // 通过空模板参数列表告知编译器使用函数模板
56 // 针对指针的版本显然比任意类型版本更加匹配
57 cout << ::min<>("hello", "hellooo") << endl; // <3c> hellooo
58
59 // 显式指定的模板参数必须在所选择的重载版本中与调用参数的类型保持一致
60 cout << ::min<char const*>("hello", "world") << endl; // <2PKc> world
61
62 // 在函数模板的实例化函数中,编译器仍然优先选择普通函数
63 cout << ::min("hello", "world", "tarena") << endl; // <4PKc> <1PKc> <1PKc> hello
64
65 return 0;
66}
普通函数和可实例化为该函数的函数模板构成重载关系。在其它条件都相同的情况下,编译器优先选择普通函数,除非函数模板能够产生具有更好匹配性的函数实例。
xxxxxxxxxx
21char const* min(char const* x, char const* y) { ... } // 普通函数
2template<typename T> T min(T x, T y) { ... } // 函数模板
xxxxxxxxxx
21cout << ::min("hello", "world") << endl; // 调用普通函数
2cout << ::min(123, 456) << endl; // 调用函数模板实例
函数模板的隐式实例化不支持隐式类型转换,但普通函数支持。因此在参数传递过程中如需隐式类型转换,编译器将优先选择普通函数。
xxxxxxxxxx
21char const* min(char const* x, char const* y) { ... } // 普通函数
2template<typename T> T min(T x, T y) { ... } // 函数模板
xxxxxxxxxx
21char s[] = "hello";
2cout << ::min(s, "world") << endl; // 调用普通函数,char*->char const*
可以显式指定一个空的模板参数列表,明确告知编译器使用函数模板,但模板参数却由隐式推断决定。即便是在函数模板中选择,编译器也会尽可能选择类型约束性强的版本,即更特殊的版本。
xxxxxxxxxx
31char const* min(char const* x, char const* y) { ... } // 普通函数
2template<typename T> T min(T x, T y) { ... } // 函数模板
3template<typename T> T* min(T* x, T* y) { ... } // 更特殊的函数模板
xxxxxxxxxx
11cout << ::min<>("hello", "hellooo") << endl; // 调用更特殊的函数模板实例
如果为函数模板显式指定了模板参数,那么所选择的重载版本必须能够保证模板参数与调用参数的类型一致。
xxxxxxxxxx
31char const* min(char const* x, char const* y) { ... } // 普通函数
2template<typename T> T min(T x, T y) { ... } // 函数模板
3template<typename T> T* min(T* x, T* y) { ... } // 更特殊的函数模板
xxxxxxxxxx
11cout << ::min<char const*>("hello", "world") << endl; // 调用函数模板实例
即使是在函数模板的实例化函数中,编译器仍然优先选择普通函数,前提是该普通函数在一次编译时可见。
xxxxxxxxxx
51char const* min(char const* x, char const* y) { ... } // 普通函数
2template<typename T> T min(T x, T y) { ... } // 两参函数模板
3template<typename T> T min(T x, T y, T z) {
4 return ::min(::min(x, y), z);
5} // 三参函数模板
xxxxxxxxxx
11cout << ::min("hello", "world", "tarena") << endl; // 调用三参函数模板实例,后者调用普通函数
xxxxxxxxxx
401// bad.cpp
2
3// 重载函数模板最好只针对参数的个数或具体类型,否则极可能导致非预期的后果
4
5
6
7
8
9
10using namespace std;
11
12template<typename T>
13T const& min(T const& x, T const& y) {
14 return x < y ? x : y;
15}
16
17// 正确的写法
18char const* const& min(char const* const& x, char const* const& y) {
19 return strcmp(x, y) < 0 ? x : y;
20}
21
22/* 错误的写法
23char const* min(char const* x, char const* y) {
24 return strcmp(x, y) < 0 ? x : y;
25}
26*/
27template<typename T>
28T const& min(T const& x, T const& y, T const& z) {
29 return ::min(::min(x, y), z); // 返回对匿名局部变量的引用
30}
31
32int main(void) {
33 char const* a = "a";
34 char const* b = "ab";
35 char const* c = "abc";
36 char const* const& d = ::min(a, b, c);
37 cout << d << endl;
38
39 return 0;
40}
xxxxxxxxxx
601// clstmpl.cpp
2
3// 类模板
4
5
6
7using namespace std;
8
9// 定义类模板
10
11template<class T>
12class Comparator {
13public:
14 Comparator(T x, T y) : x(x), y(y) {}
15
16 T min(void) const {
17 return x < y ? x : y;
18 }
19
20 T max(void) const {
21 return x < y ? y : x;
22 }
23
24private:
25 T x, y;
26};
27
28// 使用类模板
29
30int main (void) {
31 cout << "Enter two integer: " << flush;
32 int nx, ny;
33 cin >> nx >> ny;
34 Comparator<int> cn(nx, ny);
35 cout << "min=" << cn.min() << endl;
36 cout << "max=" << cn.max() << endl;
37
38 cout << "Enter two double: " << flush;
39 double dx, dy;
40 cin >> dx >> dy;
41 Comparator<double> cd(dx, dy);
42 cout << "min=" << cd.min() << endl;
43 cout << "max=" << cd.max() << endl;
44
45 cout << "Enter two string: " << flush;
46 string sx, sy;
47 cin >> sx >> sy;
48 Comparator<string> cs(sx, sy);
49 cout << "min=" << cs.min() << endl;
50 cout << "max=" << cs.max() << endl;
51
52 cout << "Enter two string: " << flush;
53 char cx[256], cy[256];
54 cin >> cx >> cy;
55 cs = Comparator<string>(cx, cy);
56 cout << "min=" << cs.min() << endl;
57 cout << "max=" << cs.max() << endl;
58
59 return 0;
60}
模板的类型参数必须用如下形式的语法来声明:
xxxxxxxxxx
11template<typename 类型形参1, typename 类型形参2, ...>
例如:
xxxxxxxxxx
61template<typename A, typename b, typename _C>
2class MyClass {
3public:
4 A a;
5 b foo(_C c) { ... };
6};
这里的typename关键字也可以换成class,但建议使用typename。
在类模板的内部,类型参数可以象其它任何具体类型一样,用于成员变量、成员函数、成员类型(内部类型),甚至基类的声明。例如:
xxxxxxxxxx
61template<typename M, typename R, typename A, typename V, typename T, typename B>
2class MyClass : public B {
3 M mem;
4 R function(A arg) { ... V var ... }
5 typedef T* pointer;
6};
从类模板到对象实际上经历了两个实例化过程
编译期:编译器将类模板实例化为类并生成对象创建指令
运行期:处理器执行对象创建指令将类实例化为内存对象
类模板本身并不代表一个确定的类型,既不能用于定义对象,也不能用于声明指针或引用。只有通过模板实参将其实例化为具体类以后,才具备类型语义。
xxxxxxxxxx
11类模板名<类型实参1, 类型实参2, ...>
类模板中,只有那些实际被调用的成员函数才会被实例化,即产生二进制代码。某些类型虽然并没有提供类模板所需要的全部功能,但照样可以实例化该类模板,只要不直接或间接调用那些依赖于未提供功能的成员函数即可。
xxxxxxxxxx
161template<class T>
2class Comparator {
3public:
4 Comparator(T x, T y) : x(x), y(y) {}
5
6 T min(void) const {
7 return x < y ? x : y; // 若调用此函数,实例化该模板的类型,必须支持小于操作符(<)
8 }
9
10 T max(void) const {
11 return x > y ? x : y; // 若调用此函数,实例化该模板的类型,必须支持大于操作符(>)
12 }
13
14private:
15 T x, y;
16};
xxxxxxxxxx
141class Integer {
2public:
3 Integer(int n) : n(n) {}
4
5 // 只实现了小于操作符(<)
6 bool operator<(Integer const& i) const {
7 return n < i.n;
8 }
9
10 // 没有实现大于操作符(>)
11
12private:
13 int n;
14};
xxxxxxxxxx
21Comparator<Integer> cn(123, 456); // 可以用Integer类实例化Comparator类模板
2cout << cn.min() << endl; // 只要不调用max函数就没问题
与函数模板不同,类模板的类型参数不支持隐式推断。
xxxxxxxxxx
61template<class T>
2class Comparator {
3public:
4 Comparator(T x, T y) : x(x), y(y) {}
5 ...
6};
xxxxxxxxxx
41int nx, ny;
2...
3Comparator<int> cn(nx, ny); // 正确
4Comparator cn(nx, ny); // 错误
类模板的静态成员变量,既不是一个对象一份,也不是一个模板一份,而是在该类模板的每个实例化类中,各有一份独立的拷贝,且为该类的所有实例化对象所共享。
xxxxxxxxxx
381// static.cpp
2
3// 类模板的静态成员
4
5
6
7using namespace std;
8
9template<typename T>
10class A {
11public:
12 static void foo(void) {
13 cout << &n << ' ' << &t << endl;
14 }
15
16private:
17 static int n;
18 static T t;
19};
20
21template<typename T> int A<T>::n;
22template<typename T> T A<T>::t;
23
24int main(void) {
25 A<int> a, b;
26 a.foo();
27 b.foo();
28
29 A<double> c, d;
30 c.foo();
31 d.foo();
32
33 A<string> e, f;
34 e.foo();
35 f.foo();
36
37 return 0;
38}
类模板的类型实参可以是任何类型,只要该类型能够提供模板所需要的功能即可。
类模板自身的实例化类亦可实例化其自身,谓之递归实例化。通过这种方法可以很容易地构建那些在空间上具有递归特征的数据结构。
xxxxxxxxxx
521// recursion.cpp
2
3// 类模板的递归实例化
4
5
6
7using namespace std;
8
9template<typename T>
10class Array {
11public:
12 T& operator[](size_t i) {
13 return arr[i];
14 }
15
16 T const& operator[](size_t i) const {
17 return const_cast<Array&>(*this)[i];
18 }
19
20private:
21 T arr[3];
22};
23
24template<typename T>
25ostream& operator<<(ostream& os, Array<T> const& array) {
26 for (size_t i = 0; i < 3; ++i)
27 os << '(' << array[i] << ')';
28
29 return os;
30}
31
32int main(void) {
33 Array<int> ai;
34 ai[0] = 100;
35 ai[1] = 200;
36 ai[2] = 300;
37 cout << ai << endl;
38
39 Array<string> as;
40 as[0] = "北京";
41 as[1] = "上海";
42 as[2] = "广州";
43 cout << as << endl;
44
45 Array<Array<int>> aai;
46 for (size_t i = 0; i < 3; ++i)
47 for (size_t j = 0; j < 3; ++j)
48 aai[i][j] = (i+1)*10+j+1;
49 cout << aai << endl;
50
51 return 0;
52}
Array<int>
:一维数组
Array<Array<int>>
:二维数组
Array<Array<Array<int>>>
:三维数组
通过特化类模板,可以优化针对某种特定类型的实现,或者克服某种特定类型在实例化类模板时所表现出的不足。
xxxxxxxxxx
601// error.cpp
2
3// 特定类型在实例化类模板时所表现出的不足
4
5
6
7using namespace std;
8
9// 定义类模板
10
11template<class T>
12class Comparator {
13public:
14 Comparator(T x, T y) : x(x), y(y) {}
15
16 T min(void) const {
17 return x < y ? x : y;
18 }
19
20 T max(void) const {
21 return x < y ? y : x;
22 }
23
24private:
25 T x, y;
26};
27
28// 使用类模板
29
30int main (void) {
31 cout << "Enter two integer: " << flush;
32 int nx, ny;
33 cin >> nx >> ny;
34 Comparator<int> cn(nx, ny);
35 cout << "min=" << cn.min() << endl;
36 cout << "max=" << cn.max() << endl;
37
38 cout << "Enter two double: " << flush;
39 double dx, dy;
40 cin >> dx >> dy;
41 Comparator<double> cd(dx, dy);
42 cout << "min=" << cd.min() << endl;
43 cout << "max=" << cd.max() << endl;
44
45 cout << "Enter two string: " << flush;
46 string sx, sy;
47 cin >> sx >> sy;
48 Comparator<string> cs(sx, sy);
49 cout << "min=" << cs.min() << endl;
50 cout << "max=" << cs.max() << endl;
51
52 cout << "Enter two string: " << flush;
53 char cx[256], cy[256];
54 cin >> cx >> cy;
55 Comparator<char const*> cc(cx, cy);
56 cout << "min=" << cc.min() << endl;
57 cout << "max=" << cc.max() << endl;
58
59 return 0;
60}
特化一个类模板可以特化该类模板的所有成员函数,相当于重新写一个针对某种特定类型的具体类。对一个类模板做全类特化:
需要特化该类模板的所有成员函数
必须在起始处声明一个template<>
,接下来声明用来特化类模板的具体类型。该类型被用作类模板的实参,且必须在类模板名后面显式指定
xxxxxxxxxx
41template<>
2class 类模板名<特化类型> {
3 针对特化类型的具体类
4};
每个成员函数都必须被重新定义为普通成员函数。通用版本中的每个模板参数都被具体的特化类型取代
特化版本的实现可以和通用版本的实现完全不同
xxxxxxxxxx
811// special.cpp
2
3// 类模板的全类特化
4
5
6
7
8
9using namespace std;
10
11// 通用版本
12
13template<class T>
14class Comparator {
15public:
16 Comparator(T x, T y) : x(x), y(y) {}
17
18 T min(void) const {
19 return x < y ? x : y;
20 }
21
22 T max(void) const {
23 return x < y ? y : x;
24 }
25
26private:
27 T x, y;
28};
29
30// 针对char const*类型的特化版本
31
32template<>
33class Comparator<char const*> {
34public:
35 Comparator(char const* x, char const* y) : x(x), y(y) {}
36
37 char const* min(void) const {
38 return strcmp(x, y) < 0 ? x : y;
39 }
40
41 char const* max(void) const {
42 return strcmp(x, y) < 0 ? y : x;
43 }
44
45private:
46 char const *x, *y;
47};
48
49// 使用类模板
50
51int main (void) {
52 cout << "Enter two integer: " << flush;
53 int nx, ny;
54 cin >> nx >> ny;
55 Comparator<int> cn(nx, ny);
56 cout << "min=" << cn.min() << endl;
57 cout << "max=" << cn.max() << endl;
58
59 cout << "Enter two double: " << flush;
60 double dx, dy;
61 cin >> dx >> dy;
62 Comparator<double> cd(dx, dy);
63 cout << "min=" << cd.min() << endl;
64 cout << "max=" << cd.max() << endl;
65
66 cout << "Enter two string: " << flush;
67 string sx, sy;
68 cin >> sx >> sy;
69 Comparator<string> cs(sx, sy);
70 cout << "min=" << cs.min() << endl;
71 cout << "max=" << cs.max() << endl;
72
73 cout << "Enter two string: " << flush;
74 char cx[256], cy[256];
75 cin >> cx >> cy;
76 Comparator<char const*> cc(cx, cy);
77 cout << "min=" << cc.min() << endl;
78 cout << "max=" << cc.max() << endl;
79
80 return 0;
81}
类模板除了可以整体进行特化以外,也可以只针对部分成员函数进行特化。对一个类模板做成员特化,特化实现与通用实现共享同一份声明,因此特化版本与通用版本的函数原型,除模板参数以外,必须严格一致。
xxxxxxxxxx
741// member.cpp
2
3// 类模板的成员特化
4
5
6
7
8
9using namespace std;
10
11// 通用版本
12
13template<class T>
14class Comparator {
15public:
16 Comparator(T x, T y) : x(x), y(y) {}
17
18 T min(void) const {
19 return x < y ? x : y;
20 }
21
22 T max(void) const {
23 return x < y ? y : x;
24 }
25
26private:
27 T x, y;
28};
29
30// 针对char const*类型的特化版本
31
32template<>
33char const* Comparator<char const*>::min(void) const {
34 return strcmp(x, y) < 0 ? x : y;
35}
36
37template<>
38char const* Comparator<char const*>::max(void) const {
39 return strcmp(x, y) < 0 ? y : x;
40}
41
42// 使用类模板
43
44int main (void) {
45 cout << "Enter two integer: " << flush;
46 int nx, ny;
47 cin >> nx >> ny;
48 Comparator<int> cn(nx, ny);
49 cout << "min=" << cn.min() << endl;
50 cout << "max=" << cn.max() << endl;
51
52 cout << "Enter two double: " << flush;
53 double dx, dy;
54 cin >> dx >> dy;
55 Comparator<double> cd(dx, dy);
56 cout << "min=" << cd.min() << endl;
57 cout << "max=" << cd.max() << endl;
58
59 cout << "Enter two string: " << flush;
60 string sx, sy;
61 cin >> sx >> sy;
62 Comparator<string> cs(sx, sy);
63 cout << "min=" << cs.min() << endl;
64 cout << "max=" << cs.max() << endl;
65
66 cout << "Enter two string: " << flush;
67 char cx[256], cy[256];
68 cin >> cx >> cy;
69 Comparator<char const*> cc(cx, cy);
70 cout << "min=" << cc.min() << endl;
71 cout << "max=" << cc.max() << endl;
72
73 return 0;
74}
类模板可以被局部特化,即一方面为类模板指定特定的实现,另一方面又允许用户对部分模板参数自行指定。
xxxxxxxxxx
671// partial.cpp
2
3// 类模板的局部特化
4
5
6
7using namespace std;
8
9// 通用版本
10
11template<typename T1, typename T2>
12class A {
13public:
14 static void foo(void) {
15 cout << "A<T1,T2>" << endl;
16 }
17};
18
19// 针对第二个类型参数取short的局部特化版本
20
21template<typename T>
22class A<T, short> {
23public:
24 static void foo(void) {
25 cout << "A<T,short>" << endl;
26 }
27};
28
29// 针对两个类型参数取相同类型的局部特化版本
30
31template<typename T>
32class A<T, T> {
33public:
34 static void foo(void) {
35 cout << "A<T,T>" << endl;
36 }
37};
38
39// 针对两个类型参数取指针的局部特化版本
40
41template<typename T1, typename T2>
42class A<T1*, T2*> {
43public:
44 static void foo(void) {
45 cout << "A<T1*,T2*>" << endl;
46 }
47};
48
49// 针对两个类型参数取引用的局部特化版本
50
51template<typename T1, typename T2>
52class A<T1&, T2&> {
53public:
54 static void foo(void) {
55 cout << "A<T1&,T2&>" << endl;
56 }
57};
58
59int main(void) {
60 A<int, char>::foo();
61 A<int, short>::foo();
62 A<int, int>::foo();
63 A<int*, char*>::foo();
64 A<int&, char&>::foo();
65
66 return 0;
67}
如果多个局部特化同等程度地匹配某个声明,那么该声明将因二义性而导致歧义错误:
xxxxxxxxxx
191// 针对两个类型参数取相同类型的局部特化版本
2
3template<typename T>
4class A<T, T> {
5public:
6 static void foo(void) {
7 cout << "A<T,T>" << endl;
8 }
9};
10
11// 针对两个类型参数取指针的局部特化版本
12
13template<typename T1, typename T2>
14class A<T1*, T2*> {
15public:
16 static void foo(void) {
17 cout << "A<T1*,T2*>" << endl;
18 }
19};
xxxxxxxxxx
11A<int*, int*>::foo(); // 编译错误,在选择A<T,T>和A<T1*,T2*>时发生歧义
除非有更好的匹配:
xxxxxxxxxx
91// 针对两个类型参数取相同类型的指针的局部特化版本
2
3template<typename T>
4class A<T*, T*> {
5public:
6 static void foo(void) {
7 cout << "A<T*,T*>" << endl;
8 }
9};
类模板的模板参数可以带有缺省值,即缺省模板实参。实例化类模板时,如果提供了模板实参则用所提供的模板实参传递给相应的模板形参,如果没有提供模板实参则相应的模板形参取缺省值。
xxxxxxxxxx
271// clsdefargs.cpp
2
3// 类模板参数的缺省值
4
5
6
7
8using namespace std;
9
10template<typename A = char, typename B = short, typename C = int>
11class X {
12public:
13 static void foo(void) {
14 cout << typeid(A).name() << ' ' <<
15 typeid(B).name() << ' ' <<
16 typeid(C).name() << endl;
17 }
18};
19
20int main(void) {
21 X<long, float, double>::foo();
22 X<long, float>::foo();
23 X<long>::foo();
24 X<>::foo();
25
26 return 0;
27}
如果类模板的某个模板参数带有缺省值,那么它后面的所有模板参数必须都带有缺省值。例如:
xxxxxxxxxx
11template<typename A = char, typename B = short, typename C = int> class X { ... };
xxxxxxxxxx
11template<typename A, typename B = short, typename C = int> class X { ... };
xxxxxxxxxx
11template<typename A, typename B, typename C = int> class X { ... };
以下情况都是不允许出现的:
前面的参数带有缺省值,后面的参数不带缺省值
xxxxxxxxxx
11template<typename A = char, typename B = short, typename C> class X { ... }; // 错误
中间的参数带有缺省值,两边的参数不带缺省值
xxxxxxxxxx
11template<typename A, typename B = short, typename C> class X { ... }; // 错误
两边的参数带有缺省值,中间的参数不带缺省值
xxxxxxxxxx
11template<typename A = char, typename B, typename C = int> class X { ... }; // 错误
类模板后面参数的缺省值可以引用前面参数的值。例如:
xxxxxxxxxx
11template<typename A, typename B = A*> class X { ... };
X<int>
即等价于X<int, int*>
。
模板的参数并不局限于类型参数,普通数值也可以作为模板的参数,前面不要写typename,而要写具体类型。
xxxxxxxxxx
531// clsargs.cpp
2
3// 类模板的非类型模板参数
4
5
6
7using namespace std;
8
9template<typename T, size_t C = 3>
10class Array {
11public:
12 T& operator[](size_t i) {
13 return arr[i];
14 }
15
16 T const& operator[](size_t i) const {
17 return const_cast<Array&>(*this)[i];
18 }
19
20private:
21 T arr[C];
22};
23
24template<typename T, size_t S>
25ostream& operator<<(ostream& os, Array<T, S> const& array) {
26 for (size_t i = 0; i < S; ++i)
27 os << '(' << array[i] << ')';
28
29 return os;
30}
31
32int main(void) {
33 Array<int, 3> ai;
34 ai[0] = 100;
35 ai[1] = 200;
36 ai[2] = 300;
37 cout << ai << endl;
38
39 Array<string> as;
40 as[0] = "北京";
41 as[1] = "上海";
42 as[2] = "广州";
43 cout << as << endl;
44
45 size_t const rows = 4, cols = 5;
46 Array<Array<int, cols>, rows> aai;
47 for (size_t i = 0; i < rows; ++i)
48 for (size_t j = 0; j < cols; ++j)
49 aai[i][j] = (i+1)*10+j+1;
50 cout << aai << endl;
51
52 return 0;
53}
类模板的非类型参数与类型参数一样,也可以带有缺省值。
与类模板一样,函数模板也可以带有非类型参数。
xxxxxxxxxx
331// funcargs.cpp
2
3// 函数模板的非类型模板参数
4
5
6
7using namespace std;
8
9template<typename T, size_t TIMES> // 将重复次数定义为模板参数
10void repeat(T const& t) {
11 for (size_t i = 0; i < TIMES; ++i)
12 cout << t << ' ';
13 cout << endl;
14}
15
16template<typename IT, typename OP>
17void for_each(IT begin, IT end, OP op) {
18 while (begin != end)
19 op(*begin++);
20}
21
22int main(void) {
23 repeat<int, 5>(0);
24 repeat<char, 5>('A');
25 repeat<string, 5>("ABC");
26
27 int ai[] = {1, 2, 3, 4, 5};
28 for_each(ai, ai + 5, (void (*)(int const&))repeat<int, 5>);
29 // 函数模板的实例化相当于一组重载函数的集合,
30 // 只有在转换为具体类型后,才能充当模板实参
31
32 return 0;
33}
函数模板的非类型参数与类型参数一样,也不可以带有缺省值。
非类型模板参数只能是常量、常量表达式,以及带有常属性(const)的变量,但不能同时具有挥发性(volatile)
非类型模板参数只能是整数(signed/unsigned char/short/int/long),枚举和指向外部变量的指针
非类型模板参数不能是浮点类型(float/double)或类类型
非类型模板参数不能是指向内部变量的指针,如字符串字面值、全局指针等
xxxxxxxxxx
441// limit.cpp
2
3// 非类型模板参数的限制
4
5
6
7using namespace std;
8
9template<char c>
10void foo(void) {
11 cout << c << endl;
12}
13
14// template<double d>
15// void bar(void) {
16// cout << d << endl;
17// }
18// 编译错误,模板的非类型参数不能是浮点类型
19
20// template<string s>
21// void hum(void) {
22// cout << s << endl;
23// }
24// 编译错误,模板的非类型参数不能是类类型
25
26template<char const* cp>
27void fun(void) {
28 cout << cp << endl;
29}
30
31char const* cp = "北京";
32static char sca[] = "天津";
33char ca[] = "上海";
34
35int main (void) {
36 foo<'A'>();
37
38 // fun<"重庆">(); // 编译错误,模板的非类型实参不能是字符串字面值
39 // fun<cp>(); // 编译错误,模板的非类型实参不能是全局指针
40 fun<sca>();
41 fun<ca>();
42
43 return 0;
44}
声明模板参数:template<typename T> ...,只有在这种语境下,typename关键字才可以和class关键字互换
class关键字
声明类:class A { ... };
声明模板参数:template<class T> ...
typename关键字
声明模板参数:template<typename T> ...
解决嵌套依赖:typename T::A a;
无论是声明模板参数还是解决嵌套依赖,都不能使用struct关键字
xxxxxxxxxx
71class X {
2public:
3 // A是X的嵌套类型
4 class A {
5 ...
6 };
7};
xxxxxxxxxx
41template<typename T> // T<-X
2void foo(void) {
3 ... T::A a ... // 编译错误
4}
xxxxxxxxxx
11foo<X>(); // X->T
在第一次编译模板代码时,模板参数的具体类型尚不明确,编译器会把依赖于模板参数的嵌套类型理解为某个类的静态成员变量。因此当它看到代码中使用这样的标识符声明其它变量时,会报告错误,这就叫做嵌套依赖。
xxxxxxxxxx
41template<typename T> // T<-X
2void foo(void) {
3 ... typename T::A a ... // 编译通过
4}
typename关键字旨在告诉编译器,所引用的标识符是个类型名,可以声明变量,具体类型等到实例化时再定。
xxxxxxxxxx
281// typename.cpp
2
3// 借助typename关键字解决嵌套依赖
4
5
6
7
8using namespace std;
9
10class X {
11public:
12 // A是X的嵌套类型
13 class A {
14 };
15};
16
17template<typename T> // T<-x
18void foo(void) {
19 // T::A a; // 编译错误
20 typename T::A a; // 编译通过
21 cout << typeid(a).name() << endl;
22}
23
24int main(void) {
25 foo<X>(); // X->T
26
27 return 0;
28}
template关键字的典型用法是声明模板
函数模板
xxxxxxxxxx
11template<typename T> void foo(void) { ... T ... }
类模板
xxxxxxxxxx
11template<typename T> class X { ... T ...};
xxxxxxxxxx
51class A {
2public:
3 template<typename T>
4 void foo(void) const { ... T ... }
5};
xxxxxxxxxx
51template<typename T>
2void bar(T const& r, T const* p) {
3 r.foo<int>(); // 编译错误
4 p->foo<double>(); // 编译错误
5}
xxxxxxxxxx
21A a, *p = &a;
2bar(a, p);
在模板代码中,通过依赖于模板参数的对象、引用或指针,访问其带有模板特性的成员,编译器常常因为无法正确理解模板参数列表的左右尖括号,而报告编译错误。在模板名前面加上template关键字,意在告诉编译器其后的名称是一个模板,编译器就可以正确理解“<>”了。
xxxxxxxxxx
51template<typename T>
2void bar(T const& r, T const* p) {
3 r.template foo<int>();
4 p->template foo<double>();
5}
template关键字旨在告诉编译器,所引用的标识符是个模板,其后尖括号括起的是传递给该模板的模板参数。
xxxxxxxxxx
321// template.cpp
2
3// 依赖模板参数的模板型成员访问
4
5
6
7
8using namespace std;
9
10class A {
11public:
12 template<typename T>
13 void foo(void) const {
14 cout << typeid(T).name() << endl;
15 }
16};
17
18template<typename T>
19void bar(T const& r, T const* p) {
20 // r.foo<int>(); // 编译错误
21 // p->foo<double>(); // 编译错误
22 r.template foo<int>();
23 p->template foo<double>();
24}
25
26int main (void) {
27 A a, *p = &a;
28
29 bar(a, p);
30
31 return 0;
32}
xxxxxxxxxx
151template<typename T>
2class Base {
3public:
4 class Nested {};
5
6 void foo(void) {
7 ++t;
8 }
9
10 void exit(int status) const {
11 cout << "Base::exit(" << status << ")" << endl;
12 }
13
14 T t;
15};
xxxxxxxxxx
101template<typename T>
2class Derived : public Base<T> {
3public:
4 void bar(void) {
5 Nested nested; // 编译错误
6 foo(); // 编译错误
7 cout << t << endl; // 编译错误
8 exit(0); // 所调用并非基类的exit成员函数
9 }
10};
在子类模板中直接访问那些依赖于模板参数的基类模板的成员,编译器在第一次编译时,通常会因为基类类型不明确而只在子类和全局作用域中搜索所引用的符号。在子类模板中可以通过作用域限定符,或者显式使用this指针,迫使编译器到基类作用域中搜索所引用的符号。
xxxxxxxxxx
551// this.cpp
2
3// 在子模板中访问基模板
4
5
6
7using namespace std;
8
9template<typename T>
10class Base {
11public:
12 class Nested {};
13
14 void foo(void) {
15 ++t;
16 }
17
18 void exit(int status) const {
19 cout << "Base::exit(" << status << ")" << endl;
20 }
21
22 T t;
23};
24
25template<typename T>
26class Derived : public Base<T> {
27public:
28 void bar(void) {
29 // Nested nested; // 编译错误
30 // foo(); // 编译错误
31 // cout << t << endl; // 编译错误
32 // exit(0); // 所调用并非基类的exit成员函数
33
34 // 在子模板中访问基模板的成员,需要使用作用域限定符
35 typename Base<T>::Nested nested;
36 Base<T>::foo();
37 cout << Base<T>::t << endl;
38
39 // 也可以借助this指针访问基模板的成员函数和成员变量
40 this->foo();
41 cout << this->t << endl;
42
43 // 调用基模板的exit函数
44 Base<T>::exit(0);
45 this->exit(0);
46 }
47};
48
49int main(void) {
50 Derived<int> d;
51
52 d.bar();
53
54 return 0;
55}
类模板的成员变量,如果其类型又源自一个类模板的实例化类,那么它就是一个模板型模板成员变量。
xxxxxxxxxx
301// membvar.cpp
2
3// 类模板的模板型成员变量
4
5
6
7
8using namespace std;
9
10template<typename T>
11class A {
12public:
13 void foo(void) const {
14 cout << typeid(T).name() << endl;
15 }
16};
17
18template<typename T>
19class B {
20public:
21 A<T> a; // 模板型成员变量
22};
23
24int main(void) {
25 B<int> b;
26
27 b.a.foo();
28
29 return 0;
30}
类模板的成员函数,如果除了类模板的模板参数外,还需要其它模板参数,那么它就是一个模板型模板成员函数。
xxxxxxxxxx
341// membfun.cpp
2
3// 类模板的模板型成员函数
4
5
6
7
8using namespace std;
9
10template<typename U>
11class A {
12public:
13 template<typename V>
14 void foo(void) const {
15 cout << typeid(U).name() << ' ' << typeid(V).name() << endl;
16 } // 模板型成员函数
17
18 template<typename V> void bar(void) const; // 模板型成员函数
19};
20
21template<typename U>
22 template<typename V>
23void A<U>::bar(void) const {
24 cout << typeid(U).name() << ' ' << typeid(V).name() << endl;
25}
26
27int main(void) {
28 A<char> a;
29
30 a.foo<short>();
31 a.bar<int>();
32
33 return 0;
34}
类模板的成员类型,如果除了类模板的模板参数外,还需要其它模板参数,那么它就是一个模板型模板成员类型。
xxxxxxxxxx
411// membcls.cpp
2
3// 类模板的模板型成员类型
4
5
6
7
8using namespace std;
9
10template<typename U>
11class A {
12public:
13 template<typename V>
14 class B {
15 public:
16 void foo(void) const {
17 cout << typeid(U).name() << ' ' << typeid(V).name() << endl;
18 }
19 }; // 模板型成员类型
20
21 template<typename V> class C; // 模板型成员类型
22};
23
24template<typename U>
25 template<typename V>
26class A<U>::C {
27public:
28 void foo(void) const {
29 cout << typeid(U).name() << ' ' << typeid(V).name() << endl;
30 }
31};
32
33int main(void) {
34 A<char>::B<short> b;
35 b.foo();
36
37 A<int>::C<long> c;
38 c.foo();
39
40 return 0;
41}
函数模板和类模板的模板型模板参数:
函数模板和类模板的模板参数本身又可以是个模板,但注意该模板参数的声明,不能使用typename关键字,而要用template<...> class结构
可以使用任何类型,甚至其它模板参数,实例化模板型模板参数
模板型模板参数的模板参数名,若未被引用,则可以省略
模板型模板参数的模板参数也可以带有缺省值
xxxxxxxxxx
441// tmplargs.cpp
2
3// 函数模板和类模板的模板型模板参数
4
5
6
7
8using namespace std;
9
10template<typename T>
11class A {
12public:
13 void foo(void) const {
14 cout << typeid(T).name() << endl;
15 }
16};
17
18// 带有模板型模板参数的函数模板
19
20template<typename U, template<typename> class V = A>
21void foo(void) {
22 V<short> v;
23 v.foo();
24}
25
26// 带有模板型模板参数的类模板
27
28template<typename U, template<typename> class V = A>
29class B {
30public:
31 void foo(void) const {
32 V<U> v;
33 v.foo();
34 }
35};
36
37int main(void) {
38 foo<char>();
39
40 B<int, A> b;
41 b.foo();
42
43 return 0;
44}
函数模板和类模板的模板参数本身又可以是个模板。
xxxxxxxxxx
11template<typename U, template<typename> class V> void foo(void) { ... }
xxxxxxxxxx
11template<typename U, template<typename> class V> class B { ... };
可以使用任何类型,甚至其它模板参数,实例化模板型模板参数。
xxxxxxxxxx
11template<typename T> class A { ... };
xxxxxxxxxx
11template<typename U, template<typename> class V> void foo(void) { ... V<short> ... }
xxxxxxxxxx
11template<typename U, template<typename> class V> class B { ... V<U> ... };
xxxxxxxxxx
11foo<char, A>();
xxxxxxxxxx
11B<int, A> b;
模板型模板参数的模板参数也可以带有缺省值。
xxxxxxxxxx
11template<typename T> class A { ... };
xxxxxxxxxx
11template<typename U, template<typename> class V = A> void foo(void) { ... V<short> ... }
xxxxxxxxxx
11template<typename U, template<typename> class V = A> class B { ... V<U> ... };
xxxxxxxxxx
11foo<char>();
xxxxxxxxxx
11B<int> b;
xxxxxxxxxx
451// init.cpp
2
3// 零初始化
4
5
6
7using namespace std;
8
9template<typename T>
10void foo(void) {
11 T var; // 若T为基本类型,则var的值未定义
12 cout << var << endl;
13}
14
15template<typename T>
16class A {
17public:
18 T var; // 若T为基本类型,则var的值未定义
19};
20
21template<typename T>
22void bar(void) {
23 T var = T(); // 无论T为类类型还是基本类型,var都以缺省方式初始化
24 cout << var << endl;
25}
26
27template<typename T>
28class B {
29public:
30 B(void) : var() {} // 无论T为类类型还是基本类型,var都以缺省方式初始化
31
32 T var;
33};
34
35int main(void) {
36 foo<int>();
37 A<int> a;
38 cout << a.var << endl;
39
40 bar<int>();
41 B<int> b;
42 cout << b.var << endl;
43
44 return 0;
45}
基本类型的局部变量,如果没有被显式初始化,其初值通常是不确定的:
xxxxxxxxxx
41void foo(void) {
2 int var; // 初值不确定
3 ...
4}
类类型的局部变量,如果没有被显式初始化,其初值通常由该类的缺省构造函数决定:
xxxxxxxxxx
31void foo(void) {
2 Student var; // 初值由缺省构造函数决定
3}
这样就会在包含模板的代码中,表现出某种程度的不一致:
xxxxxxxxxx
41template<typename T> void foo(void) {
2 T var; // 当T是基本类型时,初值不确定,而当T是类类型时,初值由该类的缺省构造函数决定
3 ...
4}
xxxxxxxxxx
11foo<int>(); // int->T
xxxxxxxxxx
11foo<Student>(); // Student->T
如果希望模板中所有参数化类型的变量,无论是类类型还是基本类型,都能以缺省方式被初始化,就必须对其进行显式地缺省构造:
xxxxxxxxxx
41template<typename T> void foo(void) {
2 T var = T(); // 显式缺省构造
3 ...
4}
当用int实例化该函数模板时:
xxxxxxxxxx
41void foo<int>(void) {
2 int var = int(); // 用int类型的零值初始化
3 ...
4}
当用Student实例化该函数模板时:
xxxxxxxxxx
41void foo<Student>(void) {
2 Student var = Student(); // 用Student类的缺省构造函数初始化
3 ...
4}
对类型一致性的追求,始终是模板设计的核心准则之一。
对于类模板,可在其缺省构造函数的初始化表中,显式初始化各个成员变量,无论是类类型的还是基本类型的:
xxxxxxxxxx
81template<typename T> class A {
2public:
3 A(void) : var() {}
4 ...
5private:
6 T var;
7 ...
8};
当用int实例化该类模板时:
xxxxxxxxxx
81class A<int> {
2public:
3 A(void) : var() {} // 用int类型的零值初始化
4 ...
5private:
6 int var;
7 ...
8};
当用Student实例化该类模板时:
xxxxxxxxxx
81class A<Student> {
2public:
3 A(void) : var() {} // 用Student类的缺省构造函数初始化
4 ...
5private:
6 Student var;
7 ...
8};
类型一致性的设计准则,同样得到了完美的体现。
将字符串传递给带有引用型参数和非引用型参数的函数模板,将导致完全不同的结果。函数模板的隐式推断会把引用型参数推断为对整个字符数组的引用,而非引用型参数则仅仅以字符指针的形式接收字符串的首地址。
对同一个函数模板,同时声明引用和非引用两个版本可能会引发歧义。而只使用非引用的版本又会导致无谓的拷贝。如果不想强迫用户通过显式类型转换或者显式实例化,完全用C++的string取代C风格的字符串,那么为非引用版本专门提供一个针对字符指针的重载总是有效的。
xxxxxxxxxx
501// string.cpp
2
3// 字符串型函数模板实参
4
5
6
7
8
9
10using namespace std;
11
12// 接收引用型参数的函数模板
13
14template<typename T>
15void foo(T const& t) {
16 // t是实参字符数组的引用
17 cout << typeid (t).name () << ": " << t << endl;
18}
19
20// 接收非引用型参数的函数模板
21
22template<typename T>
23void bar (T t) {
24 // t是实参字符指针的副本
25 cout << typeid (t).name () << ": " << t << endl;
26}
27
28// 通用版本,借助引用避免拷贝
29
30template<typename T>
31T const& min(T const& x, T const& y) {
32 return x < y ? x : y;
33}
34
35// 特殊版本,仅针对字符指针指向的空结尾字符串,即C风
36// 格字符串。既能避免编译错误,又能以正确方式实现比较
37
38const char* min(const char* x, const char* y) {
39 return strcmp(x, y) < 0 ? x : y;
40}
41
42int main (void) {
43 foo("string is a character array");
44 bar("string is a character pointer");
45
46 cout << ::min("hello", "world") << endl;
47 cout << ::min("hello", "tarena") << endl;
48
49 return 0;
50}
类模板的普通成员函数可以是虚函数,即可以为类模板定义虚成员函数。和普通类的虚成员函数一样,类模板的虚成员函数亦可表现出多态性:
xxxxxxxxxx
51template<typename PEN> class Rect {
2 ...
3 virtual void draw(PEN const& pen) { ... }
4 ...
5};
xxxxxxxxxx
41template<typename PEN> class RoundRect : public Rect<PEN> {
2 ...
3 void draw(PEN const& pen) { ... }
4};
xxxxxxxxxx
21Rect<SolidPen>* rect = new RoundRect<SolidPen>(...);
2rect->draw(SolidPen(...));
无论是类还是类模板,其虚成员函数都不能是模板函数。基于虚函数的多态机制,需要一个名为虚函数表的函数指针数组。该数组在类被编译或类模板被实例化的过程中产生,而此时那些模板形式的成员函数尚未被实例化,其入口地址和重载版本的个数,要等到编译器处理完对该函数的所有调用以后才能确定。成员函数模板的延迟编译阻碍了虚函数表的静态构建。
xxxxxxxxxx
51template<typename PEN> class Rect {
2 ....
3 template<typename BRUSH>
4 virtual void draw(PEN const& pen, BRUSH const& brush) { ... } // 编译错误
5};
xxxxxxxxxx
31template<...> class A {
2 virtual void foo(T arg) { ... }
3}
xxxxxxxxxx
31template<...> class B : public A<...> {
2 void foo(T arg) const { ... }
3}
只有当类模板B中的T,与类模板A中的T,取相同类型时,B中的foo函数才是虚函数,并对A中的foo函数构成覆盖。
xxxxxxxxxx
541// virtual.cpp
2
3// 类模板的虚成员函数
4
5
6
7using namespace std;
8
9template<typename U>
10class A {
11public:
12 // 类模板的所有非模板型成员函数(包括析构函数)都可以是虚函数
13
14 virtual ~A(void) {}
15
16 virtual void foo(U const& u) const {
17 cout << "A::foo(" << u << ") invoked" << endl;
18 }
19
20 // 类模板的所有模板型成员函数(包括析构函数)都不能是虚函数
21
22 // template<typename V>
23 // virtual void bar(V const& v) {
24 // cout << "A::bar(" << v << ") invoked" << endl;
25 // }
26};
27
28template<typename U>
29class B : public A<U> {
30public:
31 void foo(U const& u) const {
32 cout << "B::foo(" << u << ") invoked" << endl;
33 }
34};
35
36template<typename U, typename V>
37class C : public A<V> {
38public:
39 void foo(U const& u) const {
40 cout << "C::foo(" << u << ") invoked" << endl;
41 }
42};
43
44int main (void) {
45 A<int>* a = new B<int>;
46 a->foo(123); // 多态
47 delete a;
48
49 a = new C<short, int>;
50 a->foo(456); // 非多态
51 delete a;
52
53 return 0;
54}
将模板的声明、定义和实例化放在单一的编译单元中,无论编译还是链接,其结果总是对的。
xxxxxxxxxx
891// comparator1.cpp
2
3// 单一模型
4
5
6
7
8
9using namespace std;
10
11// 在声明模板的同时给出定义
12
13template<class T>
14T min(T x, T y) {
15 return x < y ? x : y;
16}
17
18template<typename T>
19T max(T x, T y) {
20 return x < y ? y : x;
21}
22
23char const* min(char const* x, char const* y) {
24 return strcmp(x, y) < 0 ? x : y;
25}
26
27char const* max(char const* x, char const* y) {
28 return strcmp(x, y) < 0 ? y : x;
29}
30
31template<class T>
32class Comparator {
33public:
34 Comparator(T x, T y) : x(x), y(y) {}
35
36 T min(void) const {
37 return x < y ? x : y;
38 }
39
40 T max(void) const {
41 return x < y ? y : x;
42 }
43
44private:
45 T x, y;
46};
47
48template<>
49class Comparator<char const*> {
50public:
51 Comparator(char const* x, char const* y) : x(x), y(y) {}
52
53 char const* min(void) const {
54 return strcmp(x, y) < 0 ? x : y;
55 }
56
57 char const* max(void) const {
58 return strcmp(x, y) < 0 ? y : x;
59 }
60
61private:
62 char const *x, *y;
63};
64
65// 根据特定的模板实参对模板进行实例化
66
67int main(void) {
68 int nx = 123, ny = 456;
69 double dx = 1.23, dy = 4.56;
70 string sx = "hello", sy = "world";
71 char const *cx = "hello", *cy = "world";
72
73 cout << ::min(nx, ny) << ' ' << ::max(nx, ny) << endl;
74 cout << ::min(dx, dy) << ' ' << ::max(dx, dy) << endl;
75 cout << ::min(sx, sy) << ' ' << ::max(sx, sy) << endl;
76 cout << ::min(cx, cy) << ' ' << ::max(cx, cy) << endl;
77
78 Comparator<int> cn(nx, ny);
79 Comparator<double> cd(dx, dy);
80 Comparator<string> cs(sx, sy);
81 Comparator<char const*> cc(cx, cy);
82
83 cout << cn.min() << ' ' << cn.max() << endl;
84 cout << cd.min() << ' ' << cd.max() << endl;
85 cout << cs.min() << ' ' << cs.max() << endl;
86 cout << cc.min() << ' ' << cc.max() << endl;
87
88 return 0;
89}
大多数C/C++程序员会这样组织他们的代码:
把公共头文件包含和宏定义,放在头文件(.h)中
把类、结构、联合、枚举、类型别名等,放在头文件(.h)中
把全局变量和全局函数的外部声明,放在头文件(.h)中
把全局变量、全局函数和成员函数的定义,放在源文件(.c或.cpp)中
这样做既能保证所有的类型信息,在整个项目被编译的过程中都可见,避免编译器报告“未声明”错误,同时也不会因为变量和函数定义的重复出现,导致链接器报告“重定义”错误。
xxxxxxxxxx
301// comparator2.h
2
3// 分离模型
4
5
6
7// 在头文件中仅给出模板的声明
8
9template<class T>
10T min(T x, T y);
11
12template<typename T>
13T max(T x, T y);
14
15char const* min(char const* x, char const* y);
16
17char const* max(char const* x, char const* y);
18
19template<class T>
20class Comparator {
21public:
22 Comparator(T x, T y);
23
24 T min(void) const;
25
26 T max(void) const;
27
28private:
29 T x, y;
30};
模板的定义只是一种规范性的描述,并非真正意义上的类型定义。当编译器看到模板定义时,仅做一般性的语法检查,同时生成一份模板的内部表示,并不生成指令代码。只有当编译器看到模板被实例化为具体函数或具体类时,才会用具体的模板实参,结合之前生成的内部表示,产生二进制指令代码。这个过程被称为模板的后期编译。
xxxxxxxxxx
521// comparator2.cpp
2
3// 分离模型
4
5
6
7
8
9// 在源文件中给出模板的定义
10
11// 在编译此文件时,编译器看不到任何模板实参,因此不会对所定义的模板进行实例化
12
13template<class T>
14T min(T x, T y) {
15 return x < y ? x : y;
16}
17
18template<typename T>
19T max(T x, T y) {
20 return x < y ? y : x;
21}
22
23char const* min(char const* x, char const* y) {
24 return strcmp(x, y) < 0 ? x : y;
25}
26
27char const* max(char const* x, char const* y) {
28 return strcmp(x, y) < 0 ? y : x;
29}
30
31template<class T>
32Comparator<T>::Comparator(T x, T y) : x(x), y(y) {}
33
34template<class T>
35T Comparator<T>::min(void) const {
36 return x < y ? x : y;
37}
38
39template<class T>
40T Comparator<T>::max(void) const {
41 return x < y ? y : x;
42}
43
44template<>
45char const* Comparator<char const*>::min(void) const {
46 return strcmp(x, y) < 0 ? x : y;
47}
48
49template<>
50char const* Comparator<char const*>::max(void) const {
51 return strcmp(x, y) < 0 ? y : x;
52}
每个C或C++源文件都是被单独编译的。编译器在编译模板定义文件时所生成的模板内部表示,此刻早已荡然无存。因此所有基于模板实例的类和函数,编译器只能假设它们被定义在其它模块中,并产生一个指向该(不存在的)定义的引用,期待链接器能在日后解决此问题。但事实上,由于模板的内部表示并没有真正化身为可执行的指令代码,链接器最终报告“未定义的引用”错误。
xxxxxxxxxx
381// main2.cpp
2
3// 分离模型
4
5
6
7
8
9using namespace std;
10
11// 根据特定的模板实参对模板进行实例化
12
13// 编译此文件时,编译器虽然可以看到传递给模板的实参,但却看不到模板的定
14// 义,因此也不会对模板进实例化。模板始终得不到实例化,最终导致链接错误
15
16int main(void) {
17 int nx = 123, ny = 456;
18 double dx = 1.23, dy = 4.56;
19 string sx = "hello", sy = "world";
20 char const *cx = "hello", *cy = "world";
21
22 cout << ::min(nx, ny) << ' ' << ::max(nx, ny) << endl;
23 cout << ::min(dx, dy) << ' ' << ::max(dx, dy) << endl;
24 cout << ::min(sx, sy) << ' ' << ::max(sx, sy) << endl;
25 cout << ::min(cx, cy) << ' ' << ::max(cx, cy) << endl;
26
27 Comparator<int> cn(nx, ny);
28 Comparator<double> cd(dx, dy);
29 Comparator<string> cs(sx, sy);
30 Comparator<char const*> cc(cx, cy);
31
32 cout << cn.min() << ' ' << cn.max() << endl;
33 cout << cd.min() << ' ' << cd.max() << endl;
34 cout << cs.min() << ' ' << cs.max() << endl;
35 cout << cc.min() << ' ' << cc.max() << endl;
36
37 return 0;
38}
由此可见,分离模型显然并不适合于包含模板的项目代码。
把模板定义文件包含在模板声明文件的内部(通常位于声明之后),即让模板的声明和定义同处于一个头文件中。
xxxxxxxxxx
371// comparator3.h
2
3// 包含模型
4
5
6
7// 在头文件中仅给出模板的声明
8
9template<class T>
10T min(T x, T y);
11
12template<typename T>
13T max(T x, T y);
14
15char const* min(char const* x, char const* y);
16
17char const* max(char const* x, char const* y);
18
19template<class T>
20class Comparator {
21public:
22 Comparator(T x, T y);
23
24 T min(void) const;
25
26 T max(void) const;
27
28private:
29 T x, y;
30};
31
32// 在声明模板的头文件中包含定义模板的源文件
33
34// 任何需要获得模板声明的程序必包含此头文件,
35// 与此同时,自然也包含了定义该模板的源文件
36
37
xxxxxxxxxx
511// comparator3.cpp
2
3// 包含模型
4
5
6
7// 在源文件中给出模板的定义
8
9// 此文件不单独参加编译,以包含文件的形式内嵌于此模板的
10// 声明文件中,与包含该声明文件的实例化程序一起参加编译
11
12template<class T>
13T min(T x, T y) {
14 return x < y ? x : y;
15}
16
17template<typename T>
18T max(T x, T y) {
19 return x < y ? y : x;
20}
21
22char const* min(char const* x, char const* y) {
23 return strcmp(x, y) < 0 ? x : y;
24}
25
26char const* max(char const* x, char const* y) {
27 return strcmp(x, y) < 0 ? y : x;
28}
29
30template<class T>
31Comparator<T>::Comparator(T x, T y) : x(x), y(y) {}
32
33template<class T>
34T Comparator<T>::min(void) const {
35 return x < y ? x : y;
36}
37
38template<class T>
39T Comparator<T>::max(void) const {
40 return x < y ? y : x;
41}
42
43template<>
44char const* Comparator<char const*>::min(void) const {
45 return strcmp(x, y) < 0 ? x : y;
46}
47
48template<>
49char const* Comparator<char const*>::max(void) const {
50 return strcmp(x, y) < 0 ? y : x;
51}
任何希望使用模板的程序都必须包含模板的声明文件,而模板定义文件亦因包含而内嵌于该声明文件,最终与模板的实例化代码同处一个编译单元。模板的声明、定义与实例化同处一个编译单元中,编译器有能力对模板进行正确地实例化,没有任何链接错误。
xxxxxxxxxx
351// main3.cpp
2
3// 包含模型
4
5
6
7// 既包含了模板的声明文件,也包含了模板的定义文件
8
9using namespace std;
10
11// 根据特定的模板实参对模板进行实例化
12
13int main(void) {
14 int nx = 123, ny = 456;
15 double dx = 1.23, dy = 4.56;
16 string sx = "hello", sy = "world";
17 char const *cx = "hello", *cy = "world";
18
19 cout << ::min(nx, ny) << ' ' << ::max(nx, ny) << endl;
20 cout << ::min(dx, dy) << ' ' << ::max(dx, dy) << endl;
21 cout << ::min(sx, sy) << ' ' << ::max(sx, sy) << endl;
22 cout << ::min(cx, cy) << ' ' << ::max(cx, cy) << endl;
23
24 Comparator<int> cn(nx, ny);
25 Comparator<double> cd(dx, dy);
26 Comparator<string> cs(sx, sy);
27 Comparator<char const*> cc(cx, cy);
28
29 cout << cn.min() << ' ' << cn.max() << endl;
30 cout << cd.min() << ' ' << cd.max() << endl;
31 cout << cs.min() << ' ' << cs.max() << endl;
32 cout << cc.min() << ' ' << cc.max() << endl;
33
34 return 0;
35}
包含模型会延长总体的编译时间,而且模板定义文件不得不连同其声明文件一起提供给模板的使用者,向用户暴露出他们不希望或者不应该了解的实现细节。
在头文件中仅给出模板的声明,不包含模板定义文件。
xxxxxxxxxx
301// comparator4.h
2
3// 实例模型
4
5
6
7// 在头文件中仅给出模板的声明
8
9template<class T>
10T min(T x, T y);
11
12template<typename T>
13T max(T x, T y);
14
15char const* min(char const* x, char const* y);
16
17char const* max(char const* x, char const* y);
18
19template<class T>
20class Comparator {
21public:
22 Comparator(T x, T y);
23
24 T min(void) const;
25
26 T max(void) const;
27
28private:
29 T x, y;
30};
在模板定义文件中使用实例化指示符,强制编译器在编译该文件时,即根据特定的模板实参对模板进行实例化。
xxxxxxxxxx
691// comparator4.cpp
2
3// 实例模型
4
5
6
7
8
9
10
11using namespace std;
12
13// 在源文件中给出模板的定义
14
15template<class T>
16T min(T x, T y) {
17 return x < y ? x : y;
18}
19
20template<typename T>
21T max(T x, T y) {
22 return x < y ? y : x;
23}
24
25char const* min(char const* x, char const* y) {
26 return strcmp(x, y) < 0 ? x : y;
27}
28
29char const* max(char const* x, char const* y) {
30 return strcmp(x, y) < 0 ? y : x;
31}
32
33template<class T>
34Comparator<T>::Comparator(T x, T y) : x(x), y(y) {}
35
36template<class T>
37T Comparator<T>::min(void) const {
38 return x < y ? x : y;
39}
40
41template<class T>
42T Comparator<T>::max(void) const {
43 return x < y ? y : x;
44}
45
46template<>
47char const* Comparator<char const*>::min(void) const {
48 return strcmp(x, y) < 0 ? x : y;
49}
50
51template<>
52char const* Comparator<char const*>::max(void) const {
53 return strcmp(x, y) < 0 ? y : x;
54}
55
56// 显式实例化
57
58template int min<int>(int x, int y);
59template double min<double>(double x, double y);
60template string min<string>(string x, string y);
61
62template int max<int>(int x, int y);
63template double max<double>(double x, double y);
64template string max<string>(string x, string y);
65
66template class Comparator<int>;
67template class Comparator<double>;
68template class Comparator<string>;
69template class Comparator<char const*>;
显式实例化指示符是由template关键字和紧接其后的实例化实体(类、全局函数、成员函数等)的声明组成。该声明是一个用实参完全替代形参之后的声明。注意,前面已经显式实例化过的成员不能再次实例化。
根据特定的模板实参对模板进行实例化的工作已在模板定义文件中完成。使用该模板时无需再行实例化,直接引用具体类型,调用具体函数即可。
xxxxxxxxxx
331// main4.cpp
2
3// 实例模型
4
5
6
7
8
9using namespace std;
10
11int main(void) {
12 int nx = 123, ny = 456;
13 double dx = 1.23, dy = 4.56;
14 string sx = "hello", sy = "world";
15 char const *cx = "hello", *cy = "world";
16
17 cout << ::min(nx, ny) << ' ' << ::max(nx, ny) << endl;
18 cout << ::min(dx, dy) << ' ' << ::max(dx, dy) << endl;
19 cout << ::min(sx, sy) << ' ' << ::max(sx, sy) << endl;
20 cout << ::min(cx, cy) << ' ' << ::max(cx, cy) << endl;
21
22 Comparator<int> cn(nx, ny);
23 Comparator<double> cd(dx, dy);
24 Comparator<string> cs(sx, sy);
25 Comparator<char const*> cc(cx, cy);
26
27 cout << cn.min() << ' ' << cn.max() << endl;
28 cout << cd.min() << ' ' << cd.max() << endl;
29 cout << cs.min() << ' ' << cs.max() << endl;
30 cout << cc.min() << ' ' << cc.max() << endl;
31
32 return 0;
33}
显式实例化的类型总是有限的,即便只考虑项目中有限类型的情况,也必须仔细跟踪每个需要实例化的类或函数,这对于大型项目而言,将成为十分繁琐的工作。
在声明头文件中给出模板的声明:
xxxxxxxxxx
301// comparator5.h
2
3// 整合包含模型和实例模型
4
5
6
7// 在头文件中给出模板的声明
8
9template<class T>
10T min(T x, T y);
11
12template<typename T>
13T max(T x, T y);
14
15char const* min(char const* x, char const* y);
16
17char const* max(char const* x, char const* y);
18
19template<class T>
20class Comparator {
21public:
22 Comparator(T x, T y);
23
24 T min(void) const;
25
26 T max(void) const;
27
28private:
29 T x, y;
30};
在定义头文件中给出模板的定义:
xxxxxxxxxx
521// comparator_def.h
2
3// 整合包含模型和实例模型
4
5
6
7
8
9using namespace std;
10
11// 在定义头文件中给出模板的定义
12
13template<class T>
14T min(T x, T y) {
15 return x < y ? x : y;
16}
17
18template<typename T>
19T max(T x, T y) {
20 return x < y ? y : x;
21}
22
23char const* min(char const* x, char const* y) {
24 return strcmp(x, y) < 0 ? x : y;
25}
26
27char const* max(char const* x, char const* y) {
28 return strcmp(x, y) < 0 ? y : x;
29}
30
31template<class T>
32Comparator<T>::Comparator(T x, T y) : x(x), y(y) {}
33
34template<class T>
35T Comparator<T>::min(void) const {
36 return x < y ? x : y;
37}
38
39template<class T>
40T Comparator<T>::max(void) const {
41 return x < y ? y : x;
42}
43
44template<>
45char const* Comparator<char const*>::min(void) const {
46 return strcmp(x, y) < 0 ? x : y;
47}
48
49template<>
50char const* Comparator<char const*>::max(void) const {
51 return strcmp(x, y) < 0 ? y : x;
52}
在源文件中对模板做显式实例化:
xxxxxxxxxx
241// comparator5.cpp
2
3// 整合包含模型和实例模型
4
5
6
7
8
9using namespace std;
10
11// 在源文件中对模板做显式实例化
12
13template int min<int>(int x, int y);
14template double min<double>(double x, double y);
15template string min<string>(string x, string y);
16
17template int max<int>(int x, int y);
18template double max<double>(double x, double y);
19template string max<string>(string x, string y);
20
21template class Comparator<int>;
22template class Comparator<double>;
23template class Comparator<string>;
24template class Comparator<char const*>;
究竟使用包含模型还是实例模型由模板的使用者决定:
xxxxxxxxxx
341// main5.cpp
2
3// 整合包含模型和实例模型
4
5
6
7// #include "comparator_def.h" // 使用包含模型
8// 使用实例模型
9
10using namespace std;
11
12int main(void) {
13 int nx = 123, ny = 456;
14 double dx = 1.23, dy = 4.56;
15 string sx = "hello", sy = "world";
16 char const *cx = "hello", *cy = "world";
17
18 cout << ::min(nx, ny) << ' ' << ::max(nx, ny) << endl;
19 cout << ::min(dx, dy) << ' ' << ::max(dx, dy) << endl;
20 cout << ::min(sx, sy) << ' ' << ::max(sx, sy) << endl;
21 cout << ::min(cx, cy) << ' ' << ::max(cx, cy) << endl;
22
23 Comparator<int> cn(nx, ny);
24 Comparator<double> cd(dx, dy);
25 Comparator<string> cs(sx, sy);
26 Comparator<char const*> cc(cx, cy);
27
28 cout << cn.min() << ' ' << cn.max() << endl;
29 cout << cd.min() << ' ' << cd.max() << endl;
30 cout << cs.min() << ' ' << cs.max() << endl;
31 cout << cc.min() << ' ' << cc.max() << endl;
32
33 return 0;
34}
通过export关键字将模板声明为导出,即可在其定义不可见的编译单元中,实例化该模板。
xxxxxxxxxx
321// comparator6.h
2
3// 导出模型
4
5
6
7// 在头文件中仅给出模板的声明
8
9// 借助export关键字将其声明为导出模板
10
11export template<class T>
12T min(T x, T y);
13
14export template<typename T>
15T max(T x, T y);
16
17char const* min(char const* x, char const* y);
18
19char const* max(char const* x, char const* y);
20
21export template<class T>
22class Comparator {
23public:
24 Comparator(T x, T y);
25
26 T min(void) const;
27
28 T max(void) const;
29
30private:
31 T x, y;
32};
在模板的定义部分不需要再使用export关键字。
xxxxxxxxxx
521// comparator6.cpp
2
3// 导出模型
4
5
6
7
8
9// 在源文件中给出模板的定义
10
11// 模板声明部分所强调的export特性,因头文件包含,而为此处的定义隐式保留
12
13template<class T>
14T min(T x, T y) {
15 return x < y ? x : y;
16}
17
18template<typename T>
19T max(T x, T y) {
20 return x < y ? y : x;
21}
22
23char const* min(char const* x, char const* y) {
24 return strcmp(x, y) < 0 ? x : y;
25}
26
27char const* max(char const* x, char const* y) {
28 return strcmp(x, y) < 0 ? y : x;
29}
30
31template<class T>
32Comparator<T>::Comparator(T x, T y) : x(x), y(y) {}
33
34template<class T>
35T Comparator<T>::min(void) const {
36 return x < y ? x : y;
37}
38
39template<class T>
40T Comparator<T>::max(void) const {
41 return x < y ? y : x;
42}
43
44template<>
45char const* Comparator<char const*>::min(void) const {
46 return strcmp(x, y) < 0 ? x : y;
47}
48
49template<>
50char const* Comparator<char const*>::max(void) const {
51 return strcmp(x, y) < 0 ? y : x;
52}
编译器对导出型模板的内部表示会有更加持久的记忆,即便该模板的实例化与它的定义不在同一个编译单元中,亦能被正确地编译,不存在任何链接错误。
xxxxxxxxxx
381// main6.cpp
2
3// 导出模型
4
5
6
7
8
9using namespace std;
10
11// 根据特定的模板实参对模板进行实例化
12
13// 此处实例化的模板被声明为导出,因此即便看不到模板的定
14// 义,编译器亦能对其进行正确地实例化,不会发生链接错误
15
16int main(void) {
17 int nx = 123, ny = 456;
18 double dx = 1.23, dy = 4.56;
19 string sx = "hello", sy = "world";
20 char const *cx = "hello", *cy = "world";
21
22 cout << ::min(nx, ny) << ' ' << ::max(nx, ny) << endl;
23 cout << ::min(dx, dy) << ' ' << ::max(dx, dy) << endl;
24 cout << ::min(sx, sy) << ' ' << ::max(sx, sy) << endl;
25 cout << ::min(cx, cy) << ' ' << ::max(cx, cy) << endl;
26
27 Comparator<int> cn(nx, ny);
28 Comparator<double> cd(dx, dy);
29 Comparator<string> cs(sx, sy);
30 Comparator<char const*> cc(cx, cy);
31
32 cout << cn.min() << ' ' << cn.max() << endl;
33 cout << cd.min() << ' ' << cd.max() << endl;
34 cout << cs.min() << ' ' << cs.max() << endl;
35 cout << cc.min() << ' ' << cc.max() << endl;
36
37 return 0;
38}
导出模型在很大程度上增加了编译器的实现难度。截至目前,真正支持export关键字的编译器少之又少(仅Comeau C/C++和Intel 7.x编译器支持),GNU和Microsoft的C++编译器都不支持,而且在C++11以后的标准中,此特性已被废除,export关键字也被移作他用。
截至目前,包含模型仍然是使用模板的主流策略。每个含有实例化模板代码的源文件,都需要同时包含模板的声明和定义,这部分内容会被重复编译多次,这将导致整个编译时间的延长。预编译头文件的作用就是将那些被多个源文件包含的头文件只被编译一次,并在包含该头文件的编译单元中直接引用编译结果。极大地缩短整体编译时间。
当编译一个文件时,编译器从文件的开头一直扫描到文件的结束。对其所见到的每个标记(包括来自头文件的标记),编译器都会更新其内部状态,以反应该标记所表达的语义。编译器的内部状态直接决定了其在目标文件中所产生的代码。
如果有多个需要编译的文件,其前N行代码完全相同,那么编译器在编译第一个文件时,就可以把与这N行代码相对应的内部状态保存在一个文件中。待其编译剩下的文件时,先从这个文件中重新加载事先保存好的内部状态,然后从第N+1行开始编译。从一个文件中加载与N行代码相对应的内部状态,比实际编译N行代码快得多。
多个文件的前N行代码完全相同,这种情况更多地表现为,以相同的顺序包含一组相同的头文件,特别是那些几乎每个程序都会用到的标准头文件,如stdio.h或iostream等。先编译这组头文件,并将编译过程中所形成的内部状态保存在一个专门的文件中,然后在编译所有以包含这组头文件为前N行代码的文件时,直接从这个专门的文件中加载与头文件有关的信息,避免重复编译完全相同的内容,缩短编译时间,提高编译速度。这种编译技术称为预编译头技术。目前大多数C/C++编译器都支持该技术,而那个用于保存编译器内部状态的专门文件则称为预编译头文件(GNU编译器生成的.gch文件或Microsoft编译器生成的.pch文件)。
在使用预编译头技术时,所包含的头文件应该是各模块所需头文件的并集。这对于每个具体模块而言可能会有部分冗余,但就整体而言要比只选择有用的头文件,能够获得更快的编译速度。
更一般化的做法是,把那些包括标准头文件在内的,不会或很少修改的头文件及代码集中放在一个头文件中;编译该头文件,得到预编译头文件;然后在每个需要使用其中内容的文件的第一行包含该头文件,编译器会自动为其加载预编译头文件,而不是重新编译该头文件。
xxxxxxxxxx
11g++ -c comparator3.h -> comparator3.h.gch
底层模板往往是在实例化上层模板的过程中被实例化的。因此,一旦因为底层模板的错误而导致上层模板不能被正确地实例化,编译器所给出的诊断信息通常会包含对产生这个问题的所有层次的完整跟踪。程序员往往很难从这么多信息中快速找到症结所在。
xxxxxxxxxx
351// behind.cpp
2
3// 诊断信息跟踪所有层次
4
5template<typename P>
6void zero(P p) {
7 *p = 0; // 完整的错误信息包含对模板实例化过程的全程跟踪
8}
9
10template<typename P>
11void core(P p) {
12 zero(p);
13}
14
15template<typename T>
16void middle(typename T::P p) {
17 core(p);
18}
19
20template<typename T>
21void shell(void) {
22 typename T::P p;
23 middle<T>(p);
24}
25
26class Object {
27public:
28 typedef int P;
29};
30
31int main(void) {
32 shell<Object>();
33
34 return 0;
35}
在程序中增加一些不可能执行到哑代码,只是为了在实例化上层模板时,提前验证一下所用模板实参能否满足底层模板所需要的操作,这种编程技巧称为浅层实例化。
xxxxxxxxxx
421// ahead.cpp
2
3// 提前验证模板实参
4
5template<typename P>
6void zero(P p) {
7 *p = 0; // 完整的错误信息包含对模板实例化过程的全程跟踪
8}
9
10template<typename P>
11void core(P p) {
12 zero(p);
13}
14
15template<typename T>
16void middle(typename T::P p) {
17 core(p);
18}
19
20template<typename T>
21void shell(void) {
22 // 浅层实例化
23 class Shallow {
24 void deref(typename T::P p) {
25 *p = 0; // 当高层模板实参不符合低层模板约束时,提前报错
26 }
27 };
28
29 typename T::P p;
30 middle<T>(p);
31}
32
33class Object {
34public:
35 typedef int P;
36};
37
38int main(void) {
39 shell<Object>();
40
41 return 0;
42}
跟踪器是一个用户自定义的类,可以做为测试模板功能和性能的类型实参。
xxxxxxxxxx
171// tracer.h
2
3// 测试模板功能和性能
4
5class Tracer {
6public:
7 Tracer(void);
8 Tracer(int n);
9 Tracer(Tracer const& tracer);
10
11 Tracer& operator=(Tracer const& tracer);
12
13 ~Tracer(void);
14
15private:
16 int n;
17};
跟踪器的定义有且仅有满足模板测试的功能。
xxxxxxxxxx
341// tracer.cpp
2
3// 跟踪器仅供测试
4
5
6
7
8
9using namespace std;
10
11Tracer::Tracer(void) {
12 cout << "缺省构造:" << this << endl;
13}
14
15Tracer::Tracer(int n) : n(n) {
16 cout << "有参构造:" << this << endl;
17}
18
19Tracer::Tracer(Tracer const& tracer) : n(tracer.n) {
20 cout << "拷贝构造:" << &tracer << "->" << this << endl;
21}
22
23Tracer& Tracer::operator=(Tracer const& tracer) {
24 cout << "拷贝赋值:" << &tracer << "->" << this << endl;
25
26 if (&tracer != this)
27 n = tracer.n;
28
29 return *this;
30}
31
32Tracer::~Tracer(void) {
33 cout << "析构函数:" << this << endl;
34}
对于被跟踪模板的每个操作,跟踪器都应该有一个与之对应的跟踪动作。
xxxxxxxxxx
311// vector.cpp
2
3// 跟踪器的覆盖范围
4
5
6
7
8
9using namespace std;
10
11int main(void) {
12 vector<Tracer> v1(3);
13 vector<Tracer> v2(3, 10);
14
15 v2.push_back(20);
16 v2.pop_back();
17
18 v2.erase(v2.begin());
19
20 v2.resize(3);
21 v2.resize(4);
22 v2.reserve(10);
23
24 v2.clear();
25
26 v2.push_back(30);
27
28 v1 = v2;
29
30 return 0;
31}
所谓多态,即令同一种类型表现出不同的行为特征。如果把类模板也看作一种类型,为其提供不同的类型实参,即可表现出不同的行为特征,这种多态称为静态多态。与基于虚函数的动态多态相比,静态多态具有更好的时间性能,而且代码也更为简洁。
xxxxxxxxxx
561// poly.cpp
2
3// 基于模板的静态多态
4
5
6
7using namespace std;
8
9template<typename Drawable>
10class Shape {
11public:
12 Shape(Drawable const& drawable) : drawable(drawable) {}
13
14 void draw(void) const {
15 drawable.draw();
16 }
17
18private:
19 Drawable const& drawable;
20};
21
22class Rect {
23public:
24 Rect(double x, double y, double w, double h) : x(x), y(y), w(w), h(h) {}
25
26 void draw(void) const {
27 cout << "Rect(" << x << ',' << y << ',' << w << ',' << h << ')' << endl;
28 }
29
30private:
31 double x, y, w, h;
32};
33
34class Circle {
35public:
36 Circle(double x, double y, double r) : x(x), y(y), r(r) {}
37
38 void draw(void) const {
39 cout << "Circle(" << x << ',' << y << ',' << r << ')' << endl;
40 }
41
42private:
43 double x, y, r;
44};
45
46int main(void) {
47 Rect rect(1, 2, 3, 4);
48 Shape<Rect> shape1 = rect;
49 shape1.draw();
50
51 Circle circle(5, 6, 7);
52 Shape<Circle> shape2 = circle;
53 shape2.draw();
54
55 return 0;
56}