程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> Item 47:使用Traits類提供類型信息

Item 47:使用Traits類提供類型信息

編輯:關於C++

Item 47: Use traits classes for information about types.

C++中的 Traits 類可以在編譯期提供類型信息,它是用Traits模板及其特化來實現的。 通過方法的重載,可以在編譯期對類型進行”if…else”判斷。我們通過STL中的一個例子來介紹Traits的實現和使用。

本文以iterator_traits為例介紹了如何實現traits類,以及如何使用traits類(在Item 42中提到過iterator_traits)。 其實C++標准庫中還提供了很多其他的traits,比如char_traits,numeric_limits等。

STL提供了很多的容器、迭代器和算法,其中的advance便是一個通用的算法,可以讓一個迭代器移動n步:

template
void advance(IterT& iter, DistT d);     // 如果d小於0,就逆向移動

STL迭代器回顧

  • 最簡單的迭代器是輸入迭代器(input iterator)和輸出迭代器(output iterator), 它們只能向前移動,可以讀取/寫入它的當前位置,但只能讀寫一次。比如ostream_iterator就是一個輸出迭代器。

  • 比它們稍強的是前向迭代器(forward iterator),可以多次讀寫它的當前位置。 單向鏈表(slist,STL並未提供)和TR1哈希容器的迭代器就屬於前向迭代器。

  • 雙向迭代器(bidirectional iterator)支持前後移動,支持它的容器包括set,multiset,map,multimap

  • 隨機訪問迭代器(random access iterator)是最強的一類迭代器,可以支持+=,-=等移動操作,支持它的容器包括vector,deque,string等。

    Tag 結構體

    對於上述五種迭代器,C++提供了五種Tag來標識迭代器的類型,它們之間是”is-a”的關系:

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

    現在回到advance的問題,它的實現方式顯然取決於Iter的類型:

    template
    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
    }
    

    怎麼得到Iter的類型呢?這正是traits的作用。

    Traits

    traits允許我們在編譯期得到類型的信息。traits並非一個關鍵字,而是一個編程慣例。

    traits的另一個需求在於advance對與基本數據類型也能正常工作,比如char*。所以traits不能借助類來實現, 於是我們把traits放到模板中。比如:

    template          // template for information about
    struct iterator_traits;           // iterator types
    

    iterator_traits將會標識IterT的迭代器類別。iterator_traits的實現包括兩部分:

    • 用戶定義類型的迭代器
    • 基本數據類型的指針

      用戶類型的迭代器

      在用戶定義的類型中,typedef該類型支持迭代器的Tag,例如deque支持隨機迭代器:

      template < ... >                    // template params elided
      class deque {
      public:
        class iterator {
        public:
          typedef random_access_iterator_tag iterator_category;
        }:
      };
      

      然後在全局的iterator_traits模板中typedef那個用戶類型中的Tag,以提供全局和統一的類型識別。

      template
      struct iterator_traits {
        typedef typename IterT::iterator_category iterator_category;
      };
      

      基本數據類型的指針

      上述辦法對基本數據類型的指針是不起作用的,我們總不能在指針裡面typedef一個Tag吧? 其實這時只需要偏特化iterator_traits,因為內置類型指針都是可以隨機訪問的:

      template               // partial template specialization
      struct iterator_traits{
        typedef random_access_iterator_tag iterator_category;
      };
      

      你已經看到了實現一個traits類的整個過程:

      1. 確定你希望提供的類型信息。比如你希望提供dequeiterator類型;
      2. 為那個信息起一個名字。比如iterator_catetory
      3. 提供一個模板以及必要的特化,來包含你希望提供的類型信息。比如iterator_traits

        advance的實現

        我們已經用iterator_traits提供了迭代器的類型信息,是時候給出advance的實現了。

        template
        void advance(IterT& iter, DistT d) {
          if (typeid(typename std::iterator_traits::iterator_category) ==
            typeid(std::random_access_iterator_tag))
          ...
        }
        

        上述實現其實並不完美,至少if語句中的條件在編譯時就已經決定,它的判斷卻推遲到了運行時(顯然是低效的)。 在編譯時作此判斷,需要為不同的iterator提供不同的方法,然後在advance裡調用它們。

        template
        void advance(IterT& iter, DistT d) {
          doAdvance(                                              // call the version
            iter, d,                                              // of doAdvance
            typename std::iterator_traits::iterator_category()
          );                                                     
        }                                                       
        
        // 隨機訪問迭代器
        template
        void doAdvance(IterT& iter, DistT d, std::random_access_iterator_tag) {
          iter += d;
        }
        
        // 雙向迭代器
        template
        void doAdvance(IterT& iter, DistT d, std::bidirectional_iterator_tag) {
          if (d >= 0) { while (d--) ++iter; }
          else { while (d++) --iter; }
        }
        
        // 輸入迭代器
        template
        void doAdvance(IterT& iter, DistT d, std::input_iterator_tag) {
          if (d < 0 ) {
             throw std::out_of_range("Negative distance");    // see below
          }
          while (d--) ++iter;
        }
        

        總結一下上面代碼是如何使用traits類的:

        1. 創建一系列的”worker”函數,擁有不同的traits參數。根據traits參數來提供相應的實現;
        2. 創建一個”master”函數來調用這些”worker”,並將traits類提供的信息傳遞給”worker”。
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved