本节条款的题目:请使用trait classes来表示类型信息
本节条款主要讲述的技术是如何在编译期间实现对迭代器类型的判断,根据判断的类型进行最优处理。
我们先来看一下迭代器的种类:
1.input_iterator:只读,只能逐个前移
2.output_iterator:只写,只能逐个前移
3.forward_iterator:可读可写,只能逐个前移
4.bidirectional_iterator:可读可写,支持逐个前移和后移
5.random_access_iterator:可读可写,支持随机访问(任意步数移动)
假设一下,如果有一个函数用于对迭代器指定移动次数d,我们如何实现这个函数,以达到最理想的状态。
我们肯定会处理迭代器是random access类型的情况和不是random access类型的情况,当时random access的时候,直接加减迭代器在常数时间内完成,如果不是,只能一步一步向前或向后移动。
如下代码:
template<typename Iter, typename DistT>
void advance(IteT& iter,DistT d)
{
if(iter is a random access iterator)
iter+=d;
else
{
if(d>=0)
while(d--) ++iter;
else
while(d++) --iter;
}
}
可是,我们怎么才能做到“iter is a random access iterator”在编译期间的实现?
实现这个功能,需要以下几步,并且相对繁杂的几步。
首先为每一种迭代器设置struct标签,每个struct都是空的结构体,如下代码:
struct input_iterator_tag{};
struct output_iterator_tag{};
struct forward_iterator_tag: public input_iterator_tag{};
struct bidirectional_iterator: public forward_iterator_tag{};
struct random_access_iterator: public bidirectional_iterator_tag{};
接着,我们就要引入traits,traits是一种技术,我们通过程序来看一下这是什么技术,有什么用。
我们来看一下关于各种容器的定义:
template class <T>
class vector
{
public:
class iterator
{
public:
typedef random_access_iterator iterator_category;
…
}
…
}
template class <T>
class list
{
public:
class iterator
{
public:
typedef bidirectional_iterator iterator_category;
…
}
…
}
从上面代码可以看出,每个容器中都有一个iterator类,可是每个iterator类中的迭代器标签却不一样。
当我们调用语句vector<int>::iterator::iterator_category
时会返回random_access_iterator类的类型,当我们调用list<int>::iterator::iterator_category
时会返回bidirectional_iterator类的类型。
我们再来看一下有关iterator_traits结构体的定义,
template<typename IterT>
struct iterator_traits{
typedef typename IterT::iterator_category iterator_category;//typedef typename的使用见条款42
};
如果我们通过语句iterator_traits<vector<int>::iterator>::iterator_category
调用就会返回有关vector<int>
迭代器iterator的类型,该语句返回random_access_iterator类型。
同理iterator_traits<list<int>::iterator>::iterator_category
该语句返回bidirectional_iterator类型。
我们这个时候可以实现有关void advance函数的定义了,大家看下面的代码:
template<typename IterT, typename DistT>
void advance(IterT& iter,DisT d)
{
if(typeid(typename std::iterator_traits<IterT>::iterator_category)==
typeid(std::random_access_iterator_tag))
……
}
通过语句advance(vector<int>::iterator iter,2)
我们可以看一下实例化后的代码:
void advance(iter,2)
{
if(typeid(std::iterator_traits<vector<int>::iterator>::iterator_category)==
typeid(std::random_access_iterator_tag)){.....}
else
{...}
}
我们来分解一下语句typeid(std::iterator_traits<vector<int>::iterator>::iterator_category)
其中vector<int>::iterator
返回vector的迭代器iterator,iterator_traits<vector<int>::iterator>::iterator_category
返回关于int型vector的迭代器类类型,此时返回random_access_iterator类型。
可是,还有一个更根本的问题:IterT类型在编译期间获知,所以iterator_traits::iterator_category在编译期间确定。但是if语句却是在运行期间核定。可以在编译期间完成的事情推到运行期间,这不仅浪费时间,还造成执行文件膨胀。所以我们采用另一种方式来避免这种问题。另一种方式就是函数重载,我们来定义一系列的重载函数,如下代码:
template<typename IterT, typename DisT>
void doAdvance(IterT& iter, Dist d, std::random_access_iterator_tag)
{
iter+=d;
}
template<typename IterT, typename DisT>
void doAdvance(IterT& iter, Dist d, std::bidirectional_iterator_tag)
{
if(d>=0)
while(d--) ++iter;
else
while(d++) --iter;
}
template<typename IterT, typename DisT>
void doAdvance(IterT& iter, Dist d, std::input_iterator_tag)
{
if(d<0)
throw std::out_of_range("Negative distance");
while(d++) --iter;
}
template<typename IterT,typename DistT>
void advance(IterT& iter,DistT d)
{
doAdvance(iter,d,typename::std::iterator_traits<IterT>::iterator_category();
}
由于重载函数的确定是在编译期间,所以当我们再次调用advance(vector<int>::iterator&iter,2)
时,我们看一下实例后的代码:
void advance(iter,2)
{ doAdvance(iter,2,typename::std::iterator_traits<vector<int>::iterator>::iterator_category;
}
这时typename::std::iterator_traits<vector<int>::iterator>::iterator_category;
编译期间返回random_access_iterator类类型。所以调用void doAdvance(IterT& iter, Dist d, std::random_access_iterator_tag)
版本的重载函数。
以上就是本条款的主要内容。
总结:
1. Traits class使得类型相关信息可以在编译期可用,它们以template和template特化完成实现;
2. 整合重载技术后,traits classes有可能在编译期对类型执行if-else测试。