程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> C++箴言:用成員函數模板接受兼容類型

C++箴言:用成員函數模板接受兼容類型

編輯:關於C++

smart pointers(智能指針)是行為很像指針但是增加了指針沒有提供的功能的 objects。例如,《C++箴言:使用對象管理資源》闡述了標准 auto_ptr 和 tr1::shared_ptr 是怎樣被應用於在恰當的時間自動刪除的 heap-based resources(基於堆的資源)的。STL containers 內的 iterators(迭代器)幾乎始終是 smart pointers(智能指針);你絕對不能指望用 "++" 將一個 built-in pointer(內建指針)從一個 linked list(線性鏈表)的一個節點移動到下一個,但是 list::iterators 可以做到。

real pointers(真正的指針)做得很好的一件事是支持 implicit conversions(隱式轉換)。derived class pointers(派生類指針)隱式轉換到 base class pointers(基類指針),pointers to non-const objects(指向非常量對象的指針)轉換到 pointers to const objects(指向常量對象的指針),等等。例如,考慮在一個 three-level hierarchy(三層繼承體系)中能發生的一些轉換:

class Top { ... };
class Middle: public Top { ... };
class Bottom: public Middle { ... };
Top *pt1 = new Middle; // convert Middle* => Top*
Top *pt2 = new Bottom; // convert Bottom* => Top*
const Top *pct2 = pt1; // convert Top* => const Top*

在 user-defined smart pointer classes(用戶定義智能指針類)中模仿這些轉換是需要技巧的。我們要讓下面的代碼能夠編譯:

template<typename T>
class SmartPtr {
 public: // smart pointers are typically
  explicit SmartPtr(T *realPtr); // initialized by built-in pointers
 ...
};
SmartPtr<Top> pt1 = // convert SmartPtr<Middle> =>
SmartPtr<Middle>(new Middle); // SmartPtr<Top>
SmartPtr<Top> pt2 = // convert SmartPtr<Bottom> =>
SmartPtr<Bottom>(new Bottom); // SmartPtr<Top>
SmartPtr<const Top> pct2 = pt1; // convert SmartPtr<Top> =>
// SmartPtr<const Top>

在同一個 template(模板)的不同 instantiations(實例化)之間沒有 inherent relationship(繼承關系),所以編譯器認為 SmartPtr<Middle> 和 SmartPtr<Top> 是完全不同的 classes,並不比(比方說)vector<float> 和 Widget 的關系更近。為了得到我們想要的在 SmartPtr classes 之間的轉換,我們必須顯式地為它們編程。

在上面的 smart pointer(智能指針)的示例代碼中,每一個語句創建一個新的 smart pointer object(智能指針對象),所以現在我們就集中於我們如何寫 smart pointer constructors(智能指針的構造函數),讓它以我們想要的方式運轉。一個關鍵的事實是我們無法寫出我們需要的全部 constructors(構造函數)。在上面的 hierarchy(繼承體系)中,我們能從一個 SmartPtr<Middle> 或一個 SmartPtr<Bottom> 構造出一個 SmartPtr<Top>,但是如果將來這個 hierarchy(繼承體系)被擴充,SmartPtr<Top> objects 還必須能從其它 smart pointer types(智能指針類型)構造出來。例如,如果我們後來加入

class BelowBottom: public Bottom { ... };

我們就需要支持從 SmartPtr<BelowBottom> objects 到 SmartPtr<Top> objects 的創建,而且我們當然不希望為了做到這一點而必須改變 SmartPtr template。

大體上,我們需要的 constructors(構造函數)的數量是無限的。因為一個 template(模板)能被實例化而產生無數個函數,所以好像我們不需要為 SmartPtr 提供一個 constructor function(構造函數函數),我們需要一個 constructor template(構造函數模板)。這樣的 templates(模板)是 member function templates(成員函數模板)(常常被恰如其分地稱為 member templates(成員模板))——生成一個 class 的 member functions(成員函數)的 templates(模板)的范例:

