effective C++ 条款 47:使用traits classes表现类型信息

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

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

stl迭代器的分类:

Input迭代器只能向前移动,一次一步,只读,且只能读取一次。模仿指向输入文件的阅读指针:istream_iterator是代表

Output迭代器类似,但只可涂写它们所指的东西,ostream_iterator是代表。

它们只适合“一次性操作算法”one-pass algorithm。

forward迭代器,可以做前述两种分类所能做的每一件事,而且可以读或写其所指物一次以上。可施行于多次性操作算法。stl未提供单向linked_list,指入这种容器的迭代器就是属于forward迭代器。

bedirectional迭代器比上一个威力更大:除了可以向前移动,还可以向后移动。stl的list、set、multiset、map和multimap的迭代器都属于这一类。

random access迭代器比上一个更威力大,它可以执行迭代器算术,常量时间内可以向前或向后跳跃任意距离。内置指针也可以当random迭代器用。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_iterator_tag: public bidirectional_iterator_tag{};

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

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让你进行的事:它们允许在编译期间取得某些类型信息

traits并不是c++关键字或一个预先定义好的构件:它们是一种技术,也是一个c++程序员共同遵守的协议。这个技术的要求之一是,它对内置类型和用户自定义类型的表现必须一样好。

“traits必须能够施行于内置类型”意味着“类型内的嵌套信息”这种东西出局了,因为我们无法将信息嵌套于原始指针内。因此类型的traits信息必须位于类型自身之外。标准技术是把它放进一个template及其一或多个特化版本中。这样的templates在标准库中有若干个,其中针对迭代器的被命名为iterator_traits:

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

习惯上traits总是被实现为structs,但它们却又往往被称为traits classes。

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

两个部分实现上述所言。首先它要求每一个“用户自定义的迭代器类型”必须嵌套一个typedef,名为iterator_category,用来确认适当的卷标结构(tag struct)。例如:deque和list的迭代器的样子

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

至于iterator_traits,只是鹦鹉学舌般地响应iterator class的嵌套式typedef:

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

iterator_traits的第二部分,专门用来对付指针,因为指针不含typedef。为了支持指针迭代器,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:

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

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

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

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))
    {
        ...
    }
}

并非我们想要的,首先会导致编译问题(条款48)。IterT类型在编译期间获得,所以iterator_traits<IterT>::iterator_category也可以在编译期间确定。但if语句却是在运行期才会核定。这不仅浪费时间,也造成可执行文件膨胀。

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

你必须详细叙述各个重载件的参数类型。当你调用f,编译器便根据传来的实参选择最适当的重载件。

template<typename IterT, typename DistT>                                            //这份实现于
void doAdvance(IterT& iter, DistT d, std::random_access_iterator_tag)        //random access
{                                                                                                                //迭代器
    iter += d;
}
template<typename IterT, typename DistT>                                            //这份实现于
void doAdvance(IterT& iter, DistT d, std::bidirectional_iterator_tag)            //biderectional
{                                                                                                                //迭代器
    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)                        //input
{                                                                                                                //迭代器
    if (d < 0){throw std::out_of_range("Negative distance");}
    while (d--) ++iter;
}

由于forward_iterator_tag继承自input_iterator_tag,所以上述doAdvance的input_irerator_tag版本也能够处理forward迭代器。

advance需要做的只是调用它们并额外传递一个对象,后者必须带有适当的迭代器分类。

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所提供的信息

posted @ 2012-02-17 17:08  lidan  阅读(514)  评论(0编辑  收藏  举报