不论是泛型思维还是STL的实际运用,迭代器都扮演着重要的角色。迭代器是一种行为类似指针的对象,在算法中运用迭代器时,很可能会用到其相关类型。其中的相关类型就包括迭代器所指的对象的类型。假设算法中有必要声明一个变量,其类型就是迭代器所指的对象的类型,但是输入参数却只有迭代器改怎么办?
c++并没有支持typeof()!
我们假设现在要实现一个算法func,其出入参数为迭代器
void func(Iter iter)
{
//如果我们需要一个iter指向的类型的变量,
//该怎么声明呢?
}
利用函数模板的参数推导机制(argument deducation)
template
void fun_impl(I iter, T t)
{
T tmp; //这里已经申请好了需要的变量
//剩余的内容
//...
}
于是,func函数就可以这么写:
void func(Iter iter)
{
func_impl(iter, *iter); //将*iter传进去后,就知道了iter指向的类型
}
func函数将实际操作全部置于func_impl()之中,由于func_impl是一个函数模板,一旦被调用,编译器会自动进行template参数推导,于是导出类型T。
但是,当我们需要func的返回值是*iter的类型而不是void呢,我们连函数声明都写不出来!
返回值类型(不知道怎么写) func(Iter iter)
{
func_impl(iter, *iter);
...
}
注意:参数推导机制无法推导返回值
template
struct Iter{
typedef T value_type;
//...
}
template
typename I::value_type func(I iter)
{
return *iter;
}
这样就成功写出了func函数。
看起来不错,但是却有个隐晦的陷阱:并不是所有迭代器都是class type。原生指针就不是!如果不是class type,就无法为它定义内嵌类型。但STL(以及整个泛型思维)绝对必须接受原生指针作为一种迭代器,所以上面这样还不够,有没有办法可以让上述的一般化概念针对特殊情况做特殊化处理呢?template partial specialization(偏特化)可以做到。
下面这个类模板专门用来萃取迭代器的特性,而value_type正是迭代器的特性之一。
template
struct iterator_traits
{
typedef typename I::value_type value_type;
//...
}
我们可以通过这个萃取器萃取类型I的特性,换句话说,如果类型I定义了自己的value_type,那么iterator_traits::value_type就是I::value_type。
如方法二定义的Iter结构,Iter中定义了自己的value_type,那么func就可以改写成:
template
typename iterator_traits::value_type func(I iter)
{
return *iter;
}
但这除了多了一个中间层iterator_traits,又带来什么好处呢?
好处就是,iterator_traits可以拥有特化版本。现在,我们令iterator_traits拥有一个partial specializations如下:
template
struct iterator_traits //偏特化版,迭代器是个原生指针
{
typedef T value_type;
};
于是当我们调用func传入的是原生指针时,通过偏特化版本同样可以萃取到value_type。
到此,我们就基本解决了迭代器的相关类型的问题了!
根据经验, 最常用到的迭代器相关类型有五种:value_type, difference_type, pointer, reference, iterator_catagoly。如果你希望你所开发的容器能与STL水乳交融,一定要为你的容器的迭代器定义这五种类型。具体内容参照STL源码剖析的相关章节。