非类型模板参数
模板参数分类类型形参与非类型形参。
类型形参:出现在模板参数列表中,跟在class或者typename之类的参数类型名称。
非类型形参,就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。
namespace bite
{template<class T, size_t N>class array{public:void push_back(constT& data){//N=10;_array[_size++] = data;}T& operator[](size_t){assert(index < _size)return _array[index];}bool empty()const{return 0 == _size;}size_t size()const{return _size;}private:T _array[N];size_t _size;};
}
注意事项
- 浮点数、类对象以及字符串是不允许作为非类型模板参数的。
- 非类型的模板参数必须在编译期就能确认结果。
模板的特化
模板特化的概念
通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,比如
class Date
{
public:Date(int year, int month, int day):_year(year), _month(month), _day(day){}bool operator>(const Date&d)const{return _day > d._day;}friend ostream& operator<<(ostream& _cout, const Date& d){_cout << d._year << "/" << d._month << "/" << d._day << endl;return _cout;}
private:int _year;int _month;int _day;
};
template<class T>
T& Max(T& left, T& right)
{return left > right ? left : right;
}char *p1 = "world";char *p2 = "hello";cout << Max(p1, p2) << endl;cout << Max(p2, p1) << endl;
这个代码他就无法比较字符串类型的变量的大小
此时,就需要对模板进行特化。即:在原模板类的基础上,针对特殊类型所进行特殊化的实现方式
函数模板特化
如果不需要通过形参改变外部实参加上const
例如
template<class T>
const T& Max(const T& left, const T& right)
{return left > right ? left : right;
}
//函数模板的特化
template<>
char *& Max<char*>(char*& left, char*& right)
{//>0大于 =0等于 <0小于if (strcmp(left, right) > 0){return left;}return right;
}
注意事项
- 必须要先有一个基础的函数模板
- 关键字template后面接一对空的尖括号<>
- 函数名后跟一对尖括号,尖括号中指定需要特化的类型
- 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。
一般函数模板没必要特化,直接把相应类型的函数给出就行
char* Max(char *left, char* right)
{if (strcmp(left, right) > 0){return left;}return right;
}
类模板的特化
全特化
//全特化-----对所有类型参数进行特化
template<class T1, class T2>
class Data
{
public:Data() { cout << "Data<T1, T2>" << endl; }
private:T1 _d1;T2 _d2;
};template<>
class Data<int, int>
{
public:Data() { cout << "Data<int, int>" << endl; }
private:int _d1;int _d2;
};int main()
{Data<int, double>d1;Data<int, int>d2;return 0;
}
偏特化
部分特化
//偏特化,将模板参数列表中的参数部分参数类型化
template<class T1>
class Data<T1,int>
{
public:Data() { cout << "Data<T1, int>" << endl; }
private:T1 _d1;int _d2;
};int main()
{Data<int, double>d1;Data<int, int>d2;Data<double, int>d3;system("pause");return 0;
}
参数更进一步的限制
//偏特化:让模板参数列表中的类型限制更加的严格
template<class T1,class T2>
class Data<T1*, T2*>
{
public:Data() { cout << "Data<T1*, T2*>" << endl; }
private:T1* _d1;T2* _d2;
};int main()
{Data<int*, int>d1;Data<int, int*>d2;Data<int*, int*>d3;//特化Data<int*, double*>d4;//特化system("pause");return 0;
}
模板特化的作用之类型萃取
编写一个通用类型的拷贝函数
template<class T>
void Copy(T* dst, T* src, size_t size)
{memcpy(dst, src, sizeof(T)*size);
}
上述代码虽然对于任意类型的空间都可以进行拷贝,但是如果拷贝自定义类型对象就可能会出错,因为自定义类型对象有可能会涉及到深拷贝(比如string),而memcpy属于浅拷贝。如果对象中涉及到资源管理,就只能用赋值。
class String
{
public:String(const char* str = ""){if (str == nullptr)str = "";this->_str = new char[strlen(str) + 1];strcpy(this->_str, str);}String(const String& s):_str ( new char[strlen(s._str)+1]){strcpy(this->_str, s._str);}String& operator=(const String &s){if (this != &s){char *str = new char[strlen(s._str) + 1];strcpy(str, s._str);delete[]_str; _str = s._str;}}~String(){delete[]_str;}private:char* _str;
};//写一个通用的拷贝函数,要求:效率尽量高
template<class T>
void Copy(T* dst, T* src, size_t size)
{memcpy(dst, src, sizeof(T)*size);
}void TestCopy()
{int array1[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };int array2[10];Copy(array2, array1, 10);String s1[3] = { "111", "222", "333" };String s2[3];Copy(s2, s1, 3);
}
如果是自定义类型用memcpy,那么会存在
- 浅拷贝----导致代码崩溃
- 内存泄露—S2数组中每个String类型对象原来的空间丢失了
增加一个拷贝函数处理浅拷贝
template<class T>
void Copy2(T* dst, T*src, size_t size)
{for (size_t i = 0; i < size; ++i){dst[i] = src[i];}
}
- 优点:一定不会出错
- 缺陷:效率比较低,让用户做选择,还需要判断调用哪个函数
让函数自动去识别所拷贝类型是内置类型或者自定义类型
bool IsPODType(const char* strType)
{//此处将所有的内置类型枚举出来const char * strTypes[] = { "char", "short", "int", "long", "long long", "float", "double" };for (auto e : strTypes){if (strcmp(strType, e) == 0)return true;}return false;
}template<class T>
void Copy(T* dst, T*src, size_t size)
{//通过typeid可以将T的实际类型按照字符串的方式返回if (IsPODType(typeid(T).name()){//T的类型:内置类型memcpy(dst, src, sizeof(T)*size);}else{//T的类型:自定义类型----原因:自定义类型种可能会存在浅拷贝for (size_t i = 0; i < size; ++i){dst[i] = src[i];}}
}
在编译期间就确定类型—类型萃取
如果把一个成员函数的声明和定义放在类里面,编译器可能会把这个方法当成内联函数来处理
class String
{
public:String(const char* str = ""){if (str == nullptr)str = "";this->_str = new char[strlen(str) + 1];strcpy(this->_str, str);}String(const String& s):_str(new char[strlen(s._str) + 1]){strcpy(this->_str, s._str);}String& operator=(const String& s){if (this != &s){char* str = new char[strlen(s._str) + 1];strcpy(str, s._str);delete[] _str;_str = str;}return *this;}~String(){delete[]_str;}private:char* _str;
};//确认T到底是否是内置类型
//是
//不是
//对应内置类型
struct TrueType
{static bool Get(){return true;}
};//对应自定义类型
struct FalseType
{static bool Get(){return true;}
};template<class T>
struct TypeTraits
{typedef FalseType PODTYPE;};template<>
struct TypeTraits<char>
{typedef TrueType PODTYPE;
};template<>
struct TypeTraits<short>
{typedef TrueType PODTYPE;
};
template<>
struct TypeTraits<int>
{typedef TrueType PODTYPE;
};//........还有很多内置类型template<class T>
void Copy(T* dst, T* src, size_t size)
{// 通过typeid可以将T的实际类型按照字符串的方式返回if (TypeTraits<T>::PODTYPE::Get()){// T的类型:内置类型memcpy(dst, src, sizeof(T)*size);}else{// T的类型:自定义类型---原因:自定义类型中可能会存在浅拷贝for (size_t i = 0; i < size; ++i)dst[i] = src[i];}
}
STL中的类型萃取
// 代表内置类型
struct __true_type {};
// 代表自定义类型
struct __false_type {};template <class type>
struct __type_traits
{typedef __false_type is_POD_type;
};// 对所有内置类型进行特化
template<>
struct __type_traits<char>
{typedef __true_type is_POD_type;
};
template<>
struct __type_traits<signed char>
{typedef __true_type is_POD_type;
};
template<>
struct __type_traits<unsigned char>
{typedef __true_type is_POD_type;
};
template<>
struct __type_traits<int>
{typedef __true_type is_POD_type;
};
template<>
struct __type_traits<float>
{typedef __true_type is_POD_type;
};
template<>
struct __type_traits<double>
{typedef __true_type is_POD_type;
};
// 注意:在重载内置类型时,所有的内置类型都必须重载出来,包括有符号和无符号,比如:对于int类型,必
须特化三个,int -- signed int -- unsigned int
// 在需要区分内置类型与自定义类型的位置,标准库通常都是通过__true_type与__false_type给出一对重载
的
// 函数,然后用一个通用函数对其进行封装
// 注意:第三个参数可以不提供名字,该参数最主要的作用就是让两个_copy函数形成重载
template<class T>
void _copy(T* dst, T* src, size_t n, __true_type)
{memcpy(dst, src, n*sizeof(T));
}
template<class T>
void _copy(T* dst, T* src, size_t n, __false_type)
{for (size_t i = 0; i < n; ++i)dst[i] = src[i];
}
template<class T>
void Copy(T* dst, T* src, size_t n)
{_copy(dst, src, n, __type_traits<T>::is_POD_type());
}
分离编译
预处理----->编译---->汇编----->链接
// a.h
template<class T>
T Add(const T& left, const T& right);
// a.cpp
template<class T>
T Add(const T& left, const T& right)
{return left + right;
}
// main.cpp
#include"a.h"
int main()
{Add(1, 2);Add(1.0, 2.0);return 0;
}
没有模板的函数,没有问题
有模板的函数,编译可以过,但是链接会出错
函数模板编译:
- 实例化之前:编译器只做一些简答的语法检测,不会生成处理具体类型的代码。并不会确认函数的入口地址
- 实例化期间:编译器会推演形参类型来确保模板参数列表中T的实际类型,在生成具体类型的代码
- 不支持分离编译
解决分离编译
- 将声明和定义放到一个文件 “xxx.hpp” 里面或者xxx.h其实是可以的。
- 模板定义的位置显式实例化。
模板总结
优点
- 模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生
- 增强了代码的灵活性
缺点
- 模板会导致代码膨胀问题,也会导致编译时间变长
- 出现模板编译错误时,错误信息非常凌乱,不易定位错误
更加深入学习参考这个链接