Traits编程技法:
1、 关于模板的参数推导
此例中以func为对外接口,却把实际操作全部放在func_impl中,由于func_impl是一个function template,一旦被调用,编译器会自动进行template参数推导,于是导出型别T,顺利解决问题
迭代器所指对象的型别,称为该迭代器的 value type .上述的参数型别推导虽可用,但不是全面可用。 万一value type必须用于函数的传回值,就束手无策了。因为该法推导的只是参数,而不能用于函数返回值类型
于是尝试使用内嵌型别:
可以看到,通过内嵌型别的声明我们得到了希望的返回类型。
注意的是,func的返回型别必须加上关键词typename,因为T是一个template参数,在它被编译器具体化之前,编译器对T一无所知,换句话说,编译器此时并不知道MyIter<T>::value_type代表的是一个型别或是一个member 或是一个data member。关键词typename告诉编译器这是一个型别,如此才能顺利通过编译
如此一来,看起来不错,但不是所有的迭代器都是class type。比如原生指针。既然不是class type,那就无法为它定义内嵌型别,但我们又必须能接受原生指针,下面看看如何处理。
偏特化
如果class template 拥有一个以上的template参数,我们可以针对其中某个(或数个,但不是全部)template参数进行偏特化操作。换句话说,我们可以在泛化中提供一个特化版本(也就是将泛化版本中的template参数赋予明确的指定)
假如有一个 class template如下:
Template<typename U, typename V, typenameT>
Class C{…}
偏特化的字面理解很容易误导
所谓偏特化,其实是提供另一份template的定义式,而起本身仍然是templatized
或者说针对template参数更进一步的条件限制设计出来的特化版本
面对下面这个class template
Template<typename T>
Class C{…}; //这个泛化版本允许(接受)T为任何类型
一个偏特化版本是:
Template<typename T>
Class C<T*>{…};
这个特化版本仅适用于“T为原生指针”的情况
“T为原生指针”即为“T为任何类型”的更进一步的限制
于是,我们就可以解决上面遇到的问题了。
现在,我们可以针对“迭代器的template参数为指针”者设计特化版的迭代器
下面这个class template是专门用来“萃取”迭代器的特性,value type正是迭代器的特性之一
Template<class I>
Struct iterator_traits{
typedef typename I::value_type value_type;
}
大意是,如果I定义有自己的value_type, 那么通过这个trait的作用,萃取出来的value_type就是I::value_type。
换句话说,如果I定义有自己的value_type,先前那个func就可以写成这样:
Template<class I>
Typename iterator_traits<I>::value_type
Func(Iite)
{
Return *ite;
}
与之前的相比较,是多了一层间接性,好处却大大增加了。因为trait可以拥有特化版本,如下,我们新增特化版本:
Template<class T>
struct iterator_traits<T*>{
typedef T value_type;.
}
比如传入的是int *类型的,那么value_type就是int 类型的了
于是,虽然原生指针不是class type,但我们依旧处理了他。
针对“指向常数对象的指针”,比如 iterator_traits<const int *>::value_type得到的类型是const int 类型而非int ,这是无法赋值的暂时变量,毫无作用,因此假如传入的是这种类型的参数,我们应该将其变为non-const类型的,于是
Template<class T>
Struct iterator_traits<const T*>{
typedef T value_type; //于是萃取出来的就是T了而非const T
}
最常用的迭代器型别有以下五种,若希望开发的容器与STL交融,那么就一定要为容器定义下列型别:
template <class Iterator>
struct iterator_traits {
typedef typename Iterator::iterator_category iterator_category;
typedef typename Iterator::value_type value_type;
typedef typename Iterator::difference_type difference_type;
typedef typename Iterator::pointer pointer;
typedef typename Iterator::reference reference;
}
我们来深入讨论迭代器的相应型别:
1、value_type
即容器中的 typedef T value_type这句话
指的是迭代器所指对象的型别
2、difference type
表示两个迭代器之间的距离,它也可以用来表示一个容器的最大容量,因为对于连续空间而言,头尾之间的距离就是其最大容量
提供计数功能的函数count,其传回值就必须使用differnce type
template <class InputIterator, class T>
typename iterator_traits<InputIterator>::difference_type
count(InputIteratorfirst, InputIterator last, const T& value) {
typename iterator_traits<InputIterator>::difference_type n= 0;
for ( ; first != last; ++first)
if (*first == value)
++n;
return n;
}
针对原生指针,则使用内建类型:typedef ptrdiff_t difference_type;
5、iterator_category
C++中迭代器被分为五类:
Input Iterator:
不允许外界改变,只读(read only)
Output Iterator
只写(write only)
Forward Iterator
只能往前进,允许写入型算法,在此种迭代器形成的区间上进行读写操作
Bidirectional Iterator
可双向移动
RandomAccess Iterator
前三种迭代器支持operator++, 第四种迭代器加上支持operator--, 这最后一种涵盖所有算数能力,包括 p+n, p-n, p[n] ,p1-p2, p1<p2
强化关系如下:
根据这里的关系,书上提到,在研究STL的过程中,效率永远是重要课题。假设有个算法可以接受Forward Iterator,但是传入的是RandomAccess Iterator,它当然也能接受,但是可用并不代表最佳!
拿advance举例,以下并不是源码,是我们自己的针对不同类型迭代器的函数:
template <class InputIterator, class Distance>
inline void__advance_II(InputIterator& i, Distance n) {
while (n--) ++i;
}
template <classBidirectionalIterator, class Distance>
inline void __advance_BI(BidirectionalIterator&i, Distance n) {
if (n >= 0)
while (n--) ++i;
else
while (n++) --i;
}
template <class RandomAccessIterator,class Distance>
inline void __advance_RI(RandomAccessIterator&i, Distance n) {
i += n;
}
对于三种不同的迭代器(说是这么说,但其实三个函数类型是相同的,都是template,等待将来的输入决定),分别采用了不同的算法,算法就不解释了,一眼就看明白了
当程序调用advance的时候,应该选用哪种方法呢?对于不同的迭代器,如果调用不匹配的算法,那么可能造成错误或者效率大减,下面是一种处理方法:
template <class InputIterator, class Distance>
voidadvance(InputIterator i, Distance n)
{
If(is_random_access_iterator(i))
advance_RAI();
else if (is_bidirectional_iterator(i))
advance_BI(i);
else
advance_II(I, n)
}
不过,此种在运行时才决定使用哪个版本会影响后序的效率,最好是能够在编译器就决定使用哪个版本,重载可以解决这个问题
这里谈到的三个算法函数都有两个参数,型别都是未定的,无法成为重载函数。为了令其同名形成重载函数,我们必须加上一个型别已经确定的函数参数,使他们重载
设计如下考虑:
如果traits有能力萃取出迭代器的种类,我们便可以利用此类型作为每个advance算法的第三个参数。这个型别必须是一个class type, 不能只是数字之类,因为编译器需要仰赖他们进行重载决议
下面是五种迭代器的型别:
struct input_iterator_tag{};
struct output_iterator_tag{};
structforward_iterator_tag : public input_iterator_tag {};
structbidirectional_iterator_tag : public forward_iterator_tag {};
struct random_access_iterator_tag: publicbidirectional_iterator_tag {};
这些都是用作标记的,不需要内容,只是为何继承以后在谈到。
经过添加第三个参数,且处理成为重载,三个算法变为了如下所示:
template <class InputIterator, class Distance>
inline void__advance(InputIterator& i, Distance n, input_iterator_tag) {
while (n--) ++i;
}
template <classBidirectionalIterator, class Distance>
inline void__advance(BidirectionalIterator& i, Distance n,
bidirectional_iterator_tag) {
if (n >= 0)
while (n--) ++i;
else
while (n++) --i;
}
template <classRandomAccessIterator, class Distance>
inline void__advance(RandomAccessIterator& i, Distance n,
random_access_iterator_tag) {
i += n;
}
我们看到第三个参数只声明参数的类型,因为函数根本不使用该参数,只是纯粹用来激活重载机制,加名称也只是画蛇添足
好了,重载完成,我们还需要一个开放的接口给用户,且能将迭代器的型别判定出来可以传递给__advance函数,进行重载判定。迭代器类型的判断就交给traits
template <class InputIterator, class Distance>
inline voidadvance(InputIterator& i, Distance n) {
__advance(i, n,iterator_traits<InputIterator>::iterator_category());
}
值得注意的是,这里写的是iterator_traits<InputIterator>::iterator_category(),这里我就产生了一些疑惑,可能是一些知识缺乏的问题。于是我打开stl_list文件,查看其中关于iterator_category的定义,发现typedefrandom_access_iterator_tag iterator_category;
于是发现,iterator_traits<InputIterator>::iterator_category()其实就是返回了一个iterator_category的一个实体对象,下面看个简短的例子:
#include <iostream>
using namespace std;
void fun(int x)
{
cout<< x <<endl;
}
int main()
{
fun(int(10));
return0;
}
这个程序成功的输出了10.
在STL源码中,其自行定义了一个函数,而不是像这里一长条:
我们要注意的一个地方是,在这个接口函数的template中,未知的情况下声明的是class InputIterator,这事因为STL的默认规定,将其声明为算法所能接受的最低阶迭代器类型
template <class InputIterator, class Distance>
inline voidadvance(InputIterator& i, Distance n) {
__advance(i, n, iterator_category(i));
}
template <class Iterator>
inline typenameiterator_traits<Iterator>::iterator_category
iterator_category(const Iterator&) {
typedef typename iterator_traits<Iterator>::iterator_categorycategory;
return category();
}
另外,对于原生指针,使用的都是random access iterator,这个不用解释
接下来,我们来解决之前遇到的疑问,为什么XXX_iterator_tag之间存在继承关系?
如下:
首先我们需要看看struct之间的继承例子:
#include <iostream>
using namespace std;
struct B {};
struct X:public B{};
struct Y:public B{};
template <class K>
void func(K&p, B)
{
cout<< "B version " << endl;
}
template <class K>
void func(K&p, X)
{
cout<< "X version " << endl;
}
int main()
{
intp;
func(p,B()); //输出B version
func(p,X()); //输出X version
func(p,Y()); //输出B version
return0;
}
看到这里,我想应该对为什么使用继承有所了解了。
这是因为在算法定义的时候,某算法可能定义了InputIterator,BidirectionalIterator, RandomAccessIterator,但我们传入的是ForwardIterator,于是根据重载和继承的原则,程序就会去调用InputIterator的算法定义了
可见,继承可以避免重复的算法定义。