笔者最近在分析STL系列源码,所取用的源码标准为SGI-STL 2.9.0。笔者并不打算详细分析STL的内部数据结构,因为数据结构这个课程为大学所学,笔者并不打算当一次复读机。
笔者个人认为分析STL源码最大的铁板为迭代器,因为迭代器是STL容器和算法两大部件之间的桥梁,迭代器并不能单独存在,它必须要依托特定的容器对象。依照笔者个人理解,容器主要提供可供操作的数据和对象,而算法是对这组数据和对象的加工,比如说std::sort(); std::find();这两个算法需要一组数据,或者排序操作,或者查找操作。例如 std::vector<int> vc; 需要去查找这个容器里面是否出现了一个数据value,std::find(vc.begin(),vc.end(),value); =======>个人觉得是算法需要向容器要一组数据,告诉算法这组数据的范围。而这个范围需要迭代器划出。
言归正传,结合源码分析,我选取了std::list作为分析对象,重点讲解list中的迭代器是如何当作桥梁使用的。
/**************************************
* list的node节点,这个节点中有一个向前指和向后指向的指针,但是为什么不是指向node???
* 却是一个指向为void的指针
*
*/
template <class T>
struct __list_node {
typedef void* void_pointer;
void_pointer next;
void_pointer prev;
T data;
};
/**************************************
* list中的迭代器
* 必须关注的五个要素:
*
* value_type
*
*
*/
/**************************************
* list迭代器是一个模板类,它的行为有点类似于指针,它有指针的所有特性,但是它还有一些其他的作用
* 所以 重载== ,!= ,* ++ ,--
*
*/
template<class T, class Ref, class Ptr>
struct __list_iterator {
typedef __list_iterator<T, T&, T*> iterator;
typedef __list_iterator<T, const T&, const T*> const_iterator;
typedef __list_iterator<T, Ref, Ptr> self;
typedef bidirectional_iterator_tag iterator_category;
typedef T value_type;
typedef Ptr pointer;
typedef Ref reference;
typedef __list_node<T>* link_type;
typedef size_t size_type;
typedef ptrdiff_t difference_type;
link_type node;
__list_iterator(link_type x) : node(x) {}
__list_iterator() {}
__list_iterator(const iterator& x) : node(x.node) {}
bool operator==(const self& x) const { return node == x.node; }
bool operator!=(const self& x) const { return node != x.node; }
reference operator*() const { return (*node).data; }
#ifndef __SGI_STL_NO_ARROW_OPERATOR
pointer operator->() const { return &(operator*()); }
#endif /* __SGI_STL_NO_ARROW_OPERATOR */
self& operator++() {
node = (link_type)((*node).next);
return *this;
}
self operator++(int) {
self tmp = *this;
++*this;
return tmp;
}
self& operator--() {
node = (link_type)((*node).prev);
return *this;
}
self operator--(int) {
self tmp = *this;
--*this;
return tmp;
}
};
由结构体__list_iterator可见,一个迭代器必须关注五个元素,iterator_category、value_type、pointer、reference、size_type、difference_type. 其中pointer和reference只是留作扩充性质,SGI_STL 2.9.0版本里面没有涉及到。
由__list_iterator结构我们可以初步窥探出一些门径,就是迭代器必须要配合容器的属性,重载一些操作符,例如双向链表++操作,必须要获取当前节点向后指向的指针,才可以知道下一个结点的地址,而类似与vector这种数组结构的容器没有此项讲究。另外就是五个特性,笔者精力有限,重点分析一下value_type。
先不说话,直接看一段代码:
template<typename T>
class MyIterator
{
typedef T value_type;
}
如上所示,如果我们要获取这个迭代器所指向类型,MyIterator::value_type即可。
这个时候,我们设计一个算法,用上面的信息
template<typename T>
typename MyIterator<T>::value_type Foo(MyIterator<T> & i)
{
}
当然还可以这样写
template<typename I>
typename I::value_type Foo(I& i)
{
}
嗯嗯,似乎全部问题得到解决,你似乎可以把任何元素的类型传入进去了,但是,等等!!!事情似乎还没完,我们似乎还不能得到原生指针类型的返回对象,但是一个迭代器必须支持原生指针。
我们似乎不可以直接通过内嵌性得到原生指针的返回值,只有加入一个中间类的方式解决这个问题。
template <typename T>
class Traits
{
typedef typename T::value_type value_type;
};
这样,我们可以通过 Traits<myIterator>::value_type 来获得myIterator的value_type,于是我们把Foo函数改写成:
template <typename I> //这里的I可以是任意类型的迭代器
typename Traits<I>::value_type Foo(I i)
{
...
}
然而,即使这样,那个原生指针的问题仍然没有解决,因为Trait类一样没办法获得原生指针的相关信息。于是我们祭出C++的又一件利器--偏特化(partial specialization):
template <typename T>
class Traits<T*> //注意 这里针对原生指针进行了偏特化
{
typedef typename T value_type;
};
通过上面这个 Traits的偏特化版本,我们陈述了这样一个事实:一个 T* 类型的指针所指向的元素的类型为 T。
如此一来,我们的 Foo函数就完全可以适用于原生指针了。比如:
int * p;
....
int i = Foo(p);
Traits会自动推导出 p 所指元素的类型为 int,从而Foo正确返回。
过程:内嵌型别->traite类->模板偏特化=>可萃取原生指针的value type。