泛型編程 模板 Traits Tag 類型生成器 對象生成器 策略類

發信人: ajaxchelsea (蘇夢枕), 信區: CPlusPlus
標  題: Re: 我的痛處:怎樣纔算精通C++(推薦資料)
發信站: BBS 水木清華站 (Fri Sep  5 11:00:15 2003), 轉信


【 在 cxwangyi (江南一枝花) 的大作中提到: 】
: 看到本版上的內容多爲OOP,generic programming 的不多,
: 小弟載boost.org上看到一篇文章介紹GP的一些基本概念,
: 寫的清晰簡練:
: http://www.boost.org/more/generic_programming.html
: 雖然完全理解,靈活運用這些概念方法可能需要至少
: 充分體驗STL對這些方法的實用。但是有個目錄也不錯。是把。





主題:泛型編程技術(Generic Programming Techniques)
作者:David Abrahams
譯者:Merlin Ran
原文:
http://www.boost.org/more/generic_programming.html

摘要:
本文大致地介紹一下Boost庫中所用到的泛型編程技術

關鍵字:泛型編程 模板 Traits Tag 類型生成器 對象生成器 策略類

-------------------------------------------------------------------------
-------

1、何謂泛型編程
泛型編程(Generic Programming)關注於產生通用的軟件組件,讓這些組件在不同
的應用場合都能很容易地重用。在C++中,類模板和函數模板是進行泛型編程極爲有
效的機制。有了這兩大利器,我們在實現泛型化的同時,並不需要付出效率的代價。


作爲泛型編程的一個簡單例子,讓我們看一下在C庫中如何讓memcpy()函數泛型化。
一種實現方法可能是這樣的:

void* memcpy(void* region1, const void* region2, size_t n)
{
    const char* first = (const char*)region2;
    const char* last = ((const char*)region2) + n;
    char* result = (char*)region1;
    while (first != last)
        *result++ = *first++;
    return result;
}
    
這個memcpy()函數已經在一定程度上進行了泛型化,採取的措施是使用void*,這樣
該函數就可以拷貝不同類型的數組。但如果我們想拷貝的數據不是放在數組裏,而是
由鏈表來存放呢?我們能不能擴展這個概念,讓它可以拷貝任意的序列?看看memcpy
()的函數體,這個函數對傳入的序列有一個_最小需求_:它需要用某種形式的指針來
遍歷這個序列;訪問指針正指向的元素;把元素寫到目的地;比較指針以判斷何時停
止拷貝。C++標準庫把這樣的需求進行分組,稱之爲概念(concepts)。在這個例子
中就有輸入迭代器(對應於region1)和輸出迭代器(對應於region2)這兩個概念。


如果我們把memcpy()用函數模板重寫,使用輸入迭代器和輸出迭代器作爲模板參數來
描述對序列的需求,我們就可以寫出一個具有較高重用性的copy()函數:

template <typename InputIterator, typename OutputIterator>
OutputIterator
copy(InputIterator first, InputIterator last, OutputIterator result)
{
    while (first != last)
        *result++ = *first++;
    return result;
}
    
使用這個泛型的copy()函數,我們可以拷貝各種各樣的序列,只要它滿足了我們指定
的需求。對外提供了迭代器的鏈表,比如std::list,也可以通過我們的copy()函數
來拷貝:

#include <list>
#include <vector>
#include <iostream>
int main()
{
    const int N = 3;
    std::vector<int> region1(N);
    std::list<int> region2;
    
    region2.push_back(1);
    region2.push_back(0);
    region2.push_back(3);
    
    std::copy(region2.begin(), region2.end(), region1.begin());
    
    for (int i = 0; i < N; ++i)
        std::cout << region1[i] << " ";
    std::cout << std::endl;
}
    
2、何謂概念?
概念就是需求的集合,這些需求可以包含有效表達式、相關類型、不變性,以及複雜
度保證。滿足概念中所有需求的類型,稱之爲此概念的一個樣例。一個概念可以是對
別的概念的擴展,稱之爲概念的細化。

