1.非类型模板参数

模板参数分类类型形参与非类型形参。

//定义一个模板类型的静态数组
template<class T, size_t N = 10>
class Array
{
public:
	T& operator[](size_t index)
	{
		return _array[index];
	}

	const T& operator[](size_t index)const
	{
		reutrn _array[index];
	}

	size_t Size()const
	{
		return _size;
	}

	bool Empty()const
	{
		return 0 == _size;
	}

private:
	T _array[N];
	size_t _size;
};

注意
1.浮点数,类对象以及字符串是不允许作为非类型模板参数的。
2.非类型的模板参数必须在编译期就能确认结果

类模板的特化

1.概念

通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结
果,比如:

template<class T>
bool IsEqual(T& left, T& right)
{
	return left == right;
}

void Test()
{
	char* p1 = "woaijuju";
	char* p2 = "jujuaiwo";

	if (IsEqual(p1, p2))
		cout << p1 << endl;
	else
		cout << p2 << endl;

}

此时,就需要对模板进行特化。即:在原模板类的基础上,针对特殊类型所进行特殊化的实现方式。模板特
化中分为函数模板特化与类模板特化。

2.函数模板特化

函数模板特化步骤:
1.必须要先有一个基础的函数模板
2.关键字template后面接一对空的尖括号<>
3.函数名后跟一对尖括号,尖括号中指定需要的特化的类型
4. 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。

template<>
bool IsEqual<char*>(char*& left, char*& right)//IsEqual不是模板
{
	if (strcmp(left, right) > 0)
		return true;

	return false;
}

注意:一般情况下如果函数模板遇到不能处理或者处理有误的类型,为了实现简单通常都是将该函数直接给出。

bool IsEqual(char*& left, char*& right)
{
	if (strcmp(left, right) > 0)
		return true;

	return false;
}

3.类模板的特化

(1)全特化:将模板参数表中所有参数都确定化

template<class T1, class T2>
class Date
{
public:
	Date()
	{
		cout << "Date<T1, T2>" << endl;
	}
private:
	T1 _d1;
	T2 _d2;

};

template<>
class Date<int, char>
{
public:
	Date()
	{
		cout << "Date<int, char>" << endl;
	}
private:
	T1 _d1;
	T2 _d2;

};
void TestVector()
{
	Date<int, int> d1;
	Date<int, char> d2;

}

(2)偏特化:任何针对模板参数进一步进行条件限制设计的特化版本

template<class T1, class T2>
class Date
{
public:
	Date()
	{
		cout << "Date<T1, T2>" << endl;
	}
private:
	T1 _d1;
	T2 _d2;

};

偏特化有以下两种表现形式:
(1)部分特化
将模板参数类表中一部分参数特化

//将第二个参数特化为int
template<class T1>
class Date<T1, int>
{
public:
	Date()
	{
		cout << "Date<T1, int>" << endl;
	}
private:
	T1 _d1;
	int _d2;

};

(2)参数更进一步的限制
偏特化并不仅仅是指特化部分参数,而是针对模板参数更进一步的条件限制所设计出来的一个特化版本。

//两个参数片特化为指针类型
template<typename T1, typename T2>
class Data<T1*, T2*>
{
public:
	Data()
	{
		cout << "Date<T1*, T2*>" << endl;
	}
private:
	T1 _d1;
	T2 _d2;
};

//两个参数偏特化引用类型
template<typename T1, typename T2>
class Data<T1&, T2&>
{
public:
	Data(const T1& d1, const T2& d2)
		:_d1(d1)
		,_d2(d2)
	{
		cout << "Data<T1&, T2&>" << endl;
	}
private:
	const T1 &_d1;
	const T2 &_d2;
};

void test2()
{
	Data<double, int> d1;      // 调用特化的int版本
	Data<int, double> d2;      // 调用基础的模板 
	Data<int *, int*> d3;      // 调用特化的指针版本
	Data<int&, int&> d4(1, 2); // 调用特化的指针版本
}
类模板特化在这里插入代码片的应用之类型萃取

问题:如何实现一个通用的拷贝函数?下面的实现有问题么?

1.使用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);
}

上述代码虽然对于任意类型的空间都可以进行拷贝,但是如果拷贝自定义类型对象就可能会出错,因为自定义类型对象有可能会涉及到深拷贝(比如string),而memcpy属于浅拷贝。如果对象中涉及到资源管理,就只能用赋值。

2.使用赋值方式进行拷贝

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];
 }
}

循环赋值的方式虽然可以,但是代码的效率比较低,而C/C++程序最大的优势就是效率高。那能否将另种方式的优势结合起来呢?遇到内置类型就用memcpy来拷贝,遇到自定义类型就用循环赋值方式来做呢?

3.增加bool类型区分自定义与内置类型

template<class T>
void Copy(T* dst, const T* src, size_t size, bool IsPODType)
{
 if(IsPODType)
 memcpy(dst, src, sizeof(T)*size);
 else
 {
 for(size_t i = 0; i < size; ++i)
 dst[i] = src[i];
 }
}

通过多增加一个参数,就可将两种拷贝的优势体现结合起来。但缺陷是:用户需要根据所拷贝元素的类型去传递第三个参数,那出错的可能性就增加。那能否让函数自动去识别所拷贝类型是内置类型或者自定义类型呢?

4.使用函数区分内置于自定义类型

因为内置类型的个数是确定的,可以将所有内置类型集合在一起,如果能够将所拷贝对象的类型确定下来,在内置类型集合中查找其是否存在即可确定所拷贝类型是否为内置类型
通过typeid来确认所拷贝对象的实际类型,然后再在内置类型集合中枚举其是否出现过,既可确认所拷贝元素的类型为内置类型或者自定义类型。但缺陷是:枚举需要将所有类型遍历一遍,每次比较都是字符串的比较,效率比较低

5.类型萃取

为了将内置类型与自定义类型区分开,给出以下两个类分别代表内置类型与自定义类型。

// 代表内置类型
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<short>
{
 typedef TrueType IsPODType;
};
template<>
struct TypeTraits<int>
{
 typedef TrueType IsPODType;
};
template<>
struct TypeTraits<long>
{
 typedef TrueType IsPODType;
};
// ... 所有内置类型都特化一下

6.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());
}
模板的分离编译

1.什么是分离编译

一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式。

2.模板的分离编译

假如有以下场景,模板的声明与定义分离开,在头文件中进行声明,源文件中完成定义:

// 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;
}
模板总结

优点
1.模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生。
2.增强了代码的灵活性。
缺点

  1. 模板会导致代码膨胀问题,也会导致编译时间变长
  2. 出现模板编译错误时,错误信息非常凌乱,不易定位错误
c++ I/O流

c++[模板进阶]_类模板