[翻译] Effective C++, 3rd Edition, Item 47: 为类型信息使用 traits classes(特征类)(下)

来源:互联网 发布:opencv分水岭算法代码 编辑:程序博客网 时间:2024/04/30 09:53

(点击此处,接上篇)

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:

// 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:

  • 识别你想让它可用的关于类型的一些信息(例如,对于 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 语句还是要到运行时才能被求值。为什么要到运行时才做我们在编译期间就能做的事情呢?它浪费了时间(严格意义上的),而且使我们的执行码膨胀。

我们真正想要的是一个针对在编译期间被鉴别的类型的 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

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_taginput_iterator_tag 继承而来,针对 input_iterator_tagdoAdvance 版本也将处理 forward iterators(前向迭代器)。这就是在不同的 iterator_tag structs 之间继承的动机。(实际上,这是所有 public inheritance(公有继承)的动机的一部分:使针对 base class types(基类类型)写的代码也能对 derived class types(派生类类型)起作用。)

advance 的规范对于 random access(随机访问)和 bidirectional iterators(双向迭代器)允许正的和负的移动距离,但是如果你试图移动一个 forward(前向)或 input iterator(输入迭代器)一个负的距离,则行为是未定义的。在我检查过的实现中简单地假设 d 是非负的,因而如果一个负的距离被传入,则进入一个直到计数降为零的非常长的循环。在上面的代码中,我展示了改为一个异常被抛出。这两种实现都是正确的。未定义行为的诅咒是:你无法预知会发生什么。

给出针对 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 信息一致地实现每一个函数。
  • 创建一个 "master" function(函数)或者 function templates(函数模板)(例如,advance)调用这些 workers,传递通过一个 traits class 提供的信息。

traits 广泛地用于标准库中。有 iterator_traits,当然,再加上 iterator_category,提供了关于 iterators(迭代器)的四块其它信息(其中最常用的是 value_type —— Item 42 展示了使用它的示例)。还有 char_traits 持有关于 character types(字符类型)的信息,还有 numeric_limits 提供关于 numeric types(数值类型)的信息,例如,可表示值的最小值和最大值,等等。(名字 numeric_limits 令人有些奇怪,因为关于 traits classes 更常用的惯例是以 "traits" 结束,但是它就是被叫做 numeric_limits,所以 numeric_limits 就是我们用的名字。)

TR1(参见 Item 54)引入了一大批新的 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 检验成为可能。
原创粉丝点击