类型萃取有什么用?
类型萃取一般用于模板中,当我们定义一个模板函数后,需要知道模板类型形参并加以运用时就可以用类型萃取。
比如我们需要在函数中进行拷贝,通常我们可以用内置函数memcpy或者自己写一个for循环来进行拷贝。比如:
template<typename T>
void Copy(T *dest, const T *src, size_t sz)
{
if (_TypeTraits<T>::_IsPODType().Get())
{
cout << "内置类型用memcpy函数进行拷贝:" << endl;
memcpy(dest, src, sz * sizeof(T));
}
else
{
cout << "自定义类型用for循环进行拷贝:" << endl;
for (size_t i = 0; i < sz; i++)
{
*(dest + i) = *(src + i);
}
}
}
这是一个通用的拷贝函数,可以拷贝多种类型的数据,比如内置类型的int、double等和自定义结构体或类类型。我们可以看到其中用了两种拷贝方式—memcpy和for循环,为什么这里要用两种拷贝方式呢?
我们知道memcpy进行拷贝时效率很高,但是它是将所拷贝对象中的值全部原样地复制到目标对象中去,那么如果我们在对带指针的自定义对象进行拷贝时就会把源对象指针值拷贝到目标对象内指针内去,这样就造成了多个对象的指针指向同一块内存,这样就成了浅拷贝了。这里为了避免浅拷贝的发生就特意用效率相对较低的for循环来对自定义类型的对象进行拷贝工作。因此这个拷贝函数里面就给出了两种拷贝方式。
搞清楚了为什么要用两种拷贝方式,那么重点来了—怎么分辨需要拷贝的对象是内置类型还是自定义类型呢?方法有很多,比如我们可以在一个函数里面罗列出各种内置类型,将模板函数的类型形参T满足传入这个函数,当满足罗列出的类型时就用返回一个true否则返回false。这是一种方法,但是这里我们给出另一种方法—类型萃取。
类型萃取解析
先给出代码:
struct _TrueType
{
bool Get()
{
return true;
}
};
struct _FalseType
{
bool Get()
{
return false;
}
};
template<typename T>
struct _TypeTraits
{
typedef _FalseType _IsPODType;
};
template<>
struct _TypeTraits<int>
{
typedef _TrueType _IsPODType;
};
解析:这里给出了四个结构体(ps:结构体默认访问类型为public比默认类型为private的类方便),前两个结构体一个为_TrueType表示内置类型,一个为_FalseType表示自定义类型,两个结构体内都定义了一个返回类型为bool类型的函数Get(),其中一个返回true一个返回false。第三个为结构体模板,模板内将结构体类型_FalseType重定义为_IsPODType(ps:在类或者结构体内对类型重定义后,重定义的类型属于这个类或结构体的成员类型,其作用域在这个类或结构体里面),当我们用这个结构体内的_IsPODType类型定义一个对象来调用函数Get()时就会返回一个false,表示T类型为自定义类型。第四个结构体是对第三个结构体模板的特化,这里特化成为int类型,当然也可以特化为double或者char类型,这个结构体里面将结构体类型_TrueType重定义为_IsPODType,当传入的参数为int类型时用_IsPODType定义对象就可以调用_TrueType结构体内的Get函数,返回一个true,表示传入了内置类型。
进行测试: 对这段代码作了解释后,来结合上面的Copy函数运行一下,看能不能对内置类型和自定义类型的参数执行不同的拷贝方式。我们先调用Copy函数拷贝内置类型:
int main()
{
//首先进行内置类型的拷贝
int array1[10] = { 0,1,2,3,4,5,6,7,8,9 };
const size_t size = sizeof(array1) / sizeof(array1[0]);
int array2[size] = { 1,2,3 };
Copy(array2, array1, size);
for (size_t i = 0; i < size; i++)
{
cout << array2[i] << " ";
}
cout << endl;
return 0;
}
这里定义了两个int类型的数组,程序执行的操作是将数组array1内的数据拷贝到array2中去并对数组array2进行打印,我们的设计应该是用memcpy函数进行拷贝。来看看执行结果:
可以看到执行结果明显符合我们的设计。
那么现在来对自定义类型的对象进行拷贝:
int main()
{
//进行自定义类型string类对象的拷贝
string s1[] = { "11111","22222","33333","44444","55555" };
const size_t size = sizeof(s1) / sizeof(s1[0]);
string s2[size] = { "abcd" };
Copy(s2, s1, size);
for (size_t i = 0; i < size; i++)
{
cout << s2[i] << endl;
}
return 0;
}
这里定义两个string类对象数组s1和s2,程序进行将s1中的数据拷贝到s2中去的工作,按照我们的设计这里会调用for循环进行拷贝。我们来看结果:
可以看到,符合我们的预期结果,调用了for循环。
有时候我们需要对指针类型的对象进行类型萃取,可以用这个结构体模板:
template<typename T>
struct _TypeTraits<T*>
{
typedef _TrueType _IsPODType;
};
因为所有的指针都用4个字节存储,且不会造成浅拷贝的问题,其实际上属于内置类型。
给出其他内置类型的特化:
struct _TypeTraits<short>
{
typedef _TrueType _IsPODType;
};
struct _TypeTraits<long>
{
typedef _TrueType _IsPODType;
};
struct _TypeTraits<long long>
{
typedef _TrueType _IsPODType;
};
struct _TypeTraits<unsigned>
{
typedef _TrueType _IsPODType;
};
template<>
struct _TypeTraits<char>
{
typedef _TrueType _IsPODType;
};
struct _TypeTraits<unsigned char>
{
typedef _TrueType _IsPODType;
};
template<>
struct _TypeTraits<double>
{
typedef _TrueType IsPODType;
};
struct _TypeTraits<float>
{
typedef _TrueType _IsPODType;
};
struct _TypeTraits<bool>
{
typedef _TrueType _IsPODType;
};
类型萃取的基本原理与简单测试讲到这里,还有很多巧妙的用法可自行挖掘!