有效表達式:任意的C++表達式。若某類型是概念的一個樣例,那麼把該類型的一個
對象代入此表達式中,該表達式必能通過編譯。 
相關類型:與樣例相關的一些類型,它們和樣例共同出現在一個或多個有效表達式中
。如果樣例類型是自定義類型,則相關類型可以由類定義中所嵌套的一些typedef來
訪問。相關類型經常也通過traits類來訪問。 
不變性:對象的一些運行時特徵,必須爲真。所有用到這個對象的函數,都必須保持
這種特徵。不變性通常以前置條件和後置條件的形式出現。 
複雜度保證:對概念中的有效表達式執行所需時間或資源所做的限制。 
C++標準庫中所使用的概念在SGI STL網站上有詳細的文檔說明。

3、Traits
traits類爲訪問編譯時實體(類型、整數常量,或者地址)的相關信息提供了一條途
徑。比如,類模板std::iterator_traits<T>看起來是這個樣子:

template <class Iterator>
struct iterator_traits {
    typedef ... iterator_category;
    typedef ... value_type;
    typedef ... difference_type;
    typedef ... pointer;
    typedef ... reference;
};
    
該traits的value_type可以讓泛型代碼得知該迭代器所“指向”的是何種類型的數據
;而iterator_category對迭代器的能力進行分類,針對不同類型的迭代器我們選擇
與之適應的最高效的算法。

traits模板有一個很重要的特點:它們是非侵入的(non-intrusive)。我們可以爲
任何類型提供相關信息,不管它是內建的類型,還是第三方的庫中提供的類型。爲了
給某一特定類型指定traits,一般採用部分特化或者全特化traits模板的方式。

欲更深入的瞭解std::iterator_traits,可以參閱SGI提供的資料。std::numeric_limits
<T>也採用了traits技術,提供表示各內建數值類型取值範圍的常量值。

4、標記分派
另外有一種技術經常與traits合用,那就是標記分派。它依據類型的屬性,通過函數
重載進行分派。一個很好的例子就是std::advance()函數。這個函數的功能是將一個
迭代器遞增n次,對於不同類型的迭代器可以有各自優化的實現方法。如果是隨機訪
問迭代器(可以以任意的距離前後跳轉),advance()函數可以用i+=n來實現,既簡
單又高效,只需要常量時間。而其它的迭代器必須一步一步地遞增,需要線性的時間
複雜度。如果是雙向迭代器,n就可能爲負值,因此必須判斷對迭代器到底是增還是
減。

標記分派和traits類的聯繫很緊密。分派所依據的屬性(在這個例子中是iterator_category
)一般都是通過traits類來取得。主advance()函數從iterator_traits中獲得對應於
該iterator的iterator_category,然後調用重載過的advance_dispach()函數。編譯
器依據作爲參數傳給advance_dispach()的iterator_category,選擇合適的重載版本
。標記只是一個極其簡單的類,它的唯一任務就是爲標記分派或者其它類似技術傳遞
必要的信息。

namespace std {
  struct input_iterator_tag { };
  struct bidirectional_iterator_tag { };
  struct random_access_iterator_tag { };

  namespace detail {
    template <class InputIterator, class Distance>
    void advance_dispatch(InputIterator& i, Distance n, input_iterator_tag
) {
      while (n--) ++i;
    }

    template <class BidirectionalIterator, class Distance>
    void advance_dispatch(BidirectionalIterator& i, Distance n,
       bidirectional_iterator_tag) {
      if (n >= 0)
        while (n--) ++i;
      else
        while (n++) --i;
    }

    template <class RandomAccessIterator, class Distance>
    void advance_dispatch(RandomAccessIterator& i, Distance n,
       random_access_iterator_tag) {
      i += n;
    }
  }

  template <class InputIterator, class Distance>
  void advance(InputIterator& i, Distance n) {
    typename iterator_traits<InputIterator>::iterator_category category;
    detail::advance_dispatch(i, n, category);
  }
}
    
5、適配器
適配器是一種類模板,建立在其它類型之上,提供新的接口或者行爲。標準庫中就使
用了適配器,比如std::reverse_iterator通過反轉迭代器的遞增/遞減行爲,適配了
迭代器,還有std::stack,通過適配標準容器,提供一個簡單的堆棧接口。


