C++:模版特化的理解

433 阅读3分钟

模版的特化

C++中的模板特化不同于模板的实例化,模板参数在某种特定类型下的具体实现称为模板的特化。模板特化有时也称之为模板的具体化,分别有函数模板特化类模板特化

概念

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

当使用一个判断相等的模板函数时

#include <iostream>
using namespace std;

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

int main() {
    const char* p1 = "hello";
    const char* p2 = "world";
    if (IsEqual(p1, p2))
        cout << p1 << endl;
    else
        cout << p2 << endl; //  world

    return 0;
}

但是该模板函数在对于字符串进行比较时就不能使用了,对于字符串我们不能直接比较,因此直接特化出一个专门供字符串使用的模板参数

template<> // 此处不添加类型模板,直接使用空即可
bool Isequal<char*>(char*& p1, char*& p2){
	return strcmp(p1, p2) == 0;
}

函数模板特化

函数模板的特化步骤:

  1. 必须要先有一个基础的函数模板
  2. 使用特换模板函数时格式有要求:
    1、关键字template后面接一对空的尖括号<>
    2、函数名<特化类型>(特化类型 参数1, 特化类型 参数2 , …) 在函数名后跟<>其中写要特化的类型
  3. 函数形参表必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。
#include <iostream>
using namespace std;

template< typename T >
void swapArgs(T & a, T & b)
{
	T tmp;
	tmp = a;
	a = b;
	b = tmp;
}

template<>
void swapArgs<int>(int & a, int & b)
{
	a = a ^ b;
	b = a ^ b;
	a = a ^ b;
}

类模板特化

类的模板特化分为两种,一种是全特化,一种为偏特化

  • 全特化:将模板参数类表中所有的参数都确定化。
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:
	T1 _d1;
	T2 _d2; 
};
void TestVector()
{
    Data<int, int> d1;	// Data<T1, T2>
    Data<int, char> d2;	// Data<int, char>
}
  • 偏特化:对于模板的类型做一些限制

比如对于以下模板类:

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

偏特化有以下两种表现方式:

  1. 部分特化:将模板参数类表中的一部分参数特化。
// 将第二个参数特化为int 
template <class T1> 
class Data<T1, int>
{
public:
    Data() {
    	cout<<"Data<T1, int>" <<endl;
    }
private:
    T1 _d1;
	int _d2; 
};
  1. 第二种是对于模板类型的进一步限制
//两个参数偏特化为指针类型
template <typename T1, typename T2> 
class Data <T1*, T2*>
{
public:
    Data() {
    	cout<<"Data<T1*, T2*>" <<endl;
    }
private:
    T1 _d1;
	T2 _d2; 
};

//两个参数偏特化为引用类型
template<class T1, class T2>
class Test<T1 &, T2 &>{
	T1 & _d1;
	T2 & _d2; 
public:
	Test(T1 &a, T2 &b) :
		_d1(a),
		_d2(b)
	{
		cout << "T1 &, T2 &" << endl;
	}
};

类模板特化应用之类型萃取(了解)

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

  • 使用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);
	return 0;
}

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

  • 使用赋值方式拷贝
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来拷贝,遇到自定义类型就用循环赋值方式来做呢?

  • 增加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];
	} 
}

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

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

因为内置类型的个数是确定的,可以将所有内置类型集合在一起,如果能够将所拷贝对象的类型确定下来,在内置类型集合中查找其是否存在即可确定所拷贝类型是否为内置类型

//
// POD: plain old data 平凡类型(无关痛痒的类型)--基本类型
// 指在C++ 中与 C兼容的类型,可以按照 C 的方式处理。 
//
// 此处只是举例,只列出个别类型
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(array)/sizeof(array[0]); ++i)
    {
        if(0 == strcmp(strType, arrType[i]))
            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];
  	}
}

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

  • 类型萃取

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

// 代表内置类型 
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; 
}; 
// ... 所有内置类型都特化一下

通过对TypeTraits类模板重写改写方式四中的Copy函数模板,来确认所拷贝对象的实际类型。

/*
T为int:TypeTraits<int>已经特化过,程序运行时就会使用已经特化过的TypeTraits<int>, 该类中的
IsPODType刚好为类TrueType,而TrueType中Get函数返回true,内置类型使用memcpy方式拷贝 T为string:TypeTraits<string>没有特化过,程序运行时使用TypeTraits类模板, 该类模板中的IsPODType
刚好为类FalseType,而FalseType中Get函数返回true,自定义类型使用赋值方式拷贝
*/

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

int main() {
	
    int a1[] = {1,2,3,4,5,6,7,8,9,0};
    int a2[10];
    Copy(a2, a1, 10);
    
    string s1[] = {"1111", "2222", "3333", "4444"};
    string s2[4];
    Copy(s2, s1, 4);
    return 0;
}

要点总结

1、函数模板的特化

针对某个函数模板,特化它其中的某种情况

特化必须针对一个已经存在的函数模板,不能单独存在。
特化不影响调用优先级

写法举例:
原模板:
template< typename T >
void swapArgs(T & a, T & b)

特化int:
template<>
void swapArgs(int & a, int & b)

2、类模板的特化。

写法和作用都跟函数模板的特化相似。