1 太懒了,发现一个博主写得非常好,原文来源https://www.cnblogs.com/zhangyachen/p/13946442.html
2 这里我是copy了一些我认为比较重要的
3
4 模板的编译和链接问题
5
6 大多数人会按照如下方式组织非模板代码:
7
8 将类或者其他类型声明放在头文件(.hpp、.H、.h、.hh、.hxx)中。
9 将函数定义等放到一个单独的编译单元文件中(.cpp、.C、.c、.cc、.cxx)。
10 但是这种组织方式在包含模板的代码中却行不通,例如:
11
12 头文件:
13 // myfirst.hpp
14 #ifndef MYFIRST_HPP
15 #define MYFIRST_HPP
16 // declaration of template
17 template<typename T>
18 void printTypeof (T const&);
19 #endif // MYFIRST_HPP
20
21 定义函数模板的文件:
22
23 // myfirst.cpp
24 #include <iostream>
25 #include <typeinfo>
26 #include "myfirst.hpp"
27 // implementation/definition of template
28 template<typename T>
29 void printTypeof (T const& x) {
30 std::cout << typeid(x).name() << '\n';
31 }
32
33 在另一个文件中使用该模板:
34
35 // myfirstmain.cpp
36 #include "myfirst.hpp"
37 // use of the template
38 int main() {
39 double ice = 3.0;
40 printTypeof(ice); // call function template for type double
41 }
42 在c/c++中,当编译阶段发现一个符号(printTypeof)没有定义只有声明时,编译器会假设它的定义在其他文件中,所以编译器会留一个”坑“给链接器linker,让它去填充真正的符号地址。
43
44 但是上面说过,模板是比较特殊的,需要在编译阶段进行instantiation,即需要进行模板参数类型推断,实例化模板,当然也就需要知道函数的定义。但是由于上面两个cpp文件都是单独的编译单元文件,所以当编译器编译myfirstmain.cpp时,它没有找到模板的定义,自然也就没有instantiation。
45
46 解决办法就是我们把模板的声明和定义都放在一个头文件。大家可以看一下自己环境下的vector等STL源文件,就是把类的声明和定义都放在了一个文件中。
47
48 普通函数可以在头文件声明,cpp上定义,其他文件调用该函数此时可以通过链接阶段找到该函数的定义;
49 而模板需要在编译过程中就需要找到定义。
50
51
52 在C++11中,我们可以利用auto和trailing return type来让编译器找出返回值类型:
53
54 template <typename T1, typename T2>
55 auto max(T1 a, T2 b) -> decltype(b < a ? a : b) {
56 return b < a ? a : b;
57 }
58
59 在C++14中,我们可以省略trailing return type:
60
61 template<typename T1, typename T2>
62 auto max (T1 a, T2 b) {
63 return b < a ? a : b;
64 }
65
66 重载函数模板
67 这个跟普通函数重载也类似:
68
69 // maximum of two int values:
70 int max(int a, int b) {
71 return b < a ? a : b;
72 }
73
74 // maximum of two values of any type:
75 template <typename T>
76 T max(T a, T b) {
77 return b < a ? a : b;
78 }
79
80 int main() {
81 max(7, 42); // calls the nontemplate for two ints
82 max(7.0, 42.0); // calls max<double> (by argument deduction)
83 max('a', 'b'); // calls max<char> (by argument deduction)
84 max<>(7, 42); // calls max<int> (by argument deduction)
85 max<double>(7, 42); // calls max<double> (no argument deduction)
86 max('a', 42.7); // calls the nontemplate for two ints
87 }
88 这里需要解释下最后一个max('a', 42.7)。因为在模板参数类型推导过程中不允许类型自动转换,但是调用普通函数是允许的,所以这个会调用非模板函数
89 模板参数类型推导过程中不允许类型自动转换!!!
90
91
92 重载时最好不要随便改变模板参数个数,最好可以显示的指定模板参数类型
93 下面是段有问题的代码:
94
95 // maximum of two values of any type (call-by-reference)
96 template <typename T> T const &max(T const &a, T const &b) {
97 return b < a ? a : b;
98 }
99
100 // maximum of two C-strings (call-by-value)
101 char const *max(char const *a, char const *b) {
102 return std::strcmp(b, a) < 0 ? a : b;
103 }
104
105 // maximum of three values of any type (call-by-reference)
106 template <typename T> T const &max(T const &a, T const &b, T const &c) {
107 return max(max(a, b), c); // error if max(a,b) uses call-by-value
108 }
109
110 int main() {
111 auto m1 = max(7, 42, 68); // OK
112
113 char const *s1 = "frederic";
114 char const *s2 = "anica";
115 char const *s3 = "lucas";
116 auto m2 = max(s1, s2, s3); // run-time ERROR
117 }
118 问题出现在return max (max(a,b), c);,因为char const *max(char const *a, char const *b)的参数是按值传递,max(a,b)会产生一个指向已经销毁的栈帧地址,这会导致未定义行为。
119 如果不拿个中间变量保存函数返回值,那么它就会消亡了,在调用Max(消亡值,s3)产生错误;
120
121
122
123 引子
124 T&&在代码里并不总是右值引用:
125
126 void f(Widget&& param); // rvalue reference
127
128 Widget&& var1 = Widget(); // rvalue reference
129
130 auto&& var2 = var1; // not rvalue reference
131
132
133 template<typename T>
134 void f(std::vector<T>&& param); // rvalue reference
135
136
137 template<typename T>
138 void f(T&& param); // not rvalue reference
139 T&&代表两种含义:
140
141 右值引用
142 万能引用(universal references, or forwarding references)
143 如何区分
144 万能引用一般出现在两个场景中:(万能引用可以推断是左值引用还是右值引用,泛型接口)
145
146 1.模板参数
147 template<typename T>
148 void f(T&& param); // param is a universal reference
149 2.auto声明
150 auto&& var2 = var1; // var2 is a universal reference
151
152 总结:万能引用只存在于推理类型,而右值引用是确定类型
153
154 auto声明
155 对于auto的场景来说,所有的auto&&都是万能引用,因为它总是有参数推导的过程。例如定义一个记录函数执行时间的lambda(C++14中允许使用auto来声明lambda的函数):
156
157 auto timeFuncInvocation = [](auto &&func, auto &&... params) {
158 start timer;
159 std::forward<decltype(func)>(func)( // invoke func
160 std::forward<decltype(params)>(params)... // on params
161 );
162 stop timer and record elapsed time;
163 };
164