在這裏可以找到標準庫中所用適配器的深入闡述。

6、類型生成器
類型生成器的工作是依據它的模板參數合成新的類型[注]。類型生成器產生的新類型
一般作爲生成器中嵌套的typedef出現。用類型生成器的主要目的是爲了讓複雜的類
型表達式顯得更簡單些。比如boost::filter_iterator_generator:

template <class Predicate, class Iterator,
    class Value = complicated default,
    class Reference = complicated default,
    class Pointer = complicated default,
    class Category = complicated default,
    class Distance = complicated default
         >
struct filter_iterator_generator {
    typedef iterator_adaptor<
        Iterator,filter_iterator_policies<Predicate,Iterator>,
        Value,Reference,Pointer,Category,Distance> type;
};
    
看起來可真夠複雜的。但現在生成一個合適的filter iterator可就容易多了,只需
要寫:

boost::filter_iterator_generator<my_predicate,my_base_iterator>::type
    
就行了。

7、對象生成器
對象生成器是一種函數模板,依據其參數產生新的對象。可以把它想象成泛型化的構
造函數。有些情況下,欲生成的對象的精確類型很難甚至根本無法表示出來,這時對
象生成器可就管用了。對象生成器的優點還在於它的返回值可以直接作爲函數參數,
而不像構造函數那樣只有在定義變量時纔會調用。Boost和標準庫中用到的對象生成
器大多都加了個前綴make_,比如std::make_pair(const T&, const U&)。

看看下面的例子:

struct widget {
  void tweak(int);
};
std::vector<widget *> widget_ptrs;
    
通過把兩個標準的對象生成器bind2nd和mem_fun合用,我們可以很輕鬆地tweak所有
的widget:

void tweak_all_widgets1(int arg)
{
   for_each(widget_ptrs.begin(), widget_ptrs.end(),
      bind2nd(std::mem_fun(&widget::tweak), arg));
}
    
如果不用對象生成器,上面的函數可能就得這樣來實現:

void tweak_all_widgets2(int arg)
{
   for_each(struct_ptrs.begin(), struct_ptrs.end(),
      std::binder2nd<std::mem_fun1_t<void, widget, int> >(
          std::mem_fun1_t<void, widget, int>(&widget::tweak), arg));
}
    
表達式越複雜,就越需要縮短這些冗長的類型說明,對象生成器的好處也就越能顯現


8、策略類
策略類就是用來傳遞行爲的模板參數。標準庫中的std::allocator就是策略類,把內
存管理的行爲應用到標準容器中。

Andrei Alexandrescu在他的文章中對策略類進入了全面而深入的分析,他寫道:

“策略類精確地反映了設計時的抉擇。他們要麼從其它類中繼承,要麼包含在其它類
中。策略類在同樣的句法結構上提供了不同的策略。使用策略的類把它用到的每一個
策略都作爲模板參數,這樣,用戶就可以自由地選擇需要使用的策略。

策略類的強大在於它們能夠自由地組合在一起。通過把策略類作爲模板參數的辦法來
組合多種策略,代碼量與使用的策略數只成線性關係。”

Andrei認爲策略類的強大源於其小粒度和正交性。Boost在迭代適配器庫中的策略類
運用可能淡化了這一賣點。在這個庫中,所有已適配的迭代器的行爲都放在一個策略
類裏面。其實,Boost並不是開先河者。std::char_traits就是一個策略類,它決定
了std::basic_string的行爲,儘管它的名字叫traits而不叫policy。

注:因爲C++缺少模板化的typedef,類型生成器可以作爲其替代方案。


-------------------------------------------------------------------------
-------

Revised 14 Mar 2001

Copyright David Abrahams 2001. Permission to copy, use, modify, sell and 
distribute this document is granted provided this copyright notice appears
 in all copies. This document is provided "as is" without express or implied
 warranty, and with no claim as to its suitability for any purpose.

翻譯於2003年5月9日

版權聲明:和David Abrahams的一樣:-)
發佈了518 篇原創文章 · 獲贊 98 · 訪問量 182萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章