template<typename T>
class SmartPtr {
 public:
  template<typename U> // member template
  SmartPtr(const SmartPtr<U>& other); // for a "generalized
  ... // copy constructor"
};

這就是說對於每一種類型 T 和每一種類型 U,都能從一個 SmartPtr<U> 創建出一個 SmartPtr<T>,因為 SmartPtr<T> 有一個取得一個 SmartPtr<U> 參數的 constructor(構造函數)。像這樣的 constructor(構造函數)——從一個類型是同一個 template(模板)的不同實例化的 object 創建另一個 object 的 constructor(構造函數)(例如,從一個 SmartPtr<U> 創建一個 SmartPtr<T>)——有時被稱為 generalized copy constructors(泛型化拷貝構造函數)。

上面的 generalized copy constructor(泛型化拷貝構造函數)沒有被聲明為 explicit(顯式)的。這是故意為之的。built-in pointer types(內建指針類型)之間的類型轉換(例如,從派生類指針到基類指針)是隱式的和不需要 cast(強制轉型)的,所以讓 smart pointers(智能指針)模仿這一行為是合理的。在 templatized constructor(模板化構造函數)中省略 explicit 正好做到這一點。

作為聲明,SmartPtr 的 generalized copy constructor(泛型化拷貝構造函數)提供的東西比我們想要的還多。是的,我們需要能夠從一個 SmartPtr<Bottom> 創建一個 SmartPtr<Top>,但是我們不需要能夠從一個 SmartPtr<Top> 創建一個 SmartPtr<Bottom>,這就像顛倒 public inheritance(公有繼承)的含義(參見《C++箴言:確保公開繼承模擬“is-a”》)。我們也不需要能夠從一個 SmartPtr<double> 創建一個 SmartPtr<int>,因為這和從 int* 到 double* 的 implicit conversion(隱式轉換)是不相稱的。我們必須設法過濾從這個 member template(成員模板)生成的 member functions(成員函數)的群體。

假如 SmartPtr 跟隨 auto_ptr 和 tr1::shared_ptr 的腳步,提供一個返回被這個 smart pointer(智能指針)持有的 built-in pointer(內建指針)的拷貝的 get member function(get 成員函數)(參見《C++箴言:在資源管理類中准備訪問裸資源》),我們可以用 constructor template(構造函數模板)的實現將轉換限定在我們想要的范圍:

template<typename T>
class SmartPtr {
 public:
  template<typename U>
  SmartPtr(const SmartPtr<U>& other) // initialize this held ptr
  : heldPtr(other.get()) { ... } // with other's held ptr
  T* get() const { return heldPtr; }
  ...
 private: // built-in pointer held
  T *heldPtr; // by the SmartPtr
};

我們通過 member initialization list(成員初始化列表),用 SmartPtr<U> 持有的類型為 U* 的指針初始化 SmartPtr<T> 的類型為 T* 的 data member(數據成員)。這只有在“存在一個從一個 U* 指針到一個 T* 指針的 implicit conversion(隱式轉換)”的條件下才能編譯,而這正是我們想要的。最終的效果就是 SmartPtr<T> 現在有一個 generalized copy constructor(泛型化拷貝構造函數),它只有在傳入一個 compatible type(兼容類型)的參數時才能編譯。

member function templates(成員函數模板)的用途並不限於 constructors(構造函數)。它們的另一個常見的任務是用於支持 assignment(賦值)。例如,TR1 的 shared_ptr(再次參見《C++箴言:使用對象管理資源》)支持從所有兼容的 built-in pointers(內建指針),tr1::shared_ptrs,auto_ptrs 和 tr1::weak_ptrs構造,以及從除 tr1::weak_ptrs 以外所有這些賦值。這裡是從 TR1 規范中摘錄出來的一段關於 tr1::shared_ptr 的內容,包括它在聲明 template parameters(模板參數)時使用 class 而不是 typename 的偏好。(就像《C++箴言:理解typename的兩個含義》中闡述的,在這裡的上下文環境中,它們的含義嚴格一致。)

template<class T> class shared_ptr {
public:
 template<class Y> // construct from
 explicit shared_ptr(Y * p); // any compatible
 template<class Y> // built-in pointer,
 shared_ptr(shared_ptr<Y> const& r); // shared_ptr,
 template<class Y> // weak_ptr, or
 explicit shared_ptr(weak_ptr<Y> const& r); // auto_ptr
 template<class Y>
 explicit shared_ptr(auto_ptr<Y>& r);
 template<class Y> // assign from
 shared_ptr& operator=(shared_ptr<Y> const& r); // any compatible
 template<class Y> // shared_ptr or
 shared_ptr& operator=(auto_ptr<Y>& r); // auto_ptr
 ...
};

除了 generalized copy constructor(泛型化拷貝構造函數),所有這些 constructors(構造函數)都是 explicit(顯式)的。這就意味著從 shared_ptr 的一種類型到另一種的 implicit conversion(隱式轉換)是被允許的,但是從一個 built-in pointer(內建指針)或其 smart pointer type(智能指針類型)的 implicit conversion(隱式轉換)是不被許可的。(explicit conversion(顯式轉換)——例如,經由一個 cast(強制轉型)——還是可以的。)同樣引起注意的是 auto_ptrs 被傳送給 tr1::shared_ptr 的 constructors(構造函數)和 assignment operators(賦值操作符)的方式沒有被聲明為 const,於此對照的是 tr1::shared_ptrs 和 tr1::weak_ptrs 的被傳送的方式。這是 auto_ptrs 被復制時需要獨一無二的被改變的事實的一個必然結果(參見《C++箴言:使用對象管理資源》)。

member function templates(成員函數模板)是一個極好的東西,但是它們沒有改變這個語言的基本規則。《C++箴言:了解C++偷偷加上和調用了什麼》闡述的編譯器可以產生的四個 member functions(成員函數)其中兩個是 copy constructor(拷貝構造函數)和 copy assignment operator(拷貝賦值運算符)。tr1::shared_ptr 聲明了一個 generalized copy constructor(泛型化拷貝構造函數),而且很明顯,當類型 T 和 Y 相同時,generalized copy constructor(泛型化拷貝構造函數)就能被實例化而成為 "normal" copy constructor(“常規”拷貝構造函數)。那麼,當一個 tr1::shared_ptr object 從另一個相同類型的 tr1::shared_ptr object 構造時,編譯器是為 tr1::shared_ptr 生成一個 copy constructor(拷貝構造函數),還是實例化 generalized copy constructor template(泛型化拷貝構造函數模板)?

就像我說過的,member templates(成員模板)不改變語言規則,而且規則規定如果一個 copy constructor(拷貝構造函數)是必需的而你沒有聲明,將為你自動生成一個。在一個 class 中聲明一個 generalized copy constructor(泛型化拷貝構造函數)(一個 member template(成員模板))不會阻止編譯器生成它們自己的 copy constructor(拷貝構造函數)(非模板的),所以如果你要全面支配 copy construction(拷貝構造),你必須既聲明一個 generalized copy constructor(泛型化拷貝構造函數)又聲明一個 "normal" copy constructor(“常規”拷貝構造函數)。這同樣適用於 assignment(賦值)。這是從 tr1::shared_ptr 的定義中摘錄的一段,可以作為例子:

template<class T> class shared_ptr {
 public:
  shared_ptr(shared_ptr const& r); // copy constructor
  template<class Y> // generalized
  shared_ptr(shared_ptr<Y> const& r); // copy constructor
  shared_ptr& operator=(shared_ptr const& r); // copy assignment
 
  template<class Y> // generalized
  shared_ptr& operator=(shared_ptr<Y> const& r); // copy assignment
  ...
};

Things to Remember

·使用 member function templates(成員函數模板)生成接受所有兼容類型的函數。

·如果你為 generalized copy construction(泛型化拷貝構造)或 generalized assignment(泛型化賦值)聲明了 member templates(成員模板),你依然需要聲明 normal copy constructor(常規拷貝構造函數)和 copy assignment operator(拷貝賦值運算符)。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved