Effective C++——条款47(第7章)

来源:互联网 发布:淘宝一元商品怎么赚钱 编辑:程序博客网 时间:2024/05/17 04:09

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

Use traits classes for information about types.

    STL主要由"用以表现容器,迭代器和算法"的 template 构成,但也覆盖若干工具性 template,其中一个名为advance,用来将某个迭代器移动某个给定距离:
template <typename T, typename DistT>void advance(IterT& iter, DistT d);
    观念上advance只是做iter+=d动作,但其实不全是这样,因为只有random access(随机访问)迭代器才支持+=操作.面对其他威力不那么强大的迭代器种类,advance必须反复施行++和--,共d次.
    STL共有5种迭代器分类,对应于它们支持的操作.input迭代器只能向前移动,一次一步,客户可读取(不能写)它们所指的东西,而且只能读取一次.它们模仿指向输入文件的阅读指针(read pointer);C++程序库中的istream_iterator是这一类的代表.output迭代器情况类似,但一切只为输出:它们只能向前移动,一次一步,客户只能写它们所指的东西,而且只能写一次.它们模仿指向输出文件的写指针(write pointer);ostream_iterator是这一类的代表.这是威力最小的两个迭代器分类.由于这两类都只能向前移动,而且只能读或写其所指物最多一次,所以它们只适合"一次性操作算法".
    另一个威力比较强大的分类是forward迭代器.这种迭代器可以做前述两种分类所做的每一件事,而且可以读或写其所指物一次以上.这使得它们可施行于多次性操作算法.指入TR1 hashed容器(详见条款54)的也可能是这一分类.
    Bidirectional迭代器比上一个分类威力更大:它除了可以向前移动,还可以向后移动.STL的list迭代器就属于这一分类,set,multiset,map和multimap的迭代器也都是这一分类.
    最有威力的迭代器是random access迭代器.这种迭代器比上一个分类威力更大的地方在于它可以执行"迭代器算术",也就是它可以在常量时间内向前或向后跳跃任意独立.这样的算法很类似指针算术,这并不令人惊讶,因为random access迭代器正是以内置(原始)指针为榜样,而内置指针也可被当做random access迭代器使用.vector,deque和string提供的迭代器正是这一分类.
    对于这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 bidirectional_iterator_tag {};
    这些 struct 之间的继承关系是有效的is-a关系(详见条款32):是的,所有forward迭代器都是input迭代器,依次类推.
    现在回到advance函数,已经知道STL迭代器有着不同的能力,实现advance的策略之一是采用"最低但最普及"的迭代器能力,以循环反复递增或递减迭代器.然而这种做法耗费线性时间,因此采用random access迭代器支持迭代器算数运算:
template <typename IterT, typename DistT>void advance(IterT& iter, DistT d) {    if (iter is a random access iterator) {        iter += d;      // 针对random access迭代器使用算数运算    } else {            // 针对其他迭代器反复调用++或--        if (d >= 0) {            while (d--)                ++iter;        } else {            while (d++)                --iter;        }    }}
    这种做法首先必须判断iter是否为random access迭代器,
    Traits并不是C++关键字或一个预先定义好的构件:它们是一种技术,也是一个C++程序员共同遵守的协议.这个技术的要求之一是,它对内置(built-in)类型和用户自定义(user-defined)类型的表现必须一样好.
举个例子,如果上述advance收到的实参是一个指针(例如 const char*)和一个 int,上述advance仍然必须有效运作,那意味着traits技术必须也能够施行于内置类型如指针上.
    "traits必须能够施行于内置类型"意味"类型内的嵌套信息"这种东西就出局了,因为无法将信息嵌套于原始指针内.因此类型的traits信息必须位于类型自身之外.标准技术是把它放进一个 template 及其一或多个特化版本中.这样的 template 的标准程序库中有若干个,其中针对迭代器者被命名为iterator_traits:
template <typename IterT>struct iterator_traits;
    iterator_traits的运作方式是,针对每一个类型IterT,在 struct iterator_traits<IterT>内一定声明某个 typedef 名为iterator_category .这个 typedef 用来确认IterT的迭代器类型.
    iterator_traits以两个部分实现上述所言.首先它要求每一个"用户自定义的迭代器类型"必须嵌套一个 typedef,名为 iterator_category,用来确认适当的卷标结构(tag struct).例如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"的运用,详见条款42template <typename IterT>struct iterator_traits {    typedef typename IterT::iterator_category iterator_category;    ...};
    这对用户自定义类型行得通,但对指针行不通,因为指针不可能嵌套 typedef . iterator_traits的第二部分如下,专门用来对付指针:
template <typename IterT>struct iterator_traits<IterT *> {    typedef random_access_iterator_tag iterator_category;    ...}
    现在,就了解如何设计并实现一个 traits class 了:
    确认若干希望将来可取得的类型相关信息.例如对迭代器而言,希望将来可取得其分类.
    为该信息选择一个名称(例如iterator_category)
    提供一个 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>::itrator_category)            == typeid(std::random_access_iterator_tag))    ...}
    这看起来是有问题的,IterT类型在编译期间获知,所以iterator_traits<IterT>::iterator_category也可在编译期确定.但 if 语句却是在运行期才会核定.为什么将可在编译期完成的事延迟到运行期才做呢?这不仅浪费时间,也会造成可执行文件膨胀.
    真正想要的是一个条件式(也就是 if ... else 语句)判断"编译期核定成功"的类型.C++有一个取得这种行为的办法,那就是重载.
    当重载某个函数f,必须详细叙述那个重载物件的参数类型.当调用f,编译器便根据传来的实参选择最适当的重载件.编译器的态度是"如果这个重载件最匹配传递过来的实参,就调用这个f".这正是一个针对类型而发生的"编译期条件句".为了让advance行为达到期望,需要做的是产生两版重载函数,内含advance的本质内容,但各自接受不同类型的iterator_category对象.
template <typename IterT, typename DistT>   // 这份实现用于random access迭代器void doAdvance(IterT& iter, DistT d, std::random_access_iterator_tag) {    iter += d;}template <typename IterT, typename DistT>   // 这份实现用于Bidirectional迭代器void doAdvance(IterT& iter, DistT d, std::bidirectional_iterator_tag) {    if (d >= 0) {        while (d--)            ++iter;    } else {        while (d++)            --iter;    }}
    由于forward_iterator_tag继承自input_iterator_tag,所以上述doAdvance的input_iterator_tag版本也能够处理forward迭代器.
    有了这些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 :
    建立一组重载函数(身份像劳工)或函数模板(例如doAdvance),彼此之间的差异只在于各自的traits参数.令每个函数实现码与其接受的traits信息相应和.
    建立一个控制函数(身份像工头)或函数模板(例如advance),它调用上述那些"劳工函数"并传递traits class 所提供的信息.
    Traits广泛用于标准程序库,其中当然有上述讨论的iterator_traits,除了供应iterator_category还供应另四份迭代器相关信息(其中最有用的是value_type,详见条款42).
    TR1(条款54)导入许多新的traits class 用以提供类型信息,包括is_fundamental<T>(判断T是否为内置类型),is_array<T>(判断T是否为数组类型),以及is_base_of<T1, T2>(T1,T2相同,或T1是T2的base class).总计TR1一共为标准C++添加了50个以上的traits class .
    注意:
    Traits class 使得"类型相关信息"在编译期可用.它们以 template 和"template特化"完成实现.
    整合重载技术(overloading)后,traits class 有可能在编译期对类型执行 if ... else 测试.

0 0
原创粉丝点击