Guru of the Week 條款16:具有最大可複用性的通用Containers (轉)

worldblog發表於2007-12-09
Guru of the Week 條款16:具有最大可複用性的通用Containers (轉)[@more@]

GotW #16 Maximally Reusable Generic Containers:namespace prefix = o ns = "urn:schemas--com::office" />

著者:Herb Sutter 

翻譯:kingofark

[宣告]:本文內容取自網站上的Guru of the Week欄目,其著作權歸原著者本人所有。譯者kingofark在未經原著者本人同意的情況下翻譯本文。本翻譯內容僅供自學和參考用,請所有閱讀過本文的人不要擅自轉載、傳播本翻譯內容;本翻譯內容的人請在閱讀瀏覽後,立即刪除其。譯者kingofark對違反上述兩條原則的人不負任何責任。特此宣告。 

Revision 1.0 

Guru of the Week 條款16:具有最大可複用性的通用Containers 

難度:8 / 10 

(你能讓這個簡單的container class具有多大的可適應性(flexibility)?預告:在本條款中,你將要學到的關於成員模板方面的知識絕不只是一點點而已。) 

[問題] 

為下面的定長(fixed-length)vector class實現複製構造(copy construction)操作和複製賦值(copy assignment)操作,以提供最大的可用性(usability)。提示:請考慮程式碼(client code)可能會用它做那些事情。

  template


  class fixed_vector {


  public:


  typedef T*  iterator;


  typedef const T* const_iterator; 


  iterator  begin()  { return v_; }


  iterator   end()  { return v_+size; }


  const_iterator begin() const { return v_; }


  const_iterator end()  const { return v_+size; } 


  private:


  T v_[size];


  };


[注意]

(1)  不要修改程式碼的其它部分。給出這個container的目的並不是要將其納入STL——它至少有一個嚴重的問題;給出它的目的在於,我們能在一個簡化了的情形下說明一些重要的問題。

(2)  這裡的程式碼是根據最初由Kevlin Henney給出的例子改編而來的。Jon Jagger曾在British C++ user magazine Overload欄目的第12和20期上面分析過那個最初的例子。(英國的讀者注意了:在本期GotW中所給出的解答與British C++ user magazine Overload第20期中的解答遠不相同。事實上,在那一期中所提出的處理方案在我將給出的GotW解答中並不湊效。) 

[解答] 

[注意:這裡的GotW原始解答存在一些,但已經在Exceptional C++和勘誤表中解決了]

在本期GotW中,我們換一種方法來進行:由我來給出用於解答的程式碼,而由你來對程式碼進行解說。

提問:下面的解決方案具體是怎樣運作的?為什麼會這樣運作?請對每一個構造(constructor)和運算子(operator)都作說明。

  template


  class fixed_vector {


  public:


  typedef T*  iterator;


  typedef const T* const_iterator;


  fixed_vector() { }


  template


  fixed_vector( const fixed_vector& other ) {


  copy( other.begin(),


  other.begin()+min(size,osize),


  begin() );


  }


  template


  fixed_vector&


  operator=( const fixed_vector& other ) {


  copy( other.begin(),


  other.begin()+min(size,osize),


  begin() );


  return *this;


  }


  iterator  begin()   { return v_; }


  iterator  end()  { return v_+size; }


  const_iterator begin() const { return v_; }


  const_iterator end()  const { return v_+size; }


  private:


  T v_[size];


  };


現在,我們就來分析這段程式碼,看看它到底有沒有圓滿的回答本期的GotW問題。

[複製構造(Copy Contruction)操作和複製賦值(Copy Assignment)操作]

首先應該注意的是,本期條款的問題本身就有使用遮眼法(a red herring)的嫌疑——原始問題中給出的程式碼本身已經具有工作良好的複製建構函式(copy constructor)和複製賦值運算子(copy assignment operator)。而我們的解決方案意欲增加一個模板化的建構函式(templated constructor)和一個模板化的賦值運算子(templated assignment operator),以使得構造操作和賦值操作更具可適應性。

我要祝賀Valentin Bonnard以及其他一些人,是他們很快就指出,預想中的複製建構函式(copy constructor)其實壓根兒就不是一個複製建構函式!事實上,連預想中的那個複製賦值運算子(copy assignment operator)其實也壓根兒就不是一個複製賦值運算子!

其原因是:真正的複製建構函式或者複製賦值運算子只對完全相同型別的施以構造或賦值操作,並且,其如果是一個模板類的話,模板的引數也都必須完全相同。例如:

  struct X {


   template


  X( const T& );  // 這不是複製建構函式,因為T不會是X 


  template


  operator=( const T& );


  //這不是複製賦值運算子,因為T不會是X


  };


“但是,”你說,“這兩個被模板化了的成員函式的確具有複製構造(Copy Contruction)操作和複製賦值(Copy Assignment)操作的準確形式呀!”這個嘛……告訴你吧:其實不然——根本不是這回事,因為在那兩種情況下,T都不一定是X。下面是摘自CD2[注:同樣的敘述也出現在1998年的官方標準中;“CD2”即“Committee Draft 2(委員會第2號草案)”]的敘述:

[12.8/2 note 4]

Because a template constructor is never a copy constructor, the presence of such a template does not suppress the implicit declaration of a copy constructor.

由於模板建構函式終究不是複製建構函式,因此這種模板的出現並不會隱藏原來隱含的複製建構函式之宣告。

