STL中的Traits编程技法

时间:2023-12-10 10:44:50

  最近在看读《STL源码剖析》,看到Traits编程技法这节时,不禁感慨STL源码作者的创新能力。那么什么是Traits编程技法呢?且听我娓娓道来:

  我们知道容器的许多操作都是通过迭代器展开的。其中容器类似于数组,迭代器类似于指针。我们用数组来写个例子:

 int arr[] = {,,,,};
int *p;
p = &arr[];

  假设,我将第1、2遮挡起来,问你p所指向的对象arr[2]是什么类型,你恐怕无法回答。因为它可以是int,char,float甚至你自己定义的类型。假设我们现在是个容器:

 list<int> lit(,);
list<int>::iterator it1,it2;
it1 = lit.begin();
it2 = lit.end();

  其中lit是个容器对象,it1和it2都是容器的迭代器。假设现在有个函数,他接受两个迭代器作为参数,并且返回类型是迭代器所指的类型:

 template<class iterator>
返回类型
fun(iterator first,iterator last)
{
//函数体
}

  问题来了,现在我的返回类型假设是iterator所指向的类型。我们无法进行下去了。。。别怕,我们接着往下看:

  如果我们将迭代器定义成一个类:

template<class T>
class My_iterator
{
public:
T* ptr;
typedef T value_type;
My_iterator(T* p = ):ptr(p){}
//...
};

  也就是说如果我们的list的迭代器的类型也是上面那种形式,那么我们的fun函数就可以这样写了:

template<class iterator>
typename iterator::value_type  //返回类型
fun(iterator first,iterator last)
{
//函数体
}

  这样,迭代器所指的容器元素的类型就可以取出来了。怎么样,是不是很cool!但是不是所有的迭代器都是一个类啊,比如我们的原生指针也是迭代器的一种。vector的迭代器就是原生指针。那么用上面的那种方法似乎就不行了。但是STL的编写者创造了一个很好的方法---迭代器类型萃取模板,可以萃取原生指针所指向的元素的类型。对了,什么是原生指针?就是那种真正的是一个指针,我们的许多容器(list,deque)的迭代器是模拟指针但实际上它不是真正意义上的指针,他是一个类里面封装了原生指针。上面的My_iterator是迭代器,My_iterator里面的成员ptr就是原生指针。现在,是Traits编程技法发挥作用的时候了:

  如果我们有个迭代器类型萃取模板,如下:

template<clas iterator>    //专门用来萃取迭代器iterator指向的元素的类型
class My_iterator_traits
{
typedef typename iterator::value_type value_type;
//...
};

  于是,我们的fun()函数就可以写成这样:

template<class iterator>
typename My_iterator_traits<iterator>::value_type  //返回类新
fun(iterator first,iterator last)
{
//函数体
}

  明眼人一眼就能看出这个代码跟原来的相比除了多一层包装,好像什么实质意义也没有,别急。我们这样写并不是做无用功,是为了应付上面说的原生指针而设计的。这时,我们的偏特化要派上用场了,针对原生指针的迭代器类型萃取偏特化模板如下:

template<clas iterator>    //专门用来萃取迭代器iterator指向的类型的
class My_iterator_traits<iterator*>
{
//typedef typename iterator::value_type value_type;
typedef T value_type; //注意与上面的区别
//...
};

  怎么样,这样一个迭代器类型萃取模板就这样诞生了。它可以萃取自定义的iterator类型和原生指针类型。但是,请注意了:在萃取自定义的iterator的时候这样写:

  typedef typename iterator::value_type value_type;所以我们自定义的迭代器都应该定义时都应该有typedef T value_type.否则,你的迭代器就不能被正确萃取。STL的迭代器在定义的时候也是有类似的限制的!