函数模版重载之iterator_traits技巧

来源:互联网 发布:安卓 卸载 瞬间 知乎 编辑:程序博客网 时间:2024/04/29 01:38

以下皆在MS  VS2005下测试运行。
大家都知道在标准STL里的vector容器有这样2个构造函数:

1)
vector(size_type  sz,value_type  val);
2)
template<class  InputIterator>
vector(InputIterator  first,InputIterator  last);

其意义分别是:
1)初始化一个大小为sz,所有元素值都为val的vector;
2)由first和last这2个迭代器内的元素初始化vector。

大家可以试一下下面的代码,就知道它们的工作分工:

int  main(){
        vector<int>  v1(5,3);                //调用1)
        int  arr[5]  =  {3,3,3,3,3};
        vector<int>  v2(arr,arr  +  5);        //调用2)
}

但是,如果你有兴趣自己写一个类,并且也加上这样2个构造函数,你也许会发现一点问题:

struct  MyVec
{
        typedef  int  value_type;        //为了简化,这是一个int容器
        typedef  unsigned  int  size_type;        //元素个数是非负数
        MyVec(){}        //默认构造函数
        MyVec(size_type  sz,value_type  val){        //模仿1)
                cout<<"MyVec(size_type  sz,value_type  val)/n";
        }
        template<class  InputIterator>
        MyVec(InputIterator  first,InputIterator  last){        //模仿2)
                cout<<"MyVec(InputIterator  first,InputIterator  last)/n";
        }
};

那么同样用上面的测试代码运行一下:

int  main(){
        MyVec  v1(5,3);                //调用?
        int  arr[5]  =  {3,3,3,3,3};
        MyVec  v2(arr,arr  +  5);        //调用?
}

运行结果是:

MyVec(InputIterator  first,InputIterator  last)
MyVec(InputIterator  first,InputIterator  last)

是不是看出什么问题了呢?是的,
MyVec  v1(5,3);
本该调用
MyVec(size_type  sz,value_type  val)
可是根据C++模版参数的推导和函数匹配规则,却调用了
MyVec(InputIterator  first,InputIterator  last)

分析一下:5和3作为参数传给MyVec的构造函数时,是作为int类型的,即编译器会寻找和
MyVec(int,int)
最相近的构造函数,这时候选者有:
MyVec(size_type,value_type)
可是要进行一次int到unsigned  int的类型转换。
还有:
MyVec(InputIterator  first,InputIterator  last)
这个是完全匹配,并且推导出InputIterator就是int。于是编译器决定调用后者。
这肯定不是我们要的结果。
那么怎么修正这个“bug”呢?我提供一个利用iterator_traits的解决办法,也是vc8中vector的解决方法。
首先大家应该知道iterator_traits是一个迭代器萃取机,它能萃取出迭代器的许多特性,包括iterator_category,即迭代器类型。
标准STL中迭代器的类型有5种:
struct  output_iterator_tag{};
struct  input_iterator_tag{};
struct  forward_iterator_tag  :  public  input_iterator_tag{};
struct  bidirectional_iterator_tag  :  public  forward_iterator_tag{};
struct  random_access_iterator_tag  :  public  bidirectional_iterator_tag{};
请注意它们的继承关系。
如果一个类型的确是一个迭代器,那么它的iterator_category必然是上述5个类型中的一个。比如任何类型的指针T*,就对应random_access_iterator_tag。
如果一个类型T不是迭代器,那么萃取它的iterator_category会得到什么呢?答案是编译错误,因为你根本没有定义T的相应iterator_category。
比如我们定义一个integer_type_tag类,并且把int的iterator_category定义为它:

struct  integer_type_tag{};

template<>
iterator_traits<int>{
        typedef  integer_type_tag  iterator_category;
};

那么我们再对int进行iterator_category萃取:

typename  iterator_traits<int>::iterator_category;

就会得到integer_type_tag而不是原先的编译错误了。那么你现在是不是想到了什么呢了?不错,我们可以利用这个特点在
MyVec(InputIterator  first,InputIterator  last)
函数内再作一次转移,如果萃取出来的
typename  iterator_traits<InputIterator>::iterator_category
是一个integer_type_tag,那么InputIterator就不是一个迭代器,而是int,于是函数转移到MyVec(size_type,value_type)的调用:

struct  MyVec
{
        typedef  int  value_type;        //为了简化,这是一个int容器
        typedef  unsigned  int  size_type;        //元素个数是非负数
        MyVec(){}        //默认构造函数
        MyVec(size_type  sz,value_type  val){        //模仿1)
                construct_n(sz,val);
        }
        template<class  InputIterator>
        MyVec(InputIterator  first,InputIterator  last){        //模仿2)
                typedef  typename  iterator_traits<InputIterator>::iterator_category  __IterCategory;
                construct(first,last,__IterCategory());
        }
private:
        template<class  InergerType>
        void  construct(InergerType  sz,InergerType  val,integer_type_tag){
                construct_n(size_type(sz),value_type(val));
        }
        template<class  InputIterator>
        void  construct(InputIterator  first,InputIterator  last,input_iterator_tag){
                cout<<"construct(InputIterator  first,InputIterator  last)/n";
        }
        void  construct_n(size_type  sz,value_type  val){
                cout<<"construct_n(size_type  sz,value_type  val)/n";
        }
       
};

首先我把MyVec(size_type  sz,value_type  val)的函数内容转移到construct_n(size_type  sz,value_type  val)里面,如果构造的参数完全匹配的话,就会正确调用construct_n来构造。
如果传入的参数是2个int的话,按照正常的逻辑应该也调用MyVec(size_type  sz,value_type  val)然后转到construct_n来构造,但是编译器会先选择
MyVec(InputIterator  first,InputIterator  last)
并且把InputIterator推导成int。接下来重点到了:

typedef  typename  iterator_traits<InputIterator>::iterator_category  __IterCategory;

这句话推导出__IterCategory的类型是integer_type_tag(想想前面iterator_traits的介绍),然后调用
construct(first,last,__IterCategory())
的时候,会转移到
void  construct(InergerType  sz,InergerType  val,integer_type_tag)
去执行,于是执行了正确的
construct_n(size_type(sz),value_type(val))。
另一方面,如果传入的参数真的是迭代器呢?
比如传入了2个int*,即:
MyVec(int  *,int  *);
那么编译器照样会选择
MyVec(InputIterator  first,InputIterator  last)
并且把InputIterator推导成int  *,于是接下来的typedef会推导出__IterCategory类型为random_access_iterator_tag,而它是input_iterator_tag的子类,所以调用:
construct(first,last,__IterCategory())
会转到
construct(InputIterator  first,InputIterator  last,input_iterator_tag)
正确的调用了需要的函数。

在VC8中不仅把int的iterator_category进行了重定义,所有的POD类型都进行了重定义,所以如果你也这样做了,即使传入
MyVec  v1(short(10),long(4));
也会调用正确的构造函数,构造出含10个4的MyVec。

原创粉丝点击