Effective C++ 条款47

本节条款的题目:请使用trait classes来表示类型信息

本节条款主要讲述的技术是如何在编译期间实现对迭代器类型的判断,根据判断的类型进行最优处理。

我们先来看一下迭代器的种类:

1.input_iterator:只读,只能逐个前移

2.output_iterator:只写,只能逐个前移

3.forward_iterator:可读可写,只能逐个前移

4.bidirectional_iterator:可读可写,支持逐个前移和后移

5.random_access_iterator:可读可写,支持随机访问(任意步数移动)

假设一下,如果有一个函数用于对迭代器指定移动次数d,我们如何实现这个函数,以达到最理想的状态。

我们肯定会处理迭代器是random access类型的情况和不是random access类型的情况,当时random access的时候,直接加减迭代器在常数时间内完成,如果不是,只能一步一步向前或向后移动。

如下代码:

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

可是,我们怎么才能做到“iter is a random access iterator”在编译期间的实现?

实现这个功能,需要以下几步,并且相对繁杂的几步。

首先为每一种迭代器设置struct标签,每个struct都是空的结构体,如下代码:

struct input_iterator_tag{};

struct output_iterator_tag{};

struct forward_iterator_tag: public input_iterator_tag{};

struct bidirectional_iterator: public forward_iterator_tag{};

struct random_access_iterator: public bidirectional_iterator_tag{};

接着,我们就要引入traits,traits是一种技术,我们通过程序来看一下这是什么技术,有什么用。

我们来看一下关于各种容器的定义:

template class <T>
class vector
{
public:
    class iterator
    {
    public:
        typedef random_access_iterator iterator_category;
        …
    }
    …
}

template class <T>
class list
{
public:
    class iterator
    {
    public:
        typedef bidirectional_iterator iterator_category;
        …
    }
    …
}

从上面代码可以看出,每个容器中都有一个iterator类,可是每个iterator类中的迭代器标签却不一样。

当我们调用语句vector<int>::iterator::iterator_category时会返回random_access_iterator类的类型,当我们调用list<int>::iterator::iterator_category时会返回bidirectional_iterator类的类型。

我们再来看一下有关iterator_traits结构体的定义,

template<typename IterT>
    struct iterator_traits{
        typedef typename IterT::iterator_category iterator_category;//typedef typename的使用见条款42
    };

如果我们通过语句iterator_traits<vector<int>::iterator>::iterator_category调用就会返回有关vector<int>迭代器iterator的类型,该语句返回random_access_iterator类型。

同理iterator_traits<list<int>::iterator>::iterator_category该语句返回bidirectional_iterator类型。

我们这个时候可以实现有关void advance函数的定义了,大家看下面的代码:

 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))
        ……
    }

通过语句advance(vector<int>::iterator iter,2)我们可以看一下实例化后的代码:

    void advance(iter,2)
    {
        if(typeid(std::iterator_traits<vector<int>::iterator>::iterator_category)==
        typeid(std::random_access_iterator_tag)){.....}
        else
        {...}

    }

我们来分解一下语句typeid(std::iterator_traits<vector<int>::iterator>::iterator_category)其中vector<int>::iterator返回vector的迭代器iterator,iterator_traits<vector<int>::iterator>::iterator_category返回关于int型vector的迭代器类类型,此时返回random_access_iterator类型。

可是,还有一个更根本的问题:IterT类型在编译期间获知,所以iterator_traits::iterator_category在编译期间确定。但是if语句却是在运行期间核定。可以在编译期间完成的事情推到运行期间,这不仅浪费时间,还造成执行文件膨胀。所以我们采用另一种方式来避免这种问题。另一种方式就是函数重载,我们来定义一系列的重载函数,如下代码:

template<typename IterT, typename DisT>
    void doAdvance(IterT& iter, Dist d, std::random_access_iterator_tag)
    {
        iter+=d;
    }
    template<typename IterT, typename DisT>
    void doAdvance(IterT& iter, Dist d, std::bidirectional_iterator_tag)
    {
        if(d>=0)
        while(d--) ++iter;
    else
        while(d++) --iter;
    }
    template<typename IterT, typename DisT>
    void doAdvance(IterT& iter, Dist 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,d,typename::std::iterator_traits<IterT>::iterator_category();
    }

