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

目录

1.前言

2.实例分析

3.总结


1.前言

STL主要由“用以表现容器,迭代器和算法”的template构成,但也覆盖若干工具性templates,其中一个名为advance,用来将某个迭代器移动某个给定距离:

tempalte<typename IterT,typename DistT>//将迭代器向前移动d单位
void advance(IterT& iter,DistT d);//如果d<0,则向后移动

观念上advance只是做iter+=d动作,但其实不可以全然是那样,因为只有random access(随机访问)迭代器才支持+=操作。面对其它迭代器种类,advance必须反复施行++或--,共d次。

先回顾下STL迭代器的分类:STL共有5种迭代器分类,input迭代器只能向前移动,一次一步,客户可只读取它们所指的变量,而且只读取一次,他们模仿指向输入文件的阅读指针(read pointer);c++程序库中的istream_iterators是这一分类的代表。

output迭代器情况类似,但一切只为输出:它们只向前移动,一次一步,客户只涂写一次。它们模仿指向输出文件的涂写指针(write pointer);ostream_iterators是这一分类的代表。这是功能最小的两个迭代器分类。由于这;两类都只能向前移动,而且只能读或写其所指物最多一次,所以它们只适合“一次性操作”(one-pass algorithms)。另一个功能比较强大的分类是forward迭代器。这种迭代器可以做前述两种分类所能做的每一件事,而且可以读或写其所指物一次以上。这使得它们可以施行于多次性操作算法(multi-pass algorithms)。STL并未提供单向linked list,但某些程序库有slit,而置入这种容器的迭代器就是属于forward迭代器。指入TR1 hashed容器的也可能是这一分类。

Bidirectional迭代器比上一个功能更大:它除了可以向前移动,还可以向后移动。STL的list迭代器就属于这一类,set,multiset,map和multimap的迭代器也都是这一分类。

最强大的迭代器当属random access迭代器。这种迭代器功能强大之处再与它可以执行“迭代器算术”,即可以在常量时间内向前或向后跳跃任意距离。

对这5种分类,c++标准程序库分别提供专属的卷标结构(tag struct)加以确认:

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 biairectional_iterator_tag{};

这些struct之间的继承关系是有效的is-a。

现在回到advance函数,我们知道STL迭代器有着不同的能力,实现advance的策略之一是采用“最低但最普及”的迭代能力,以循环往复递增或递减迭代器。然而这种做法耗费线性时间,我们知道random access迭代器支持迭代器算术运算,只耗费常量时间,如果面对这种迭代器,我们希望运用其优势:

然而我们真正希望的是以这种方式实现advance:

template<typename IterT,typename DistT>
void advance(IterT& iter,DistT d)
{
    if(iterT& iter,DistT d)
    {
        if(iter is a random access iterator)
        {
            iter+=d;//针对random access迭代器使用迭代器算术运算
        }
        else{
            if(d>=0)
            {
                while(d--) ++iter;
            }
            else{
                    if(d>=0){while(d--) ++iter;}
                    else {while(d++) --iter;}//针对其它迭代器分类,反复调用++或--
                }
        
        }
    }

}

这种做法首先是必须判断iter是否未random access迭代器,也就是说需要知道类型IterT是否为random access迭代器。换句话说我们需要取得类型的某些信息。那就是traits让你得以进行的事情:它们允许你在编译期间取得某些类型信息。。

2.实例分析

traits并不是c++关键自或一个预先定义好的构件;他们是一种技术,也是一个c++程序员共同遵守的协议。这个技术的要求之一是他对内置(built-in)类型或用户自定义类型的表现必须一样好。举个例子,如果上述advance仍然必须有效运作,那意味这traits技术必须能够施行于内置类型如指针上。

”traits必须能够施行于内置类型“意味”类型内的嵌套信息(nesting information)“这种东西必须出局了,因为我们无法将信息嵌套于原始指针内。因此类型的traits信息必须位于类型自身之外。标准技术时把它放进一个template及其一个被命名为iterator_traits:

template<typename iterT>//template,用来处理
struct iterator_traits;//迭代器分类的相关信息

正如所看到的,iterattor_traits是个struct。习惯上traits总是被实现为structs,但它们又往往被称为traits classes。

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

iterator_traits以两个部分实现上述所言。首先它要求每一个“用户自定义的迭代器类型”必须嵌套一个typedef,名为iterator_category,用来确认适当的卷标结构。例如deque的迭代器可随机访问,所以一个针对deque迭代器而设计的class看起来会是这个样子:

template<...>
class deque{

    public:
        class iterator{
        public:
            typedef random_access_iterator_tag  iterator_category;
            ....
    };
    ....
};

list的迭代器可双向行进,所以它们应该是这样的:

template<...>
class list{

    public:
        class iterator{

            public:
                typedef bidirectional_iterator_tag iterator_category;
                ....
        };
        ....
};

至于iterator_traits,只是相应iterator class的嵌套式typedef:

//类型IterT的iterator_category其实是用来表现“IterT说它自己是什么”
//关于“typedef typename”的运用,见条款42
template<typename IterT>
struct iterator_traits{

    typedef typename IterT::iterator_category iterator_category;
    ...
};

这对用户自定义类型行得通,但对指针(也是一种迭代器)行不通,因为指针不可能嵌套tepedef。iterator_traits的第二部分如下,专门用来对付指针:

为了支持指针迭代器,iterator_traits特别针对指针类型提供一个偏特化版本(partial template specialization)。由于指针的行径与random access迭代器类似,所以iterator_traits为指针指定的迭代器类型是:

template<typename IterT>//template偏特化
struct iterator_traits<IterT*>//针对内置指针
{
    typedef random_access_iterator_tag  iterator_category;
    ....
};

现在,我们应该了解了如何设计并实现一个traits class了:

(1)确认若干你希望可取的的类型相关信息。例如对迭代器而言,我们希望将来可取其分类(category);

(2)为该信息选择一个名称(比如iterator_category)

(3)提供一个template和一组特化版本,内含你希望支持的类型相关信息。

现在有了iterator_traits(实际上是std::iterator_traits,因为它是c++标准程序的一部分),我们可以对advance实践先前的伪码(pseudocode):

template<typename IterT,typename DistT>
void advance(IterT& iter,DistT d)
{
    if(typeid(typename std::iterator_traits<IterT>::iterator_category)==typeid(std::random_access_iterator_tag))
    ....
}

虽然这看起来前景光明,但并不是我们想要的。首先会导致编译问题。IterT类型在编译器间获知,所以iterator_traits<IterT>::iterator_category也可以在编译器间确定。但if语句却是在运行期才会核定。

我们真正想要的是一个条件式,判断“编译器核定成功”之类型。恰巧c++有一个取得这种行为的办法,那就是重载(overloading)。

当我们重载某个函数f,必须详细描述各个重载建房的参数类型。当你调用f,编译器便根据传来的实参选择最适合的重载件。编译器的态度是“如果这个重载件最匹配传递过来的实参,就调用这个f;如果那个重载件最匹配,就调用那个f;如果第三个f最匹配,就调用第三个f”。依次类推。这正是一个针对类型而发生的“编译器条件句”。为了让advance的行为如我们所期望的那样,我们需要做的是产生两版重载函数,内含advance的本质内容,但各自接受不同类型的iterator_category对象。我将这两个函数取名为doAdvance:

template<typename IterT,typename DistT>//用于random access 迭代器
void doAdvance(IterT& iter,typename Dist,std::random_access_iterator_tag)
{
    iter+=d;
}

template<typename IterT,typename DistT,std::bidirectional_iterator_tag>//这份
{                                   //实现用于bidirectional迭代器
    if(d>=0){while(d--)++iter;}
    else{while(d++) --iter;}

}
template<typename IterT,typename DistT>//这份用于实现input迭代器
void doAdvance(IterT& iter,DistT d,std::input_iterator_tag)
{
    if(d<0){throw std::out_of_range("Negative distance");}
    while(d--) ++iter;
}

由于forward_iterator继承自input_iterator_tag,所以上述doAdvance的input_iterator_tag版本也能够处理forward迭代器。这是iterator_tag structs继承关系带来的一项红利。实际上这也是public继承带来的部分好处:针对base class编写的代码用于derived class身上行得通

advance函数规范说,如果面对的是random access和bidirectional迭代器,则接受正距离和负距离;但如果面对的是forward或input迭代器,则移动负距离会导致不明确(未定义)行为。我所检验过的实现码都假设d不为负,于是直接进入一个冗长的循环迭代,等待计数器降为0。上述异常我以抛出异常取而代之。

有了这些doAdvance重载版本,advance需要做的是调用它们并额外传递一个对象,后者必须带有适当的迭代器分类。于是编译器运用重载解析机制(overloading resolution)调用适当的实现代码:

template<typename IterT,typename DistT>
void advance(IterT& iter,DistT d)
{
    doAdvance(iter,d,typename std::iterator_traits<IterT>::iterator_category());
}

现在我们可以总结如何使用一个traits class了:

(1)建立一组重载函数或函数模板,,彼此间的差异只在于各自的traits参数。令每个函数实现码与其接受之traits信息相应和。

(2)建立一个控制函数或函数模板,它调用上述那些劳工函数并传递traits class所提供的信息。

traits广泛用于标准程序库,其中当然有上述讨论的iterator_traits,除了供应iterator_category还供应另四分迭代器相关信息。此外还有char_traits用来保存字符类型相关信息,以及numeric_limits用来保存数值类型的相关信息,例如某些数值类型可表现之最小值和最大值等等;命名为numeric_limits有点让人惊讶,因为traits classes的名称以"traits"结束,但numeric_limits却没有遵守这种风格。

3.总结

(1)Traits classes使得“类型相关信息”在编译器可用。它们以template和“templates特化”完成实现。

(2)整合重载技术后,traits classes有可能在编译器对类型执行if...else 测试。

  • 24
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值