【STL 源码剖析】浅谈 STL 迭代器与 traits 编程技法 (3)

所谓特化,就是特殊情况特殊处理,第一个类为泛化版本,T 可以是任意类型,第二个类为特化版本,是第一个类的特殊情况,只针对原生指针。

5.1、原生指针怎么办?——特性 “萃取” traits

还记得前面说过的参数推导机制+内嵌型别机制获取型别有什么问题吗?问题就在于原生指针虽然是迭代器但不是class ,无法定义内嵌型别,而偏特化似乎可以解决这个问题。

有了上面的认识,我们再看看 STL 是如何应用的。STL 定义了下面的类模板,它专门用来“萃取”迭代器的特性,而value type 正是迭代器的特性之一:

traits 在 bits/stl_iterator_base_types.h 这个文件中:

template<class _Tp> struct iterator_traits<_Tp*> { typedef ptrdiff_t difference_type; typedef typename _Tp::value_type value_type; typedef typename _Tp::pointer pointer; typedef typename _Tp::reference reference; typedef typename _Tp::iterator_category iterator_category; }; template<typename Iterator> struct iterator_traits { //类型萃取机 typedef typename Iterator::value_type value_type; //value_type 就是 Iterator 的类型型别 }

加入萃取机前后的变化:

template<typename Iterator> //萃取前 typename Iterator::value_type func(Iterator iter) { return *iter; } //通过 iterator_traits 作用后的版本 template<typename Iterator> //萃取后 typename iterator_traits<Iterator>::value_type func(Iterator iter) { return *iter; }

看到这里也许你会问了,这个萃取前和萃取后的 typename :iterator_traits::value_type 跟 Iterator::value_type 看起来一样啊,为什么还要增加 iterator_traits 这一层封装,岂不是多此一举?

回想萃取之前的版本有什么缺陷:不支持原生指针。而通过萃取机的封装,我们可以通过类模板的特化来支持原生指针的版本!如此一来,无论是智能指针,还是原生指针,iterator_traits::value_type 都能起作用,这就解决了前面的问题。

//iterator_traits的偏特化版本,针对迭代器是原生指针的情况 template<typename T> struct iterator_traits<T*> { typedef T value_type; };

看到这里,我们不得不佩服的 STL 的设计者们,真·秒啊!我们用下面这张图来总结一下前面的流程:

stl-iterator-09.png

5.2 、const 偏特化

通过偏特化添加一层中间转换的 traits 模板 class,能实现对原生指针和迭代器的支持,有的读者可能会继续追问:对于指向常数对象的指针又该怎么处理呢?比如下面的例子:

iterator_traits<const int*>::value_type // 获得的 value_type 是 const int,而不是 int

const 变量只能初始化,而不能赋值(这两个概念必须区分清楚)。这将带来下面的问题:

template<typename Iterator> typename iterator_traits<Iterator>::value_type func(Iterator iter) { typename iterator_traits<Iterator>::value_type tmp; tmp = *iter; // 编译 error } int val = 666 ; const int *p = &val; func(p); // 这时函数里对 tmp 的赋值都将是不允许的

那该如何是好呢?答案还是偏特化,来看实现:

template<typename T> struct iterator_traits<const T*> { //特化const指针 typedef T value_type; //得到T而不是const T } 6、traits编程技法总结

通过上面几节的介绍,我们知道,所谓的 traits 编程技法无非 就是增加一层中间的模板 class,以解决获取迭代器的型别中的原生指针问题。利用一个中间层 iterator_traits 固定了 func 的形式,使得重复的代码大量减少,唯一要做的就是稍稍特化一下 iterator_tartis 使其支持 pointer 和 const pointer 。

【STL 源码剖析】浅谈 STL 迭代器与 traits 编程技法

#include <iostream> template <class T> struct MyIter { typedef T value_type; // 内嵌型别声明 T* ptr; MyIter(T* p = 0) : ptr(p) {} T& operator*() const { return *ptr; } }; // class type template <class T> struct my_iterator_traits { typedef typename T::value_type value_type; }; // 偏特化 1 template <class T> struct my_iterator_traits<T*> { typedef T value_type; }; // 偏特化 2 template <class T> struct my_iterator_traits<const T*> { typedef T value_type; }; // 首先询问 iterator_traits<I>::value_type,如果传递的 I 为指针,则进入特化版本,iterator_traits 直接回答;如果传递进来的 I 为 class type,就去询问 T::value_type. template <class I> typename my_iterator_traits<I>::value_type Func(I ite) { std::cout << "normal version" << std::endl; return *ite; } int main(int argc, const char *argv[]) { MyIter<int> ite(new int(6)); std::cout << Func(ite)<<std::endl;//print=> 6 int *p = new int(7); std::cout<<Func(p)<<std::endl;//print=> 7 const int k = 8; std::cout<<Func(&k)<<std::endl;//print=> 8 }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wspdfd.html