[翻譯] Effective C++, 3rd Edition, Item 47: 為類型資訊使用 traits classes(特徵類)(下)

來源:互聯網
上載者:User

(點擊此處,接上篇)

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_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 是非負的,因而如果一個負的距離被傳入,則進入一個直到計數降為零的非常長的迴圈。在上面的代碼中,我展示了改為一個異常被拋出。這兩種實現都是正確的。未定義行為的詛咒是:你無法預知會發生什麼。

給出針對 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 檢驗成為可能。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.