在[12.8/9 note 7]中也有關於複製賦值操作的類似敘述。如此一來,我們在解答中給出的程式碼實際上與原來問題中的原始程式碼有著相同的複製建構函式和複製賦值運算子——因為始終生成它們隱含的版本。我們所做的改動只是增強了構造操作和賦值操作的可適應性,而不是替換掉了舊有的版本。舉個例子來說:

  fixed_vector v;


  fixed_vector  w;


  fixed_vector  w2(w);


  // 隱含的複製建構函式


  fixed_vector  w3(v);


  // 呼叫模板化了的轉換建構函式


  w = w2; // 呼叫隱含的賦值運算子


  w = v;  // 呼叫模板化了的賦值運算子


由此可以看出,本條款的問題所尋求的真正答案其實是提供了具有可適應性的“從其它fixed_vectors進行構造和複製的操作”,而不是具有可適應性的“複製構造操作和複製賦值操作”——它們早就存在了。

[構造操作和賦值操作的可用性]

我們增加的兩個操作具有如下兩個主要用途:

1.  支援可變的型別(包括繼承在內)

儘管fixed_vector原則上應該保持在相同型別的container之間進行複製和賦值操作,但有時候從另一個包含不同型別的物件之fixed_vector進行構造和賦值操作,也是不無意義的。只要源物件可以被賦值給目的物件,就應該允許這種不同型別物件之間的賦值。例如,使用者程式碼可能會這樣使用fixed_vector:

  fixed_vector v;


  fixed_vector  w(v);  // 複製


  w = v;  // 賦值


  class B { /*...*/ };


  class D : public B { /*...*/ };


  fixed_vector x;


  fixed_vector y(x);  // 複製


  y = x;  // 賦值


2.  支援可變的大小

與第1點類似,使用者程式碼有時也可能希望從具有不同大小的fixed_vector進行構造或賦值。支援這個操作同樣也是有意義的。例如:

  fixed_vector v;


  fixed_vector  w(v);  // 複製4個物件


  w = v;  // 對4個物件進行賦值


   class B { /*...*/ };


  class D : public B { /*...*/ };


  fixed_vector x;


  fixed_vector y(x);  // 複製16個物件


  y = x;  // 對16個物件進行賦值


 

[另一種解決方案:標準庫風格的解答]

我很喜愛以上函式的形式以及其甚佳的可用性,但它們還是有些做不到的事情。接下來,我們考察一種具有標準庫風格的解答:

1.  複製(Copying)

  template


  fixed_vector( Iter first, Iter last ) {


  copy( first,


  first+min(size,last-first),


  begin() );


  }


於是,當要進行複製操作時,我們不用:

  fixed_vector v;


  fixed_vector  w(v);  // 複製4個物件


而要用:

  fixed_vector v;


  fixed_vector  w(v.begin(), v.end());


  // 複製4個物件


對於一個構造操作而言,哪一種方案更好呢:是先前的預想方案,還是這個標準庫風格的方案?前一個相對更容易使用一些,而後一個的可適應性更好(比如它允許使用者選擇操作範圍並可以其它種類的container進行複製)——你可以選擇任一種或者乾脆兩者兼而提供之。

2.  賦值(Assignment)

這裡應注意的是,由於operator=()只能接收一個引數,因此我們無法讓賦值操作把iterator的範圍作為另一個引數。一個可行的方法是,提供一個具名函式(named function):

  template


  fixed_vector&


  assign( Iter first, Iter last ) {


  copy( first,


  first+min(size,last-first),


  begin() );


  return *this;


  }


於是,當要進行賦值操作時,我們不用:

  w = v;  // 對4個物件進行賦值


而要用:

  w.assign(v.begin(), v.end());


  // 對4個物件進行賦值


從技術上而言,assign()其實並不是必需的,沒有它我們也可以達到相同程度的可適應性,但這樣一來,進行賦值操作時就不太有效率,顯得比較不爽:

  w = fixed_vector(v.begin(), v.end());


  // 對4個物件進行賦值


對於一個賦值操作而言,哪一種方案更好呢:是先前的預想方案,還是這個標準庫風格的方案?這一回我們就不能再像第1點中那樣用其可適應性來衡量孰好孰壞了,因為使用者可以很容易的編寫複製操作的程式碼(這樣甚至還更具可適應性)。使用者將不會用:

  w.assign(v.begin(), v.end());


而會直接用:

  copy( v.begin(), v.end(), w.begin() );


在這種情況下,就沒有必要編寫assign()了。因此使用先前預想的方案或許更好一些,這樣可以讓使用者程式碼在需要對某個範圍內的物件進行賦值操作的時候直接使用copy()。

[為什麼給出了預設建構函式(default constructor)?]

最後的問題:既然第一種預想方案中的空的預設建構函式之功能與編譯器自己生成的預設建構函式之功能相同,那為什麼還要特意在解答程式碼中給出它來呢?這是因為,一旦你定義了一個任意形式的建構函式,編譯器就不會為你生成其預設版本了。顯然,像上述那樣的使用者程式碼就需要這樣做。

 

[小結:成員函式模板(Member Function Templates)到底怎麼樣?]

希望本期GotW條款已經使你確信,成員函式模板非常易用。另外,我還希望本期條款能夠使你明白為什麼成員函式模板會被廣泛用於標準庫中。如果你還對此用法不太熟悉,千萬不要傷心欲絕——並不是所有現存的編譯器都支援成員模板特性,只不過這是C++標準的規定,所有的編譯器很快都將支援它。(在撰寫本文時,Microsoft Visual C++ 5.0已經可以編譯透過使用此特性的程式碼了,但在某些使用者程式碼例程中,其編譯器還是不能對osize引數進行推理分析。)

在你建立自己的classes時使用成員模板,不僅可以取悅使用者,而且使用者還會越來越多,並爭先恐後的使用那些極易複用的程式碼。 

(完)


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-990179/,如需轉載,請註明出處,否則將追究法律責任。

相關文章