1.1 模板初探
1.1.1 模板定义
template<typename T>
T max (T a, T b)
{
// 如果 b < a, 返回 a,否则返回 b
return b < a ? a : b;
}
template<class T>
T max (T a, T b)
{
// 如果 b < a, 返回 a,否则返回 b
return b < a ? a : b;
}
其中typename或者class含义和功能一致。
1.1.2 模板使用
#include<iostream>
#include<time.h>
#include <string>
using namespace std;
template<class T>
T max (T a, T b)
{
// 如果 b < a, 返回 a,否则返回 b
return b < a ? a : b;
}
int main()
{
int i = 42;
std::cout << "max(7,i): " << ::max(7,i) << endl;
double f1 = 3.4;
double f2 = -6.7;
std::cout << "max(f1,f2): " << ::max(f1,f2) << endl;
std::string s1 = "mathematics";
std::string s2 = "math";
std::cout << "max(s1,s2): " << ::max(s1,s2) << endl;
}
其中max前面需要使用作用域限制符::,否则编译阶段会报错,加了::程序会在全局作用域中查找max()模板;并且书中提到,在编译阶段,模板并不是被编译成一个支持多种类型的实体,而是在用类型实例化模板时,都会产生一个独立的实体,按照上面这段代码,用int doulbe string实例化模板,会得到三个函数,下面通过readelf验证下,将cpp编译成.o后,通过readelf -s 查看内部的符号表,结果如下:
Symbol table '.symtab' contains 52 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS template.cpp
2: 0000000000000000 0 SECTION LOCAL DEFAULT 6
3: 0000000000000000 0 SECTION LOCAL DEFAULT 8
4: 0000000000000000 0 SECTION LOCAL DEFAULT 9
5: 0000000000000000 0 SECTION LOCAL DEFAULT 10
6: 0000000000000000 1 OBJECT LOCAL DEFAULT 10 _ZStL19piecewise_construc
7: 0000000000000000 1 OBJECT LOCAL DEFAULT 9 _ZStL8__ioinit
8: 0000000000000000 0 SECTION LOCAL DEFAULT 11
9: 0000000000000000 0 SECTION LOCAL DEFAULT 12
10: 0000000000000000 0 SECTION LOCAL DEFAULT 13
11: 0000000000000000 0 SECTION LOCAL DEFAULT 14
12: 0000000000000000 0 SECTION LOCAL DEFAULT 16
13: 00000000000002d7 73 FUNC LOCAL DEFAULT 6 _Z41__static_initializati
14: 0000000000000320 21 FUNC LOCAL DEFAULT 6 _GLOBAL__sub_I_main
15: 0000000000000000 0 SECTION LOCAL DEFAULT 18
16: 0000000000000000 0 SECTION LOCAL DEFAULT 20
17: 0000000000000000 0 SECTION LOCAL DEFAULT 23
18: 0000000000000000 0 SECTION LOCAL DEFAULT 24
19: 0000000000000000 0 SECTION LOCAL DEFAULT 22
20: 0000000000000000 0 SECTION LOCAL DEFAULT 1
21: 0000000000000000 0 SECTION LOCAL DEFAULT 2
22: 0000000000000000 0 SECTION LOCAL DEFAULT 3
23: 0000000000000000 0 SECTION LOCAL DEFAULT 4
24: 0000000000000000 0 SECTION LOCAL DEFAULT 5
25: 0000000000000000 727 FUNC GLOBAL DEFAULT 6 main
26: 0000000000000000 8 OBJECT WEAK HIDDEN 20 DW.ref.__gxx_personality_
27: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _ZSt4cout
28: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _GLOBAL_OFFSET_TABLE_
29: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _ZStlsISt11char_traitsIcE
30: 0000000000000000 28 FUNC WEAK DEFAULT 12 _Z3maxIiET_S0_S0_
31: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _ZNSolsEi
32: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _ZSt4endlIcSt11char_trait
33: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _ZNSolsEPFRSoS_E
34: 0000000000000000 40 FUNC WEAK DEFAULT 13 _Z3maxIdET_S0_S0_
35: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _ZNSolsEd
36: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _ZNSaIcEC1Ev
37: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _ZNSt7__cxx1112basic_stri
38: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _ZNSaIcED1Ev
39: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _ZNSt7__cxx1112basic_stri
40: 0000000000000000 74 FUNC WEAK DEFAULT 14 _Z3maxINSt7__cxx1112basic
41: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _ZStlsIcSt11char_traitsIc
42: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _ZNSt7__cxx1112basic_stri
43: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _Unwind_Resume
44: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND __stack_chk_fail
45: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND __gxx_personality_v0
46: 0000000000000000 40 FUNC WEAK DEFAULT 16 _ZStltIcSt11char_traitsIc
47: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _ZNKSt7__cxx1112basic_str
48: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _ZNSt8ios_base4InitC1Ev
49: 0000000000000000 0 NOTYPE GLOBAL HIDDEN UND __dso_handle
50: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _ZNSt8ios_base4InitD1Ev
51: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND __cxa_atexit
这里看30 34 40行,就是之前提到过的int double string实例化为函数的结果,读者可以试着删除某行::max,然后通过readelf查看.o的符号表,会发现确实如书中所说,符号表会少 一行;
这里我有存在另外一个疑问了,通过函数模板指定类型实例化的结果与自己定义的函数在编译阶段有什么区别吗?
将代码改写下,新增一个函数string max (string a, string b),并且不通过函数模板去实例化string类型,得到的结果如下:
#include<iostream>
#include<time.h>
#include <string>
using namespace std;
string max (string a, string b)
{
// 如果 b < a, 返回 a,否则返回 b
return b < a ? a : b;
}
template<class T>
T max (T a, T b)
{
// 如果 b < a, 返回 a,否则返回 b
return b < a ? a : b;
}
int main()
{
int i = 42;
std::cout << "max(7,i): " << ::max(7,i) << endl;
double f1 = 3.4;
double f2 = -6.7;
std::cout << "max(f1,f2): " << ::max(f1,f2) << endl;
std::string s1 = "mathematics";
std::string s2 = "math";
std::cout << "max(s1,s2): " << ::max(s1,s2) << endl;
}
Symbol table '.symtab' contains 49 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS template.cpp
2: 0000000000000000 0 SECTION LOCAL DEFAULT 5
3: 0000000000000000 0 SECTION LOCAL DEFAULT 7
4: 0000000000000000 0 SECTION LOCAL DEFAULT 8
5: 0000000000000000 0 SECTION LOCAL DEFAULT 9
6: 0000000000000000 1 OBJECT LOCAL DEFAULT 9 _ZStL19piecewise_construc
7: 0000000000000000 1 OBJECT LOCAL DEFAULT 8 _ZStL8__ioinit
8: 0000000000000000 0 SECTION LOCAL DEFAULT 10
9: 0000000000000000 0 SECTION LOCAL DEFAULT 11
10: 0000000000000000 0 SECTION LOCAL DEFAULT 13
11: 0000000000000000 0 SECTION LOCAL DEFAULT 14
12: 00000000000001f9 73 FUNC LOCAL DEFAULT 5 _Z41__static_initializati
13: 0000000000000242 21 FUNC LOCAL DEFAULT 5 _GLOBAL__sub_I__Z3maxNSt7
14: 0000000000000000 0 SECTION LOCAL DEFAULT 15
15: 0000000000000000 0 SECTION LOCAL DEFAULT 17
16: 0000000000000000 0 SECTION LOCAL DEFAULT 20
17: 0000000000000000 0 SECTION LOCAL DEFAULT 21
18: 0000000000000000 0 SECTION LOCAL DEFAULT 19
19: 0000000000000000 0 SECTION LOCAL DEFAULT 1
20: 0000000000000000 0 SECTION LOCAL DEFAULT 2
21: 0000000000000000 0 SECTION LOCAL DEFAULT 3
22: 0000000000000000 0 SECTION LOCAL DEFAULT 4
23: 0000000000000000 74 FUNC GLOBAL DEFAULT 5 _Z3maxNSt7__cxx1112basic_
24: 0000000000000000 40 FUNC WEAK DEFAULT 11 _ZStltIcSt11char_traitsIc
25: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _GLOBAL_OFFSET_TABLE_
26: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _ZNSt7__cxx1112basic_stri
27: 000000000000004a 431 FUNC GLOBAL DEFAULT 5 main
28: 0000000000000000 8 OBJECT WEAK HIDDEN 17 DW.ref.__gxx_personality_
29: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _ZSt4cout
30: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _ZStlsISt11char_traitsIcE
31: 0000000000000000 28 FUNC WEAK DEFAULT 13 _Z3maxIiET_S0_S0_
32: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _ZNSolsEi
33: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _ZSt4endlIcSt11char_trait
34: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _ZNSolsEPFRSoS_E
35: 0000000000000000 40 FUNC WEAK DEFAULT 14 _Z3maxIdET_S0_S0_
36: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _ZNSolsEd
37: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _ZNSaIcEC1Ev
38: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _ZNSt7__cxx1112basic_stri
39: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _ZNSaIcED1Ev
40: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _ZNSt7__cxx1112basic_stri
41: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _Unwind_Resume
42: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND __stack_chk_fail
43: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND __gxx_personality_v0
44: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _ZNKSt7__cxx1112basic_str
45: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _ZNSt8ios_base4InitC1Ev
46: 0000000000000000 0 NOTYPE GLOBAL HIDDEN UND __dso_handle
47: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _ZNSt8ios_base4InitD1Ev
48: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND __cxa_atexit
见23行 31行 35行,23行是非模板函数string max (string a, string b),31和35行分别是函数模板实例化出来的int类型模板函数以及double类型的模板函数,唯一的区别是bind那列,23行是GLOBAL,31行和35行是WEAK;这列的解释如下:
Bind = GLOBAL binding means the symbol is visible outside the file. LOCAL binding is visible only in the file. WEAK is like global, the symbol can be overridden.
如字面意思,WEAK的函数符号可被覆盖,看起来有些像是C语言中强符号和弱符号的意思,所以从另一方面印证了上面为什么在链接阶段不会报符号重复定义,因为string max (string a, string b)
是强符号,会优先使用这里的函数定义而不会去使用函数模板实例化string的函数,这里我本地做了验证,发现把自己定义的函数b<a改成b>a,::max会使用自己定义的函数而不是函数模板的函数。
1.1.3 两阶段编译检查
概括来说,就是
模板的检查第一步是先检查不包含类型参数的检查:
1.语法检查。比如少了分号。
2.使用了未定义的不依赖于模板参数的名称(类型名,函数名,…)。
3.未使用模板参数的 static assertions。
第二步在实例化阶段,检查依赖于类型参数的部分;书中还提到一点,如果模板没有被实例化的话,有些编译器并不会执行第一阶段在的所有检查,导致一些代码的常规错误发现不了。
1.1.4 编译和链接
两阶段的编译检查给模板的处理带来了一个问题:当实例化一个模板的时候,编译器需要(一
定程度上)看到模板的完整定义。这不同于函数编译和链接分离的思想,函数在编译阶段只
需要声明就够了。第 9 章将讨论如何应对这一问题。我们将暂时采取最简单的方法:将模板
的实现写在头文件里。