由于重载函数的确定是在编译期间,所以当我们再次调用advance(vector<int>::iterator&iter,2)时,我们看一下实例后的代码:

    void advance(iter,2)
    {     doAdvance(iter,2,typename::std::iterator_traits<vector<int>::iterator>::iterator_category;
    }

这时typename::std::iterator_traits<vector<int>::iterator>::iterator_category;编译期间返回random_access_iterator类类型。所以调用void doAdvance(IterT& iter, Dist d, std::random_access_iterator_tag)版本的重载函数。

以上就是本条款的主要内容。

总结:

1. Traits class使得类型相关信息可以在编译期可用,它们以template和template特化完成实现;

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

时间: 2024-08-01 00:22:22

Effective C++ 条款47的相关文章

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

1. STL迭代器分类: input迭代器:只能一次一步向前移动,客户只可读取(不能涂写)且只能读取一次它们所指的东西,模仿指向输入文件的阅读指针.例如istream_iterators output迭代器:与input迭代器类似,但"一切只为输出",只能一次一步向前移动,客户只可涂写(不能读取)且只能涂写一次它们所指向的东西,模仿指向输出文件的涂写指针.例如ostream_iterators. forward迭代器:具有input迭代器和output迭代器的所有功能:只能一次一步向前

More Effective C++ 条款35 让自己习惯于标准C++ 语言

(由于本书出版于1996年,因此当时的新特性现在来说可能已经习以为常,但现在重新了解反而会起到了解C++变迁的作用) 1. 1990年后C++的重要改变 1). 增加了新的语言特性:RTTI,namespaces,bool,关键词mutable和explicit,enums作为重载函数之自变量所引发的类型晋升转换,以及"在class 定义区内直接为整数型(intergral) const static class members设定初值"的能力. 2). 扩充了Templates的特性

effective c++ 条款4 make sure that objects are initialized before they are used

1 c++ 类的数据成员的初始化发生在构造函数前 class InitialData { public: int data1; int data2; InitialData(int a, int b) { data1 = a: //this is assignment data2 = b; //this is assignment } /* InitialData(int a, int b):data1(a),data2(b) //this is initial {} */ } 2 不同cpp文

More Effective C++ 条款34 如何在一个程序中结合C++和C

1. C++和C混合使用的前提之一就是编译器产生兼容的目标文件(.lib和.dll等).所谓"兼容",指的是编译器在"预编译器相依的特性上"一致,如int和double大小,参数压栈机制等,只有在这个基础上才能讨论结合使用C++和C模块的问题. 2. 在1的基础上,要结合使用C++和C的模块,主要有以下几点需要注意: 1). name mangling(名称重整) Name mangling是C++用于支持函数重载的机制,它对重载的函数名称进行一定改变,使得每个函数

Effective C++ 条款3 尽可能用const

1. const可被施加于任何作用域内的对象,函数参数,函数返回类型,成员函数本体.用const修饰指针,如果const出现在*之前,表明指针不能更改所指向的对象的内容,如果const出现在*之后,表明指针只能指向同一块内存.另外int const*p和const int*p含义相同.如果对象成员有普通指针,那么构造该类的一个const对象时,const修饰使得该指针只能指向同一块内存,但指针指向的内容可以改变. 2. 将某些东西声明为const可以帮助编译器侦测出错误用法. 3. 编译器强制实

effective c++ 条款13 use object to manage resources.

请求的系统资源需要最终还回系统,为了避免遗忘返还这个动作,可以利用析构函数在object销毁时自动调用的特点来实现. 简单说就是用object来管理资源. 以内存资源为例 class Investment {}; Investment* creatInvestment(){...} // factory function to produce investment object void main() { Investment* pInv = creatInvestment();//call t

effective c++ 条款3 use const whereever you can

1 const 传达的意思应该是这个变量是常量不能更改 2 const 在 * 左边表示数据是const,在右边表示指针是const // char greeting[] = "hello"; char* p = greeting; //const *: const data //* const: const pointer char const *p1 = greeting; // const data char * const p2 = greeting; // const poi

effective c++ 条款18 make interface easy to use correctly and hard to use incorrectly

举一个容易犯错的例子 class Date { private: int month; int day; int year; public: Date(int month,int day,int year) { this->month = month; ... } } //wrong example Date date(30,3,1995);//should be 3,30 Date date(2,30,1995);//should be 3,30 使用类型可避免这个问题 class Month

effective c++ 条款9 do not call virtual function in constructor or deconstructor

在构造函数中不要调用virtual函数,调用了也不会有预期的效果. 举个例子 class Transaction { public: Transaction() { log(); } virtual void log() =0; } class BusinessTransaction: public Transaction { public: virtual void log() { ;//log something here } } BusinessTransaction b_trx; b_t