- 非类型模板参数
- 模板的特化
- 类模板特化应用之类型萃取
- 模板分离编译
- 模板总结、
1. 非类型模板参数
类型形参即:出现在模板参数列表中,跟在class或者typename之后的参数类型名称。
非类型形参:是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。
类型模板参数
template <class T1, typename T2>
T1 Add(const T1& a, const T2& b)
{
return a + b;
}
//非类型模板参数,作为常量使用
//
template <class T, size_t N>
class Array
{
private:
T a[N];
};
int main()
{
Array<int, 10> arr1;
Array<double, 100> arr2;
return 0;
}
- 浮点数、类对象以及字符串是不允许作为非类型模板参数的。
- 非类型的模板参数必须在编译期就能确认结果。
2. 模板的特化
2.1 概念
在原模板类的基础上,针对特殊类型所进行特殊化的实现方式。模板特
化中分为函数模板特化与类模板特化。
2.2 函数模板特化::定制指定类型数据的处理逻辑
函数模板的特化步骤:
- 必须要先有一个基础的函数模板
- 关键字template后面接一对空的尖括号<>
- 函数名后跟一对尖括号,尖括号中指定需要特化的类型
- 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。
template <>
bool isGreater<char*&>(char*& const a, char*& const b)
{
if (strcmp(a, b) > 0)
return true;
return false;
}
//5. 一般不去写函数模板的特化版本,函数模板的特化比较复杂,会报出一些奇怪的错误
建议对于函数模板处理不了或者报错的类型,去实现一个对应类型的普通函数
bool isGreater(char* a, char* b)
{
if (strcmp(a, b) > 0)
return true;
return false;
}
int main()
{
int a = 10, b = 20;
char* str2 = "world";
char* str1 = "hello";
cout << isEqual(a, b) << endl;
cout << isEqual(str1, str2) << endl; // bool isEqual(char*& a, char*& b)
cout << isGreater(a, b) << endl;
cout << isGreater(str1, str2) << endl;
return 0;
}
2.3 类模板特化
2.3.1 全特化:是将模板参数类表中所有的参数都确定化
//全特化:template<>
class 类<类型1,类型2>
template<class T1, class T2>
class Data
{
public:
Data() { cout << "Data<T1, T2>" << endl; }
private:
T1 _d1;
T2 _d2;
};
//全特化
template<>
class Data<int, char>
{
public:
Data() { cout << "Data<int, char>" << endl; }
private:
int _d1;
char _d2;
};
2.3.2 偏特化(半特化):任何针对模版参数进一步进行条件限制设计的特化版本。
// 1. 部分特化:只特化部分类型, template<class T>
class Data<T,int>{};
template<class T>
class Data<T, char>
{
public:
Data() { cout << "Data<T, char>" << endl; }
private:
int _d1;
char _d2;
};
// 2. 对模板参数做进一步限制,template<class T1,class T2>;
class Data<T1*,T2*>{};
template<class T1, class T2>
class Data<T1*, T2*>
{
public:
Data() { cout << "Data<T1*, T2*>" << endl; }
private:
T1* _d1;
T2* _d2;
};
// 2. 对模板参数做进一步限制 template<class T1,class T2>;
class Data<T1&,T2&>{};
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<T1, T2>
Data<int, char> d2; // Data<int, char>
Data<char, char> d3;// Data<T, char>
Data<int*, int*> d4; //Data<T1*, T2*>
Data<int&, int&> d5; // Data<T1&,T2&>
return 0;
}
3. 类模板特化应用之类型萃取
3.1 容器增容: 拷贝
STL 拷贝分两路:
1. 内置类型: memcpy ( 一片内存拷贝,效率高 : O(1))
2. 自定义类型:赋值拷贝 ( 每个元素依次拷贝,效率较低 : O(n))
内置类型: memcpy
template<class T>
void Copy(T* dst, const T* src, size_t size)
{
memcpy(dst, src, sizeof(T)*size);
}
int main()
{
// 试试下面的代码
string strarr1[3] = {"11", "22", "33"};
string strarr2[3];
Copy(strarr2, strarr1, 3);
}
自定义类型:赋值拷贝
template<class T>
void Copy(T* dst, const T* src, size_t size)
{
for(size_t i = 0; i < size; ++i)
{
dst[i] = src[i];
}
}
3.2 增加bool类型区分自定义与内置类型
void Copy(T* dst, const T* src, size_t sz, bool isPODtype)
{
if (isPODtype)
{
memcpy(dst, src, sizeof(T)* sz);
}
else
{
for (int i = 0; i < sz; i++)
{
dst[i] = src[i];
}
}
}
通过多增加一个参数,就可将两种拷贝的优势体现结合起来。但缺陷是:用户需要根据所拷贝元素的类型去传递第三个参数,那出错的可能性就增加。那能否让函数自动去识别所拷贝类型是内置类型或者自定义类型呢
3.3 使用函数区分内置与自定义类型
因为内置类型的个数是确定的,可以将所有内置类型集合在一起,如果能够将所拷贝对象的类型确定下来,在内置类型集合中查找其是否存在即可确定所拷贝类型是否为内置类型
// RTTI : Run Time Type Information / Identification 进行时判断类型
// RTTI: typeid typeid(a).name
// bool isPODType(const char* tp): 缺陷:效率较低,时间复杂度O(n^2)
template <class T>
bool isPODType(const char* tp)
{
static char* typyArr[] = { "int", "double", "float", "char", "..." };
for (int i = 0; i < sizeof(typyArr) / sizeof(typyArr[0]); i++)
{
if (strcmp(tp, typyArr[i]))
return true;
}
return false;
}
template <class T>
void Copy(T* dst, const T* src, size_t sz)
{
if (isPODtype(typeid(T).name()))
{
memcpy(dst, src, sizeof(T)* sz);
}
else
{
for (int i = 0; i < sz; i++)
{
dst[i] = src[i];
}
}
}
3.4 类型萃取typetraits:类模板的一个应用场景
1.使用类模板和模板特化技术定义类型萃取类typetraits,内部定义类型falsetype
2.对于内置类型,通过模板特化自定义类型typetraits,内部定义类型truetype
3.编译期通过输入的类型生成对应的typetraits,调用truetype或者falsetype的 get()方法确定类型是否为自定义类型,根据返回结果决定拷贝的方式
4.优点:效率高,编译时确定类型,不占运行时间
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 <>
struct TypeTraits<double>
{
typedef TrueType _isPodType;
};
template <>
struct TypeTraits<float>
{
typedef TrueType _isPodType;
};
//.........
/*
T为int:TypeTraits<int>已经特化过,程序运行时就会使用已经特化过的TypeTraits<int>, 该类中的
IsPODType刚好为类TrueType,而TrueType中Get函数返回true,内置类型使用memcpy方式拷贝
T为string:TypeTraits<string>没有特化过,程序运行时使用TypeTraits类模板, 该类模板中的IsPODType
刚好为类FalseType,而FalseType中Get函数返回false,自定义类型使用赋值方式拷贝
*/
template <class T>
void Copy(T* dst, const T* src, size_t sz)
{
if (TypeTraits<T>::_isPodType::Get())
{
cout << typeid(T).name() << ": memcpy()" << endl;
memcpy(dst, src, sizeof(T)* sz);
}
else
{
cout << typeid(T).name() << ": operator=()" << endl;
for (int i = 0; i < sz; i++)
{
dst[i] = src[i];
}
}
}
int main()
{
int arr[] = { 1, 2, 3, 4, 5, 6, 7 };
int arr2[7];
Copy(arr2, arr, 7);
string str1[] = { "123", "345", "hello", "world" };
string str2[4];
Copy(str2, str1, 4);
return 0;
}
4. 模板的分离编译
概念:
一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式。
模板不支持分离编译:因为链接时找不到实际的指令
解决方法:将声明和定义放到一个文件 “xxx.hpp” 里面或者xxx.h其实也是可以的
分离编译会导致链接错误,如下图
将声明和定义放在一个.h文件中可避免错误,如下图
5. 模板总结
【 优点】:
- 模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生
- 增强了代码的灵活性
【缺陷】:
- 模板会导致代码膨胀问题,也会导致编译时间变长
- 出现模板编译错误时,错误信息非常凌乱,不易定位错误