C++之使用traits classes表现类型信息(47)---《Effective C++》

来源:互联网 发布:apm飞控软件 编辑:程序博客网 时间:2024/05/18 01:35

条款47:请使用traits classes表现类型信息

我们声明一个advance函数,进行了iter+=d的动作:

template <typename IterT,typename DistT>void advance(IterT& iter,DistT d);

但是我们完全可以不用这样做,因为STL中支持random access,使用advance进行实现的话则需要反复进行++或者–运算,需要进行d次,非常冗杂。

STL中共有5中迭代器:
1)Input迭代器每次只能向前移动,一次一步,客户只能读取它们所指的东西,而不能进行修改,而且只能读取一次,istream_iterator是这种迭代器的代表,只适合“一次性操作算法”;
2)Output迭代器类似,一切只为输出,只能向前移动,一次一步,客户只能修改它们所指的东西,而且只能涂改一次,ostream_iterator是这种迭代器的代表,只适合“一次性操作算法”;
3)forward迭代器,这种迭代器可以做上述两种迭代器所能做的每一个,同时可以读或者写其所指物一次以上;
4)Bidirectional迭代器比上一个分类威力更大:它除了可以前向移动,还能后向移动,即双向移动;
5)random access迭代器威力更大,不仅可以进行双向移动移动,同时还可以进行移动随机步,即它可以在常量时间内向前或者向后跳转任意距离。
上述五种类型的关系可以表述为:

struct input_iterator_tag{};struct output_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{};

上述structs之间的继承关系是有效的“is-a”关系,以此类推,我们很快就能看到这个继承关系的效力。

针对advance的实现,我们真正想要使用的方式是如下这种:

template <typename IterT,typename DistT>void advance(IterT& iterT,DistT d){    if(iter is a random access iterator){        iter+=d;    }    else{        if(d>=0) {while(d--) ++iter;}        else {while(d++) --iter;}    }}

Traits这种技术对内置类型和用户自定义类型的表现一样好,如果上述advance函数收到的实参是一个指针和一个int类型,上述的advance函数仍然需要有效运作,这就要求traits技术必须也可以施行于内置类型如指针身上。类型的trait信息必须位于类型自身之外,标准技术是将其放进一个template机器一或者多个特化版本中。这样的template在标准程序库中有若干个,其中针对迭代器者被命名为iterator_traits:

方法1:

template <typename IterT>struct iterator_traits;

iterator_traits的运作方式是针对每一个类型IterT,在struct iterator_traits<IterT>内部一定声明一个typedef名为iterator_category,这个category用来确认IterT的迭代器分类。

template <...>class deque{public:    class Iterator{    public:    typedef random_access_iterator_tag iterator_category;    ...    };};template <...>class list{public:    class Iterator{    public:        typedef bidirectional_iterator_tag iterator_category;        ...    };    ...};  //用来对付用户自定义类型template <typename IterT>struct iterator_traits{    typedef typename IterT::iterator_category iterator_category;    ...};  //用来专门对付指针类型template <typename IterT>struct iterator_traits<IterT*>{    typedef random_access_iterator_tag iterator_category;    ...};template <typename IterT,typename DistT>void advance(IterT& iter,Dist d){    if(typeid(typename std::iterator_traits<IterT>::iterator_category)==typeid(std::random_access_iterator_tag))    ...}

现在,我们知道如何设计并实现一个traits class了:
1)确认若干你希望将来可取得的类型相关信息,例如针对迭代器而言,我们希望将来可以获得其分类;
2)为该信息选择一个名称;
3)提供一个template和一组特化版本,内含你希望支持的类型相关信息。
然而这种方式也会导致一些问题,首先会导致编译问题,IterT类型在编译期间即可确定,所以iterator_traits<IterT>::iterator_category也可在编译期间确定,但if…else…直到运行期间才可以确定,这样不仅浪费时间,而且造成可执行文件膨胀。
我们真正想要的条件式判断“编译期核定成功”之类型,恰巧C++中便有这种行为的方法,就是通过“重载”方式。

方法2:

template <typename IterT,typename DistT>void doAdvance(IterT& iter,DistT d,std::random_access_iterator_tag){    iter+=d;}template <typename IterT,typename DistT>void doAdvance(IterT& iter,DistT d,std::bidirectional_iterator_tag){    if(d>=0) {    while(d--)     ++iter;    }    else{    while(d++)     --iter;    }}template <typename IterT,typename DistT>void doAdvance(IterT& iter,DistT d,std::input_iterator_tag){    if(d<0){        throw std::out_of_range("Negative distance");    }    while(d--) ++iter;}template <typename IterT,typename DistT>void advance(IterT& iter,DistT d){    //调用doAdvance版本必须是对iter之迭代器分类而言,必须是适当的。    doAdvance(iter,d,typename std::iterator_traits<IterT>::iterator_category());}

下面对trait class如何使用进行一个总结!
1)建立一组重载函数或者函数模板,彼此间的差异只在于各自的traits参数。令每个函数实现码与其接受之traits信息相应和。
2)建立一个控制函数或者函数模板,调用1)中创建的重载函数并传递traits class所提供的信息。

总结:
1)Traits classes使得“类型相关信息”在编译期可用。它们以templates和“templates特化”完成实现。
2)整合重载技术,traits class有可能在编译期对类型执行if…else…测试。

阅读全文
0 0