(2)偏特化
偏特化是任何针对模板参数进一步进行条件限制的特化版本。偏特化分为两种表现方式:
①部分特化:把模板参数类表中的一部分参数特化
1. //偏特化-部分特化,把第二个参数特化为double 2. template<class T1> 3. class Data<T1, double> 4. { 5. public: 6. Data() 7. { 8. cout << "Data<T1,T2>" << endl; 9. } 10. private: 11. T1 _d1; 12. double _d2; 13. };
这时候d2就会去调这个偏特化 :
②对参数进一步限制:对模板参数做更进一步的条件限制
可以将参数偏特化为指针类型 :
1. //偏特化-两个参数偏特化为指针类型,只指定指针,什么类型的指针都可以 2. template<typename T1,typename T2> 3. class Data<T1*, T2*> 4. { 5. public: 6. Data() 7. { 8. cout << "Data<T1*,T2*>" << endl; 9. } 10. private: 11. T1 _d1; 12. T2 _d2; 13. };
1. void Test_Class() 2. { 3. Data<int, int> d1; 4. Data<int, double> d2; 5. Data<int, char> d3; 6. Data<char*, char*> d4;//会调用两个参数偏特化为指针类型的偏特化 7. }
也可以将参数偏特化为引用类型:
1. //偏特化-两个参数偏特化为引用类型,只指定引用,什么类型的引用都可以 2. template<typename T1, typename T2> 3. class Data<T1&, T2&> 4. { 5. public: 6. Data(const T1& d1,const T2& d2) 7. :_d1(d1) 8. ,_d2(d2) 9. { 10. cout << "Data<T1&,T2&>" << endl; 11. } 12. private: 13. const T1& _d1; 14. const T2& _d2; 15. };
1. void Test_Class() 2. { 3. Data<int, int> d1; 4. Data<int, double> d2; 5. Data<int, char> d3; 6. Data<char*, char*> d4;//会调用两个参数偏特化为指针类型的偏特化 7. Data<double&, double&> d5(2.1, 3.2);//会调用两个参数偏特化为引用类型的偏特化 8. }
最后,编译器匹配参数时,会自动匹配最匹配的那个,编译器实例化之后就没有模板了,实例化之后就会把T1&替换成double&,把T2&替换成double&,就变成了普通类。
假如参数类型一个是指针,一个是引用呢?将一个参指定为指针类型,另外一个参数指定为引用类型
1. //偏特化-一个参数偏特化为指针类型,一个参数偏特化为引用类型 2. template<typename T1, typename T2> 3. class Data<T1*, T2&> 4. { 5. public: 6. Data() 7. { 8. cout << "Data<T1*,T2&>" << endl; 9. } 10. };
Data<int*, double&> d6;//会调用一个参数偏特化为指针类型,一个参数偏特化为引用类型的偏特化
三、模板分离编译
分离式编译:一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,将所有目标文件连接起来形成单一的可执行文件的过程称为分离编译模式。
1.模板支持分离编译吗
对于分离式编译的模板:
template.h
1. template<class T> 2. T Add(const T& left, const T& right);
template.cpp
1. #include "template.h" 2. 3. template<class T> 4. T Add(const T& left, const T& right) 5. { 6. return left + right; 7. }
template-main.cpp
1. #include "template.h" 2. 3. int main() 4. { 5. Add(1, 2); 6. Add(1.0, 2.0); 7. 8. return 0; 9. }
上述代码运会报错:
2.为什么模板不支持分离编译
程序要运行,需要经过预处理、编译、汇编、链接4个阶段
(1)预处理
包含头文件、宏替换、、条件编译、删除注释
(2)编译
语法分析、词法分析、语义分析,符号汇总
(3)汇编
汇编代码转换成机器指令 、生成符号表
(4)链接
链接目标文件和连接库、合并符号表、重定位
预处理阶段,#include "template.h"包含头文件,认为会有Add函数的定义。编译时在符号表里填上符号和地址,生成符号表,编译阶段只有函数声明,没有函数定义,如果有人调用了这个符号,就能通过符号表找到这个符号对应的地址。但是函数模板不会生成对应符号。汇编时把符号转换成二进制,这样,机器就可以识别了。链接时会去找函数地址进行合并重定位。
虽然编译时,想生成函数模板对应的符号,但是此时并不知道T是什么,由于在链接之前,各个文件不知道对方的存在,所以template-main.cpp无法告诉template.cpp T具体是什么,template.cpp中无法对T进行实例化,会寄希望于最后一步链接,所以不会生成对应符号。即,定义的地方(template.cpp)不实例化,而实例化的地方(template-main.cpp)没有定义,只有声明。没有实例化T,生成不了函数模板的符号。
3.如何编译模板文件
如何解决函数模板不能生成对应符号,导致程序运行不了的问题呢?有两种方法
(1)显式指定实例化
模板定义时直接指定T的类型,即增加模板的特化,将template.cpp改为:
1. #include "template.h" 2. 3. template<class T> 4. T Add(const T& left, const T& right) 5. { 6. return left + right; 7. } 8. 9. //增加int类型的模板特化 10. template<> 11. int Add<int>(const int& left, const int& right) 12. { 13. return left + right; 14. } 15. 16. //增加double类型的模板特化 17. template<> 18. double Add<double>(const double& left, const double& right) 19. { 20. return left + right; 21. }
(2)将声明和定义放到一个文件中(推荐)
将template.h和template.cpp合并成一个文件,这个文件名可以是template.h,也可以是template.cpp,此时template就只剩下一个文件了。
template.h
1. template<class T> 2. T Add(const T& left, const T& right); 3. 4. template<class T> 5. T Add(const T& left, const T& right) 6. { 7. return left + right; 8. }
template.main不变
1. #include "template.h" 2. 3. int main() 4. { 5. Add(1, 2); 6. Add(1.0, 2.0); 7. 8. return 0; 9. }
编译通过。