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