定义
template开始,后跟一个模板参数列表(在模板定义中,模板参数列表不能为空)
typename是用来定义模板参数关键字(C++98之后),也可以 使用class(切记:不能使用struct代替class)
实例化
隐式实例化:让编译器根据实参推演模板参数的实际类型,编译器一般不会进行类型转换操作,形参和实参类型必须完全匹配
显式实例化:在函数名后的<>中指定模板参数的实际类型。如果类型不匹配,编译器会尝试进行隐式类型转换,如果无法转换成功编译器将会报错
非类型模板参数
用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用
- 浮点数、类对象以及字符串不可作为非类型模板参数
- 非类型的模板参数必须在编译期就能确认结果
模板参数的匹配原则
对于非模板函数和同名函数模板,如果其他条件都相同,在隐式实例化时会优先调用非模板函数,不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板。模板函数不允许自动类型转换,但普通函数可以进行自动类型转换
函数模板特化
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| template<class T> bool IsEqual(T& left, T& right) { return left == right; }
template<> bool IsEqual<char*>(char*& left, char*& right) { if (strcmp(left, right) == 0) return true; return false; }
|
一般情况下为了实现简单通常都是将函数直接给出
类模板
类模板中函数放在类外进行定义时,需要加模板参数列表
类模板实例化需要在类模板名字后跟<>,实例化的类型放在<>中,类模板名字不是真正的类,而实例化的结果才是真正的类
类模板特化
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| template<typename T1, typename T2> class Data {};
template<> class Data<int, char> {};
template<typename T1> class Data<T1, int> {};
template<typename T1, typename T2> class Data<T1*, T2*> {};
|
类型萃取
实现Copy函数,使其可以拷贝任意类型
单使用memcpy,可以拷贝内置类型,如果涉及到深拷贝(比如string),就会出错
单使用循环赋值,效率很低
通过增加bool参数判断是否内置类型,就可将两种拷贝的优势结合。但用户需要根据所拷贝元素的类型传递第三个参数,那出错的可能性就增加
所以需要通过类型萃取来判断是否内置类型,但需要将所有类型遍历一遍,每次比较都是字符串的比较,效率比较低
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| bool IsPODType(const char* strType) { const char* arrType[] = { "char", "short", "int", "long", "long long", "float", "double", "long double" }; for (size_t i = 0; i < sizeof(arrType) / sizeof(arrType[0]); ++i) { if (strcmp(strType, arrType[i]) == 0) return true; } return false; }
template<class T> void Copy(T* dst, const T* src, size_t size) { if (IsPODType(typeid(T).name())) memcpy(dst, src, sizeof(T)*size); else { for (size_t i = 0; i < size; ++i) dst[i] = src[i]; } }
|
类型萃取一般写法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| struct TrueType { static bool Get() { return true; } };
struct FalseType { static bool Get() { return false; } };
template<class T> struct TypeTraits { typedef FalseType IsPODType; };
template<> struct TypeTraits<char> { typedef TrueType IsPODType; };
template<> struct TypeTraits<int> { typedef TrueType IsPODType; };
template<class T> void Copy(T* dst, const T* src, size_t size) { if (TypeTraits<T>::IsPODType::Get()) memcpy(dst, src, sizeof(T) * size); else { for (size_t i = 0; i < size; ++i) dst[i] = src[i]; } }
|
stl中写法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| struct TrueType {}; struct FalseType {};
template <class T> struct TypeTraits { typedef FalseType IsPodType; };
template<> struct TypeTraits<char> { typedef TrueType IsPODType; };
template<> struct TypeTraits<int> { typedef TrueType IsPODType; };
template <class T> void _Copy(T* dst, T* src, size_t size, TrueType) { memcpy(dst, src, sizeof(T)* size); } template <class T> void _Copy(T* dst, T* src, size_t size, FalseType) { for (size_t i = 0; i < size; ++i) { dst[i] = src[i]; } }
template <class T> void Copy(T* dst, T* src, size_t size) { _Copy(dst, src, size, TypeTraits<T>::IsPodType()); }
|
模板分离编译
如果将模板的声明放在.h文件中,将其定义放在其他.cpp中,在main.cpp中实例化就会出现链接错误
因为头文件不参与编译,编译器对工程中的多个源文件单独编译,然后将多个obj文件链接
在定义的.cpp文件中编译器找不到对模板的实例化,不会生成对应的函数
所以在main.cpp中call找不到对应函数的地址
有两种方法可以解决:
- 将声明和定义放到一个文件 “xxx.hpp” 里面或者xxx.h其实也是可以的
- 模板定义的位置显式实例化,这种方法不推荐使用
总结
模板复用了代码,更快迭代开发,增强了代码的灵活性,但是会导致代码膨胀问题,编译时间变长,出现模板编译错误时,不易定位错误