• EC读书笔记系列之18:条款47、48


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

    记住:

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

    ★整合重载技术后,traits classes有可能在编译期对类型执行if...else测试

    ---------------------------------------------------------

    STL共有5种迭代器:

    ①Input迭代器: 如:istream_iterator

      仅前移,一次一步;

      客户只可读取,且只能读取一次;

    ②Output迭代器:如ostream_iterator

      仅前移,一次一步;

      客户只可涂写其所指物,且只能涂写一次,不能读取

    ③Forward迭代器:

      只能使用++操作符来单向遍历容器(不能用--);

      可读写;

    ④Bidirectional迭代器:如STL的list、set、map的迭代器

      可以用++和--操作符来双向遍历容器;

      可读写;

    ⑤Random access迭代器: 如vector、deque和string的迭代器

      可执行“迭代器算术”;

      可直接访问容器中的任意一个元素的双向迭代器;

    对于这五种分类,C++标准库提供专门的类型标记结构对它们进行区分:

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

    ------------------------------

    Traits并非C++关键字或一个预先定义好的构件;它是一种技术,也是一个C++程序员共同遵守的协议。

    ------------------------------

    设计并实现一个traits class

    使用traits技术可以在编译期间获取某些类型信息,它要求对内置类型和用户自定义类型表现得一样好。标准模板库是把traits信息放到模板中,其中针对迭代器的被命名为iterator_traits,它是一个结构体:

        template<typename IterT> 

        struct iterator_traits;

    其工作原理是:针对每一个类型IterT,在结构体中声明某个typedef名为iterator_category,这个typedef用于确认IterT的迭代器分类。它包括两部分实现:

    (1)对于自定义类型,用户必须在类模板中声明一个typedef名为iterator_category,比如对双端队列deque和列表list的模板类:

    template<typename ...>  
    class deque{  
        public:  
            class iterator{  
                public:  
                    typedef  random_access_iterator_tag  iterator_category; 
                    ...
            };
        ...  
    };  
    
    template<typename ...>  
    class list{  
        public:  
            class iterator{  
                public:  
                    typedef  bidirectional_access_iterator_tag  iterator_category;   
                    ...
            };
        ...  
    };

    在iterator_traits中,获取迭代器的类型信息:

    template<typename IterT>  
    struct iterator_traits{  
        typedef  typename IterT::iterator_category  iterator_category;  
        ...  
    }; 

    (2)对于内置类型的指针,iterator_traits提供一个偏特化版本:

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

    因此,设计并实现traits class的步骤是:

    (1)确认若干希望将来可取得的类型相关信息(例如对于迭代器,希望获取它的分类)。

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

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

    -------------------------------------

    traits class的使用

    如上所述的类型信息可在编译期间获取,但是此时需要对传递来的指针类型做出判断,如果使用if...else...分支判断语句,只有在运行期间才能获取。怎么办?

    可以使用函数重载的办法:重载函数需要在编译期间通过确定参数类型来决定调用哪个版本,正好可以解决“编译期条件句”的问题。

    PS:这个想法实在太牛了!

    如前所述的迭代器类型问题,可以设计多个重载函数做适配于各个类型的操作,然后在同一个函数中调用它们。

    template<typename IterT, typename DistT>  
    void advance( IterT& iter, DistT d ) { 
     
        doAdvance(  //doAdvance()有多个重载,其版本不同之处在于传递的迭代器的参数不同 
            iter, d,  
            typename std::iterator_traits<IterT>::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;  
        }  
    }  
     
    template<typename IterT, typename DistT>      //这份实现用于input迭代器,由于forward迭代器派生于input迭代器,也可用于forward迭代器  
    void doAdvance(IterT& iter, DistT d, std::input_iterator_tag)  
    {  
        if(d<0){  
            throw std::out_out_range("Negnative distance"); //对于input或forward迭代器,移动负距离会导致不明确的行为,因而抛出异常  
        }  
        while(d--)   
            ++iter;  
    } 

    可见,使用traits class的方法是:

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

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

    条款48 认识template元编程(TMP

    记住:

    ★TMP(模板元编程)可将工作由运行期移往编译期,因而得以实现早期错误侦测和更高的执行效率

    ★TMP可被用来生成“基于政策选择组合”的客户定制代码,也可用来避免生成对某些特殊类型并不适合的代码

    --------------------------------------------------------------------------------------------------

    TMP可以将部分执行期的任务提前至编译期完成,从而可以更早发现错误,更高效(编译时间会变长,但是执行期效率会高)。且TMP对“难以或甚至不可能于运行期实现出来的行为”的表现也很吸引人!

    ----------------------------------------------

    TMP是图灵完备的,可以执行分支语句和循环(通过递归实现)。如解决阶乘问题:

    template<unsigned n>        //一般情况,递推关系式
    struct Factorial{
        enum { value = n * Factorial<n-1>::value };
    };
    template<>                  //特殊情况,以结束递归
    struct Factorial<0>{
        enum{value = 1};
    };
    
    int main()
    {
        std::cout<<Factorial<5>::value;
        std::cout<<Factorial<10>::value;
    }

    -----------------------------------------------------

    使用TMP一般可以有如下效果:

    (1)确保量度单位正确。

    (2)优化矩阵运算。

    (3)生成客户定制的设计模式。

  • 相关阅读:
    链接的具体内容
    多线程下载图片
    Commo*IO组件的简单应用
    文件分割
    mybatis动态sql
    ajax请求与json数据处理
    ModelAndView 配置与使用
    div塌陷,以及页面常用属性
    EasyUI 时间插件使用配置
    Editor富文本编辑器配置【不含图片上传】
  • 原文地址:https://www.cnblogs.com/hansonwang99/p/5025290.html
Copyright © 2020-2023  润新知