当前位置:天才代写 > tutorial > C语言/C++ 教程 > C++箴言:为范例信息利用特征类

C++箴言:为范例信息利用特征类

2017-11-06 08:00 星期一 所属: C语言/C++ 教程 浏览:677

副标题#e#

STL 主要是由 containers(容器),iterators(迭代器)和 algorithms(算法)的 templates(模板)组成的,可是也有几个 utility templates(实用模板)。个中一个被称为 advance。advance 将一个指定的 iterator(迭代器)移动一个指定的间隔:

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 不能这样实现,因为只有 random access iterators(随时机见迭代器)支持 += operation。不足强力的 iterator(迭代器)范例不得不通过重复操作 ++ 或 — d 次来实现 advance。

你不记得 STL iterator categories(迭代器种类)了吗?没问题,我们这就做一个简朴回首。对应于它们所支持的操纵,共有五种 iterators(迭代器)。input iterators(输入迭代器)只能向前移动,每次只能移动一步,只能读它们指向的对象,并且只能读一次。它们以一个输入文件中的 read pointer(读指针)为原型;C++ 库中的 istream_iterators 就是这一种类的典范代表。output iterators(输出迭代器)与此雷同,只不外用于输出:它们只能向前移动,每次只能移动一步,只能写它们指向的对象,并且只能写一次。它们以一个输出文件中的 write pointer(写指针)为原型;ostream_iterators 是这一种类的典范代表。这是两个最不强力的 iterator categories(迭代器种类)。因为 input(输入)和 output iterators(输出迭代器)只能向前移动并且只能读可能写它们指向的处所最多一次,它们只适合 one-pass 运算。

一个更强力一些的 iterator category(迭代器种类)是 forward iterators(前向迭代器)。这种 iterators(迭代器)能做 input(输入)和 output iterators(输出迭代器)可以做到的每一件工作,再加上它们可以读可能写它们指向的对象一次以上。这就使得它们可用于 multi-pass 运算。STL 没有提供 singly linked list(单向链表),但某些库提供了(凡是被称为 slist),而这种 containers(容器)的 iterators(迭代器)就是 forward iterators(前向迭代器)。TR1 的 hashed containers(哈希容器)的 iterators(迭代器)也可以属于 forward category(前向迭代器)。


#p#副标题#e#

bidirectional iterators(双向迭代器)为 forward iterators(前向迭代器)加上了和向前一样的向后移动的本领。STL 的 list 的 iterators(迭代器)属于这一种类,set,multiset,map 和 multimap 的 iterators(迭代器)也一样。

最强力的 iterator category(迭代器种类)是 random access iterators(随时机见迭代器)。这种 iterators(迭代器)为 bidirectional iterators(双向迭代器)加上了 "iterator arithmetic"(“迭代器运算”)的本领,也就是说,在常量时间里向前可能向后跳转一个任意的间隔。这样的运算雷同于指针运算,这并不会让人感想惊奇,因为 random access iterators(随时机见迭代器)就是以 built-in pointers(内建指针)为原型的,而 built-in pointers(内建指针)可以和 random access iterators(随时机见迭代器)有同样的行为。vector,deque 和 string 的 iterators(迭代器)是 random access iterators(随时机见迭代器)。

对付五种 iterator categories(迭代器种类)中的每一种,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_access_iterator_tag: public bidirectional_iterator_tag {};

这些布局体之间的 inheritance relationships(担任干系)是合法的 is-a 干系:所有的 forward iterators(前向迭代器)也是 input iterators(输入迭代器),等等,这都是创立的。我们不久就会看到这个 inheritance(担任)的坚守。

#p#副标题#e#

可是返回到 advance。对付差异的 iterator(迭代器)本领,实现 advance 的一个要领是利用重复增加或淘汰 iterator(迭代器)的轮回的 lowest-common-denominator(最小共通特性)计策。然而,这个要领要耗费 linear time(线性时间)。random access iterators(随时机见迭代器)支持 constant-time iterator arithmetic(常量时间迭代器运算),当它呈现的时候我们最好能操作这种本领。

我们真正想做的就是大抵像这样实现 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 >= 0) { while (d--) ++iter; } // use iterative calls to
  else { while (d++) --iter; } // ++ or -- for other
 } // iterator categories
}

#p#分页标题#e#

这就需要可以或许确定 iter 是否是一个 random access iterators(随时机见迭代器),依次下来,就需要知道它的范例,IterT,是否是一个 random access iterators(随时机见迭代器)范例。换句话说,我们需要获得关于一个范例的某些信息。这就是 traits 让你做到的:它们答允你在编译进程中获得关于一个范例的信息。 traits 不是 C++ 中的一个要害字或预界说布局;它们是一项技能和 C++ 措施员遵守的老例。成立这项技能的要求之一是它在 built-in types(内建范例)上必需和在 user-defined types(用户界说范例)上一样有效。譬喻,假如 advance 被一个指针(譬如一个 const char*)和一个 int 挪用,advance 必需有效,可是这就意味着 traits 技能必需合用于像指针这样的 built-in types(内建范例)。

#p#副标题#e#

traits 对 built-in types(内建范例)必需有效的事实意味着将信息嵌入到范例内部是不行以的,因为没有步伐将信息嵌入指针内部。那么,一个范例的 traits 信息,必需在范破例部。尺度的要领是将它放到 template(模板)以及这个 template(模板)的一个或更多的 specializations(特化)中。对付 iterators(迭代器),尺度库中 template(模板)被称为 iterator_traits:

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

