STL主要由为容器,迭代器和算法创建的模板组成,但是也有一些功能模板。其中之一叫做advance。Advance将一个指定的迭代器移动指定的距离:

 template<typename IterT, typename DistT> // move iter d units
void advance(IterT& iter, DistT d); // forward; if d < 0,
// move iter backward

从概念上来说,advance仅仅做了iter += d,但是advance并不是用这种方式实现的,因为只有随机访问迭代器支持+=操作。其他一些更加弱的迭代器类型必须通过反复的++或者--d次来实现advance。

1. 五种迭代器种类回顾

你不记得STL迭代器的种类了?没问题,我们现在做一些简单的回顾。总共有5类迭代器,对应着它们支持的五种操作。输入迭代器(input iterator只能向前移动,一次只能移动一步,只能读取它们指向的内容,并且只能读一次。这种迭代器模拟的是输入文件的读指针;C++库的istream_iterator是这种迭代器的代表。输出迭代器(output
iterator
同输入迭代器类似,但是对于输出迭代器来说:它们只能向前移动,每次只能移动一步,只能对它们指向的内存进行写操作,并且只能写一次。它们模拟的是输出文件的写指针;ostream_iterator代表了这种迭代器。这是两类功能最弱的迭代器。因为输入和输出迭代器只能向前移动,只能对它们所指向的内容进行读写最多一次,它们只能为one-pass算法所使用。

一个更加强大的迭代器种类由前向迭代器(forward
iterator
组成。这种迭代器能做输入和输出迭代器能做的所有事情,并且它们能对它们指向的内容进行多次的读写。这就使得它们能被multi-pass算法所使用。STL没有提供单链表,但是一些库却提供了(通常叫做slist),这种容器中的迭代器为前向迭代器。TR1中的哈希容器(Item 54)迭代器也可能是前向迭代器。

双向迭代器(bidirectional
iterators
和前向迭代器相比添加了向后移动的能力。为STL中的list提供的迭代器就属于这种类别,为set,multiset,map和multimap提供的迭代器也是这种类别。

最强大的迭代器类别叫做随机访问迭代器(random
access iterator
。这种类型的迭代器和双向迭代器相比添加了执行“迭代器运算(iterator arithmetic)”的能力,也就是在常量时间内向前或者向后跳跃任意的距离。这种运算同指针运算类似,不要吃惊,因为随机访问迭代器模拟的就是内建类型的指针,内建类型指针的行为表现就如同随机访问迭代器。Vector,deque和string迭代器都是随机访问迭代器。

为了识别这五种迭代器类型,C++在标准库中为五种迭代器类型提供了一个“tag结构体”:

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

这些结构体之间的继承关系是有效的“is-a”关系(Item32):所有的前向迭代器同样是输入迭代器,等等。我们很快会看到这种继承的效用。

2. 如何实现advance简析

回到advance。考虑到不同的迭代器功能,实现advance的一种方法是使用循环的最小公分母策略:对迭代器进行反复加或者减。然而,这个方法会花费线性的时间。随机访问迭代器支持常量时间的迭代器算法,在我们需要的时候会使用它的这种能力。

我们真正想要的是像下面这样去实现advance:

 template<typename IterT, typename DistT>
void advance(IterT& iter, DistT d)
{
if (iter is a random access iterator) { iter += d; // use iterator arithmetic } // for random access iters else { if (d >= ) { while (d--) ++iter; } // use iterative calls to
else { while (d++) --iter; } // ++ or -- for other
} // iterator categories
}

这需要决定iter是不是一个随机访问迭代器,也就是需要知道它的类型,IterT,是不是随机访问迭代器类型。换句话说,我们需要获取一个类型的相关信息。这也是trait让你所做的:它们允许你在编译期间获取一个类型的相关信息

3. Traits技术分析

3.1 使用traits技术的要求

Traits不是C++中的关键字或者一个预定义的概念;它们是一种技术,也是一个C++程序员需要遵守的约定。使用这项技术的一个要求是它必须使内建类型同用户自定义类型一样能够很好的工作。例如,如果advance的入参为一个指针(像const
char*)和一个Int,advance必须能够工作,但是这就意味着trait技术必须能够使用在像指针一样的内建类型上。

Traits必须能够同内建类型一块工作就意味着不能在类型内部嵌入一些信息,因为没有方法在指针内部嵌入信息。于是对于一种类型的traits信息,必须是放在类型外部的。标准的技术是将其放在模板和模板的一个或多个特化实例中。对于迭代器来说,标准库中的模板被命名为iterator_traits:

 template<typename IterT> // template for information about
struct iterator_traits; // iterator types

正如你所见的,iterator_traits是一个结构体。按照惯例,traits经常被实现为一个结构体。另外一种常用手法是将实现traits的结构体替换为traits class(这不是我说的)。

Iterator_traits的工作方式是对于每个类型IterT,在结构体iterator_traits<IterT>中声明一个叫做iterator_category的typedef。这个typedef唯一确认了IterT的迭代器类别。

3.2 实现traits class需要处理用户自定义类型

Iterator_traits会在两部分中实现它。首先,它强制任何用户自定义的迭代器类型必须包含一个叫做iterator_category的内嵌typedef,它能够识别合适的tag结构体。举个例子,deque的迭代器是随机访问的,因此一个deque迭代器的类会像是下面这个样子:

 template < ... >                                                                         // template params elided

 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类的内嵌typedef:

 // the iterator_category for type IterT is whatever IterT says it is;

 // see Item 42 for info on the use of “typedef typename”

 template<typename IterT>

 struct iterator_traits {

 typedef typename IterT::iterator_category iterator_category;

 ...

 };

3.3 实现traits class需要处理指针类型

这对用户自定义类型来说会工作的很好,但是对于指针迭代器来说就不工作了,因为指针中没有内嵌的typedef。Iterator_trait实现的第二部分需要处理指针迭代器。

为了支持这种迭代器,iterator_traits为指针类型提供了一种部分模板特化(partial template specialization)。指针的行为表现同随机访问迭代器类似,所以iterator_trait为它们指定了随机访问类别:

 template<typename T> // partial template specialization
struct iterator_traits<T*> // for built-in pointer types
{
typedef random_access_iterator_tag iterator_category;
...
};

3.4 实现traits class总结

到现在你应该了解了如何设计和实现一个traits class:

  • 确认你想要支持的类型的一些信息(例如,对于迭代器来说,它们的迭代器类别)。
  • 为了确认信息,你需要选择一个名字(例如,iterator_category)
  • 为你想支持的类型提供包含相关信息的一个模板和一些特化(例如,iterator_traits)

4. 使用traits
class实现advance

4.1 类别判断不应该在运行时进行

考虑iterator_traits——实际上是std::iterator_traits,既然它是C++标准库的一部分——我们能为advance的实现精炼成我们自己的伪代码:

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

虽然这看上去很有希望,但它不会如我们所愿。第一,它会导致编译问题,这个问题我们将在Item 48研究;现在,有更加基本的问题需要考虑。IterT的类型在编译期被确认,所以iterator_traits<IterT>::iterator_category同样能够在编译期被确定。但是if语句会在运行时被评估(除非你的优化器足够疯狂,把if语句去掉)。为什么将本来可以在编译期做的事情挪到运行时去做呢?它会浪费时间,并且会造成执行代码膨胀。

4.2 将条件评估提前到编译期——使用重载

我们真正想要的是为类型提供一个能够在编译期进行评估的条件结构(也就是一个if…else语句)。C++已经有一种方法来实现这种行为。她叫做重载

当你重载某个函数f的时候,你为不同的重载函数指定不同的参数类型。当你调用f时,编译器会根据你所传递的参数选择最佳匹配重载函数。编译器会说:“如果这个重载对于传递过来的参数来说是最佳匹配,那么调用这个f;如果另外一个重载函数是最佳匹配,那么调用另外一个函数;如果第三个函数是最佳匹配,调用第三个”等等,看到了么?这是一个与类型相关的编译期条件结构。为了让advance表现出我们想要的行为,所有我们必须要做的是创建一个重载函数的多个版本,它们包含了advance的“内脏”,每个函数都带有一个不同类型的iterator_category对象。我将这些函数命名为doAdvance:

 template<typename IterT, typename DistT> // use this impl for
void doAdvance(IterT& iter, DistT d, // random access
std::random_access_iterator_tag) // iterators
{
iter += d;
}
template<typename IterT, typename DistT> // use this impl for
void doAdvance(IterT& iter, DistT d, // bidirectional
std::bidirectional_iterator_tag) // iterators
{
if (d >= ) { while (d--) ++iter; }
else { while (d++) --iter; }
}
template<typename IterT, typename DistT> // use this impl for
void doAdvance(IterT& iter, DistT d, // input iterators
std::input_iterator_tag)
{
if (d < ) {
throw std::out_of_range("Negative distance"); // see below
}
while (d--) ++iter;
}

因为forward_iterator_tag继承自input_iterator_tag,为input_iterator_tag提供的doAdvance版本同样能够处理forward迭代器。这是在不同iterator_tag结构体之间引入继承的动机。(事实上,这也是所有的使用public继承的动机:为基类类型实现的代码对于派生类类型来说同样适用。)

对于随机访问迭代器和双向迭代器来说,advance的特化版本同时能够做正向或者负向的移动,但是对于forward迭代器或者input迭代器来说,如果你想进行一个负向的移动就会出现未定义行为。实现中如果简单的假设d是非负的,当传递一个负参数时,你就会进入一个很长的循环中,直到d变为0为止。在上面的代码中,我所使用的替代方法是抛出一个异常。两种实现都是有效的。这就是未定义行为的诅咒:你不能预测出来会发成什么。

考虑为doAdvance所重载的不同版本,所有advance需要做的就是调用它们,传递一个额外的合适的迭代器类别对象,最后编译器就能够使用重载方案来调用合适的实现:

 template<typename IterT, typename DistT>
void advance(IterT& iter, DistT d)
{
doAdvance( // call the version
iter, d, // of doAdvance
typename // that is
std::iterator_traits<IterT>::iterator_category() // appropriate for
); // iter’s iterator } // category

5. traits class使用总结

我们可以总结一下如何使用traits class:

  • 创建一系列重载的”worker”函数或者函数模板(例如,doAdvance),通过使用traits 参数来进行区分。根据传递的traits信息来对应的实现每个函数。
  • 创建一个“master”函数或者函数模板(例如,advance)来调用worker,将traits class提供的信息传递进去。

Traits被广泛使用在标准库中。对于iterator_traits来说,除了iterator_category,还为迭代器提供了四种其它的信息(最有用的就是value_type—Item 42中给出了一个例子。)还有char_traits,存储了字符类型的信息,numeric_limits,提供数字类型信息,例如,它们能够表示的最大和最小值等等。(numeric_limits这个名字可能让你感到意外,因为传统的命名方式是以“traits”结尾,但是numeric_limits没有遵守。)

TR1(Item 54)为了为类型提供信息引入了大量的新的traits class,包括is_fundamental<T>(判断T是否为内建类型),is_array<T>(判断T是否为数组),和is_base_of<T1,T2>(判断T1和T2是否相同或者是T2的基类)。TR1向标准C++中添加了大概有50个traits classes。

6. 本条款总结

  • Traits classes使得在编译期就能够获得类型信息。它们用模板和模板特化版本来进行实现。
  • 利用重载,traits classes使得在类型上执行编译期if-else测试成为可能。

读书笔记 effective c++ Item 47 使用traits class表示类型信息的更多相关文章

  1. 读书笔记 effective c++ Item 54 让你自己熟悉包括TR1在内的标准库

    1. C++0x的历史渊源 C++标准——也就是定义语言的文档和程序库——在1998被批准.在2003年,一个小的“修复bug”版本被发布.然而标准委员会仍然在继续他们的工作,一个“2.0版本”的C+ ...

  2. 读书笔记 effective c++ Item 55 让你自己熟悉Boost

    你正在寻找一个高质量的,开源的,与平台和编译器无关的程序库的集合?看一下Boost吧.想加入一个由雄心勃勃的,充满天赋的正致力于最高水平的程序库设计和实现工作的C++程序员们组成的团体么?看一下Boo ...

  3. 读书笔记 effective c++ Item 48 了解模板元编程

    1. TMP是什么? 模板元编程(template metaprogramming TMP)是实现基于模板的C++程序的过程,它能够在编译期执行.你可以想一想:一个模板元程序是用C++实现的并且可以在 ...

  4. 读书笔记 effective c++ Item 9 绝不要在构造函数或者析构函数中调用虚函数

    关于构造函数的一个违反直觉的行为 我会以重复标题开始:你不应该在构造或者析构的过程中调用虚函数,因为这些调用的结果会和你想的不一样.如果你同时是一个java或者c#程序员,那么请着重注意这个条款,因为 ...

  5. 读书笔记 effective c++ Item 1 将c++视为一个语言联邦

    Item 1 将c++视为一个语言联邦 如今的c++已经是一个多重泛型变成语言.支持过程化,面向对象,函数式,泛型和元编程的组合.这种强大使得c++无可匹敌,却也带来了一些问题.所有“合适的”规则看上 ...

  6. 读书笔记 effective c++ Item 11 在operator=中处理自我赋值

    1.自我赋值是如何发生的 当一个对象委派给自己的时候,自我赋值就会发生: class Widget { ... }; Widget w; ... w = w; // assignment to sel ...

  7. 读书笔记 effective c++ Item 12 拷贝对象的所有部分

    1.默认构造函数介绍 在设计良好的面向对象系统中,会将对象的内部进行封装,只有两个函数可以拷贝对象:这两个函数分别叫做拷贝构造函数和拷贝赋值运算符.我们把这两个函数统一叫做拷贝函数.从Item5中,我 ...

  8. 读书笔记 effective c++ Item 13 用对象来管理资源

    1.不要手动释放从函数返回的堆资源 假设你正在处理一个模拟Investment的程序库,不同的Investmetn类型从Investment基类继承而来, class Investment { ... ...

  9. 读书笔记 effective c++ Item 14 对资源管理类的拷贝行为要谨慎

    1. 自己实现一个资源管理类 Item 13中介绍了 “资源获取之时也是初始化之时(RAII)”的概念,这个概念被当作资源管理类的“脊柱“,也描述了auto_ptr和tr1::shared_ptr是如 ...

随机推荐

  1. Activemq 宕机解决方案

    关于消息服务的集群,大概分为Consumer集群(消费者集群)和Broker集群(消息服务器集群)两种.ActiveMQ提供了一种叫做失效转移(也叫故障转移,FailOver)的策略.失效转移提供了在 ...

  2. 在Azure上部署带有GPU的深度学习虚拟机

    1. 登录https://portal.azure.com 2. 点击"+创建",在弹出的页面搜索"deep learning toolkit for the DSVM& ...

  3. SQL Server-聚焦深入理解死锁以及避免死锁建议(三十三)

    前言 终于进入死锁系列,前面也提到过我一直对隔离级别和死锁以及如何避免死锁等问题模棱两可,所以才鼓起了重新学习SQL Server系列的勇气,本节我们来讲讲SQL Server中的死锁,看到许多文章都 ...

  4. R语言入门(二)基础语法

    1.help可以提供帮助,如help(nchar), help("[["),或者用?nchar也能获取帮助.example(nchar)可以获取到某个主题的使用方法. 2.ncha ...

  5. Mac OSX Sierra WiFi connecting problem

    吐槽一下,苹果的质量管控越来越差了. Mac OSX Sierra有时突然或升级后会遇到wifi不停重连连不上问题,现象为不停地连接wifi. 网上有人说删除 /Library/Preferences ...

  6. cocos2dx 中文路径编译错误记录

    '/Q' 不是内部或外部命令,也不是可运行的程序1> 或批处理文件.1> 'y' 不是内部或外部命令,也不是可运行的程序1> 或批处理文件.1>C:\Program Files ...

  7. [.net 面向对象程序设计深入](24)实战设计模式——策略模式(行为型)

    [.net 面向对象程序设计深入](24)实战设计模式——策略模式(行为型) 1,策略模式定义 策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换.策略模式让算法独立于使用它 ...

  8. storm1.0节点间消息传递过久分析及调优

    序:最近对storm平台系统进行性能检测发现偶尔会出现oncebolt向另一个twobolt发送数据后,twobolt要500毫秒后才接收到进行处理.这里简单说增大twobolt的并行度即可解决,但是 ...

  9. python 机器学习 决策树

    决策树(Decision Trees ,DTs)是一种无监督的学习方法,用于分类和回归. 优点:计算复杂度不高,输出结果易于理解,对中间值缺失不敏感,可以处理不相关的特征数据缺点:可能会产生过度匹配的 ...

  10. CAS进行https到http的改造方案,结合cookie源码分析

    先说具体的改造方案: 服务端: 一.CAS Server端的修改 1.找到cas\WEB-INF\deployerConfigContext.xml 对以下Bean增加参数p:requireSecur ...