元编程之中有两种元数据,一种是类型数据,这里的type traits就是对类型数据的操作或者称为计算。这种技术在STL的设计中使用的非常广泛。本质上是借用C++模版提供的模版形参推导和特化两种机制来实现的。
迭代器
迭代器是联系算法与容器的桥梁,二者彼此独立设计,都使用泛型方法实现。迭代器就是联系他们的胶着剂。以经典的交换函数swap来入手:这个函数是一个模版函数,接收两个迭代器模版参数,将他们所指之物进行交换。
为了得到迭代器所指之物的类型,就需要使用了模版参数的自动推导来实现,因为C++不提供typeof操作,typeid也只能获取类型名称而不能拿来当做变量声明使用。
为了实现异常安全性代码,通常的实现方式是copy-and-swap方式,代码如下:
template<typename Ite1, typename Ite2, typename V>
void swap(Ite1 i1, Ite2 i2, V)
{
V tmp(*i1);
*i1 = *i2;
*i2 = tmp;
}
//调用方式
list<int> a; vector<int> b;
swap(a.begin(), b.begin(), *a);
交换的两个迭代器所指的类型必须相同,上述调用中都是指向int类型的迭代器,迭代器类型可以不同也可以相同。从上述例子中可以看出,swap的第三个模版参数V就是用来进行tmp变量声明使用,并无其他作用,这正是利用了模版参数自动推导实现的。
但是,上述模版函数实现的第三个参数太丑陋。因此,最好将这个类型放到迭代器内部,只需要传入两个迭代器给swap函数。这正是type traits(类型萃取)的来历。
类型萃取
将上述实现代码进行改进,得到如下的经过萃取后的代码:
template<typename Ite>
struct iterator_traits{
typedef typename Ite::value_type value_type;
//其他萃取类型,如reference,pointer等...
};
template<typename Ite1, typename Ite2>
void swap(Ite1 i1, Ite2 i2)
{
typename iterator_traits<Ite1>::value_type
tmp(*i1);
*i1 = *i2;
*i2 = tmp;
}
上述实现中的iterator_traits就是一个萃取迭代器类型的包装模板类,它要求每个迭代器自己定义内嵌的typedef类型定义,支持value_type
、reference等多种类型。
可以看出,iterator_traits
仅仅是将各个迭代器自定义的内嵌类型做了一次转接或者包装,我们可以不用这个包装,而直接使用迭代器的内嵌类型也是可以的:
template<typename Ite1, typename Ite2>
void swap(Ite1 i1, Ite2 i2)
{
typename Ite1::value_type tmp(*i1);
*i1 = *i2;
*i2 = tmp;
}
但是,对于原生指针、引用等类型,是无法使用上述方式进行的,这就体现了使用iterator_traits
包装的作用,因为模板类可以进行偏特化,专门针对引用、指针等实现特化版本。
template<typename Ite>
struct iterator_traits<Ite*>{ //针对原生指针的偏特化
typedef Ite value_type;
typedef Ite * pointer;
typedef Ite & reference;
typedef ptrdiff_t difference_type;
//...
};
template<typename Ite>
struct iterator_traits<const Ite*>{ //针对常量指针的偏特化
typedef Ite value_type;
typedef Ite * pointer;
typedef Ite & reference;
typedef ptrdiff_t difference_type;
//...
};
高效swap
除了前面所述的对迭代器所指类型的萃取之外,还有一个问题就是,如果某些迭代器所指类型是很大的对象,创建一个临时变量并进行拷贝会非常耗时,需要进行动态内存分配和释放。这里就有一个是否是通过pimpl手法实现的问题,如果是就只需要交换简单的指针而不用创建对象,而交换指针只需要用标准库提供的std::swap函数进行高效交换即可。因此,这里需要使用偏特化来实现条件选择,如果两个迭代器类型相同,而且是所指类型是指针或者引用就可以进行高效的交换。
首先是判断迭代器类型以及所指类型,这就是用偏特化来实现if-else语句。
template<typename T, typename U>
struct is_same{
const static int value = 0;
};
template<typename T>
struct is_same<T, T>{
const static int value = 1;
};
template<typename T>
struct is_pointer{
const static int value = 0;
};
template<typename T>
struct is_pointer<T *>{
const static int value = 1;
};
//is_reference与上述实现类似
这样就可以对swap模版函数进行高效实现,其中对使用两个版本的函数又使用全特化进行了一次转发。
template<typename Ite1, typename Ite2>
void swap(Ite1 i1, Ite2 i2){
typedef typename iterator_traits<Ite1> itetraits1;
typedef typename iterator_traits<Ite2> itetraits2;
typedef typename itetraits1::value_type v1;
typedef typename itetraits1::reference_type r1;
typedef typename itetraits1::value_type v1;
typedef typename itetraits1::reference_type r2;
const bool tag = is_same<v1, v2> &&
is_reference<r1> && is_reference<r2>;
swap_impl<tag>(i1, i2);
}
template<bool tag>
struct swap_impl{
template<typename Ite1, typename Ite2>
swap_impl(Ite1 i1, Ite2, i2){
typename iterator_traits<Ite1>::value_type tmp(*i1);
*i1 = *i2;
*i2 = tmp;
}
};
template<>
struct swap_impl<true>{
template<typename Ite1, typename Ite2>
swap_impl(Ite1 i1, Ite2, i2){
std::swap(*i1, *i2);
}
};
上述计算tag就是判断,当两个迭代器所指类型相同,且所指对象的引用为真的引用时,可以不用建立临时对象,进行高效交换操作即可。
Type Traits
上述实现的对迭代器的所指类型的特性萃取,以及所指类型是否一样、是否是真的引用类型等实现,就是type traits。这是一种通过C++的模版进行模版实参推演在编译阶段实现的,巧妙利用全特化、偏特化,本质上就是使用元编程实现了条件语句,如果是上述的tag或者is_same
这种两种结果的就是if-else语句,如果条件多于两个,如iterator_traits
针对指针、引用、常量引用等多种特化版本,那么就是switch语句的实现相对应。据此,就可以很容易的理解各种type_traits
的原理并进行运用了。