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。

iterator_traits  的工作方法是对于每一个  IterT  类型,在 struct(结构体) iterator_traits<IterT>  中声明一个名为  iterator_category  的 typedef。这个 typedef 被看成是  IterT  的 iterator category(迭代器种类)。

iterator_traits 通过两部分实现这一点。首先,它强制要求任何 user-defined iterator(用户定义迭代器)类型必须包含一个名为 iterator_category 的嵌套 typedef 用以识别适合的 tag struct(卷标结构)。例如,deque 的 iterators(迭代器)是随机访问的,所以一个 deque iterators 的 class 看起来就像这样:

template < ... >                    // template params elided
class deque {
public:
  class iterator {
  public:
    typedef random_access_iterator_tag iterator_category;
    ...
  };
  ...
};
然而, list  的 iterators(迭代器)是双向的,所以它们是这样做的:

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;
  ...
};
以上这种方法对于用户自定义类型行得通,但是对于指针(也是一种迭代器)却行不通,因为指针不可能嵌套typedef。

所以为了支持指针迭代器,iterator_traits特别针对指针类型提供一个偏特化版本。由于指针的行径与ramdom access迭代器类似,所以iterator_traits为指针指定的迭代器类型是:

template<typename IterT>
struct iterator_traits<TierT*> {
	typedef random_access_iterator_tag iterator_category;
	...
};


(五)

到此为止,你了解了如何设计和实现一个 traits class:

  • 识别你想让它可用的关于类型的一些信息(例如,对于 iterators(迭代器)来说,就是它们的 iterator category(迭代器种类))。
  • 选择一个名字标识这个信息(例如,iterator_category)。
  • 提供一个 template(模板)和一系列 specializations(特化)(例如,iterator_traits),它们包含你要支持的类型的信息。

给出了 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...else语句)判断“编译期核定成功”之类型。恰巧C++有一个取得这种行为的办法,那就是重载。

所以为了让advance的行为如我们所期望,我们需要做的是产生两版重载函数,内含advance的本质内容,但各自接受不同类型的iterator_category对象。

我们必须要做的全部就是创建一个包含 advance 的“内容”的重载函数的多个版本(此处原文有误,根据作者网站勘误修改——译者注),声明它们取得不同 iterator_category object 的类型。我为这些函数使用名字 doAdvance

template <typename IterT, typename DistT>
void doAdvance(IterT& iter, DistT d, random_access_iterator_tag) {
	iter += d;
}

template <typename IterT, typename DistT>
void doAdcance(IterT& iter, DistT d, bidirectionl_iterator_tag) {
	if(d >= 0) {
		while (d--) ++iter;
	}else {
		while (d++) --iter;
	}
}

template <typename IterT, typename DistT>
void doAdvance(IterT& iter, DistT d, input_iterator_tag) {
	if (d < 0) {
		throw out_of_range("Negative distance");
	}
	while (d--) ++iter;
}
因为  forward_iterator_tag  从  input_iterator_tag  继承而来,针对  input_iterator_tag  的  doAdvance  版本也将处理 forward iterators(前向迭代器)。这就是在不同的  iterator_tag  structs 之间继承的动机。(实际上,这是所有 public inheritance(公有继承)的动机的一部分:使针对 base class types(基类类型)写的代码也能对 derived class types(派生类类型)起作用。)


(六)

有了上面定义的这些重载的函数,那么advance需要做的只是调用它们并额外传递一个对象,

template<typename IterT, typename DistT>
void advance(IterT& iter, DistT d)
{
  doAdvance(                                             
    iter, d,                                             
    typename                                             
      std::iterator_traits<IterT>::iterator_category()   
  );                                                      
}   

我们现在能够概述如何使用一个 traits class 了:

  • 创建一套重载的 "worker" functions(函数)或者 function templates(函数模板)(例如,doAdvance),它们在一个 traits parameter(形参)上不同。与传递的 traits 信息一致地实现每一个函数。
  • 创建一个 "master" function(函数)或者 function templates(函数模板)(例如,advance)调用这些 workers,传递通过一个 traits class 提供的信息。


请记住:

(1)traits classes 使关于类型的信息在编译期间可用。它们使用 templates(模板)和 template specializations(模板特化)实现。

(2)结合 overloading(重载),traits classes 使得执行编译期类型 if...else 检验成为可能。












  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值