就像你能看到的,iterator_traits 是一个 struct(布局体)。按照老例,traits 老是被实现为 struct(布局体)。另一个老例就是用来实现 traits 的 structs(布局体)以 traits classes(这可不是我假造的)闻名。

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(迭代器)是双向的,所以它们是这样做的:

#p#副标题#e# template < ... >
class list {
 public:
 class iterator {
  public:
  typedef bidirectional_iterator_tag iterator_category;
  ...
 };
 ...
};

iterator_traits 仅仅是简朴地仿照了 iterator class 的嵌套 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;
 ...
};

这样对付 user-defined types(用户界说范例)能很好地运转。可是对付自己是 pointers(指针)的 iterators(迭代器)基础不起浸染,因为不存在雷同于带有一个嵌套 typedef 的指针的对象。iterator_traits 实现的第二个部门处理惩罚自己是 pointers(指针)的 iterators(迭代器)。

为了支持这样的 iterators(迭代器),iterator_traits 为 pointer types(指针范例)提供了一个 partial template specialization(部门模板特化)。pointers 的行为雷同 random access iterators(随时机见迭代器),所以这就是 iterator_traits 为它们指定的种类:

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

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

#p#副标题#e#

·识别你想让它可用的关于范例的一些信息(譬喻,对付 iterators(迭代器)来说,就是它们的 iterator category(迭代器种类))。

·选择一个名字标识这个信息(譬喻,iterator_category)。

·提供一个 template(模板)和一系列 specializations(特化)(譬喻,iterator_traits),它们包括你要支持的范例的信息。

#p#分页标题#e#

给出了 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))
 ...
}

这个固然看起来有点但愿,但它不是我们想要的。在某种状态下,它会导致编译问题,这个问题我们今后再来研究它,此刻,有一个更基本的问题要接头。IterT 的范例在编译期间是已知的,所以 iterator_traits<IterT>::iterator_category 可以在编译期间被确定。可是 if 语句照旧要到运行时才气被求值。为什么要到运行时才做我们在编译期间就能做的工作呢?它挥霍了时间(严格意义上的),并且使我们的执行码膨胀。

我们真正想要的是一个针对在编译期间被鉴此外范例的 conditional construct(条件布局)(也就是说,一个 if…else 语句)。可巧的是,C++ 已经有了一个获得这种行为的要领。它被称为 overloading(重载)。

当你重载某个函数 f 时,你为差异的 overloads(重载)指定差异的 parameter types(形参范例)。当你挪用 f 时,编译器会按照被通报的 arguments(实参)挑出最佳的 overload(重载)。根基上,编译器会说:“假如这个 overload(重载)与被通报的对象是最佳匹配的话,就挪用这个 f;假如另一个 overload(重载)是最佳匹配,就挪用它;假如第三个 overload(重载)是最佳的,就挪用它”等等。看到了吗?一个针对范例的 compile-time conditional construct(编译时条件布局)。为了让 advance 拥有我们想要的行为方法,我们必需要做的全部就是建设一个包括 advance 的“内容”的重载函数的多个版本(此处原文有误,按照作者网站勘误修改——译者注),声明它们取得差异 iterator_category object 的范例。我为这些函数利用名字 doAdvance:

#p#副标题#e# 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 >= 0) { 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 < 0 ) {
  throw std::out_of_range("Negative distance"); // see below
 }
 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 的类型对付 random access(随时机见)和 bidirectional iterators(双向迭代器)答允正的和负的移动间隔,可是假如你试图移动一个 forward(前向)或 input iterator(输入迭代器)一个负的间隔,则行为是未界说的。在我查抄过的实现中简朴地假设 d 长短负的,因而假如一个负的间隔被传入,则进入一个直到计数降为零的很是长的轮回。在上面的代码中,我展示了改为一个异常被抛出。这两种实现都是正确的。未界说行为的谩骂是:你无法预知会产生什么。

#p#副标题#e#

给出针对 doAdvance 的各类重载,advance 需要做的全部就是挪用它们,通报一个适当的 iterator category(迭代器种类)范例的特别 object 以便编译器操作 overloading resolution(重载理会)来挪用正确的实现:

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

我们此刻可以或许概述如何利用一个 traits class 了:

·建设一套重载的 "worker" functions(函数)可能 function templates(函数模板)(譬喻,doAdvance),它们在一个 traits parameter(形参)上差异。与通报的 traits 信息一致地实现每一个函数。

#p#分页标题#e#

·建设一个 "master" function(函数)可能 function templates(函数模板)(譬喻,advance)挪用这些 workers,通报通过一个 traits class 提供的信息。

traits 遍及地用于尺度库中。有 iterator_traits,虽然,再加上 iterator_category,提供了关于 iterators(迭代器)的四块其它信息(个中最常用的是 value_type )。尚有 char_traits 持有关于 character types(字符范例)的信息,尚有 numeric_limits 提供关于 numeric types(数值范例)的信息,譬喻,可暗示值的最小值和最大值,等等。(名字 numeric_limits 令人有些奇怪,因为关于 traits classes 更常用的老例是以 "traits" 竣事,可是它就是被叫做 numeric_limits,所以 numeric_limits 就是我们用的名字。)

TR1引入了一大批新的 traits classes 提供关于范例的信息,包罗 is_fundamental<T>(T 是否是一个 built-in type(内建范例)),is_array<T>(T 是否是一个 array type(数组范例)),以及 is_base_of<T1, T2>(T1 是否和 T2 沟通可能是 T2 的一个 base class(基类))。合计起来,TR1 在尺度 C++ 中插手了高出 50 个 traits classes。

Things to Remember

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

·团结 overloading(重载),traits classes 使得执行编译期范例 if…else 检讨成为大概。

 

    关键字:

天才代写-代写联系方式