首先,需要举一个例子来说明c++在什么情况下需要对不同的类型不同对待。
template <class T>
class SeqList
{
public:
SeqList()
:_a(NULL)
, _size(0)
, _capacity(0)
{}
void CheckCapacity()
{
if (_size >= _capacity)
{
_capacity = _capacity > 0 ? _capacity * 2 : 3;//当容量为0时增容为3,之后每次增2倍
T* newA = new T[_capacity];
if (_a != NULL)
{
memcpy(newA, _a, _size*sizeof(T));//使用memcpy
}
delete[] _a;
_a = newA;
}
}
void PushBack(const T& x)
{
CheckCapacity();
_a[_size++] = x;
}
void Print()
{
for (size_t i = 0; i < _size; i++)
{
cout << _a[i] << " ";
}
cout << endl;
}
private:
T* _a;
size_t _size;
size_t _capacity;
};
这是一个模板类的顺序表,实现了尾插和增容两个接口。
接下来进行测试:
void TestSeqList()
{
SeqList<int> s1;
s1.PushBack(1);
s1.PushBack(2);
s1.PushBack(3);
s1.PushBack(4);
s1.Print();
}
它是没有问题的:
但是如果里面存string类型:
可以看到,它崩溃了,而且打印出来了数据,说明是在析构的时候崩溃,到底怎么回事?
如图,因为使用得是memcpy,所以新开辟的空间的前三个_str和_a指向的空间的每一个_str指向的一样。
这样就导致delete[] _a的时候,新开辟的空间的前三个_str为野指针,必然崩溃。
如果需要解决问题,只要这样做:
for (size_t i = 0; i < _size; i++)
{
newA[i] = _a[i];
}
利用operator=,通过for循环依次赋值,这样新旧_str指向的空间不同,解决了问题。
现在就出现了需要对不同的类型不同对待的情况,因为memcpy的效率比for循环高得多,但是对特殊类型又不得不使用for循环。
使用全特化可以解决:
template <>
class SeqList<int>
{
public:
SeqList()
:_a(NULL)
, _size(0)
, _capacity(0)
{}
void CheckCapacity()
{
if (_size >= _capacity)
{
_capacity = _capacity > 0 ? _capacity * 2 : 3;
int* newA = new int[_capacity];
if (_a)
{
memcpy(newA, _a, _size*sizeof(int));
}
delete[] _a;
_a = newA;
}
}
void PushBack(const int& x)
{
CheckCapacity();
_a[_size++] = x;
}
~SeqList()
{
delete[] _a;
_size = _capacity = 0;
}
private:
int* _a;
size_t _size;
size_t _capacity;
};
template <>
class SeqList<string>
{
public:
SeqList()
:_a(NULL)
, _size(0)
, _capacity(0)
{}
void CheckCapacity()
{
if (_size >= _capacity)
{
_capacity = _capacity > 0 ? _capacity * 2 : 3;
string* newA = new string[_capacity];
if (_a)
{
for (size_t i = 0; i < _size; i++)
{
newA[i] = _a[i];
}
delete[] _a;
}
_a = newA;
}
}
void PushBack(const string& x)
{
CheckCapacity();
_a[_size++] = x;
}
~SeqList()
{
delete[] _a;
_size = _capacity = 0;
}
private:
string* _a;
size_t _size;
size_t _capacity;
};
这不是一个好办法,这样做会让代码的复用性很差,全是重复代码。
现在就该类型萃取出场了:
struct __TrueType//一个空类
{};
struct __FalseType//空类
{};
template <class T>
struct __TypeTraits
{
typedef __FalseType ISPODType; //默认为不是基本类型 POD(基本类型)
};
template <>
struct __TypeTraits<int>
{
typedef __TrueType ISPODType; //int是基本类型
};
template <class T>
T* TypeCopy(T* dst, const T* src, size_t n)
{
return __TypeCopy(dst, src, n, __TypeTraits<T>::ISPODType());
}
template <class T>
T* __TypeCopy(T* dst, const T* src, size_t n,__TrueType)
{
cout << "memcpy()" << endl;
return (T*)memcpy(dst, src, n*sizeof(T));
}
template <class T>
T* __TypeCopy(T* dst, const T* src, size_t n, __FalseType)
{
for (size_t i = 0; i < n; i++)
{
dst[i] = src[i];
}
cout << "operator=()" << endl;
return dst;
}
想要读懂这段代码不容易,我总结一下:
通过TypeCopy,那么就可以根据类型来确定拷贝方案。