1. 程式人生 > 實用技巧 >七.模板與泛型程式設計

七.模板與泛型程式設計

本章旨在介紹一直支撐著所有基於template的程式設計

條款41:瞭解隱式介面和編譯器多型

面向物件程式設計世界總是以顯示介面和執行期多型解決問題。以下面這個class為例:

class Widget {
public:
      Widget();
      virtual ~Widget();
      virtual std::size_t size() const;
      virtual void normalize();
      void swap(Widget& other);      
      ...
};
void doProcessing(Widget& w)
{
      if(w.size() > 10 && w != someNastyWidget) {
            Widget temp(w);
            temp.normalize();
            temp.swap(w);
      }
}

我們可以這樣分析:
- 由於w的型別被宣告為Widget,所以w必須支援Widget介面。我們可以在原始碼中找出這個介面,這也是顯示介面。
- 由於Widget的某些成員函式是virtual,w對那些函式的呼叫將表現出執行期多型。
Template及泛型程式設計的世界,與面向物件有根本的不同。在此世界中顯示介面和執行期多型仍然存在,但重要性降低。反倒是隱式介面和編譯期多型移到前頭,我們再探究將函式從函式模板時發生什麼:

template<typename T>
void doProcessing(T& w)
{
      if(w.size() > 10 && w != someNastyWidget) {
            T temp(w);
            temp.normalize();
            temp.swap(w);
      }
}
現在我們怎麼說doProcessing內的w?
**-** w必須支援哪一種介面, 系由template中執行於w身上的操作來決定。本例看來w的型別T好像必須支援size,normalize和swap成員函式、copy建構函式(用以建立temp)、不等比較。重要的是,這一組表示式便是T必須支援的一組隱式介面。
**-** 凡涉及w的任何函式呼叫,例如operator>和operator!=,有可能造成template具現化,使這些呼叫得以成功,這樣的具現化行為發生在編譯器。“以不同的template引數具現化”會導致呼叫不同的函式,這就是所謂的編譯器多型。你應該分清“執行期多型”和“編譯器多型”之間的差異,因為它類似於“哪一個過載函式被呼叫”和“哪一個virtual函式該被繫結”之間的差異。
顯示介面和隱式介面的差異,需要更多貼近的解釋和說明。
##1)
通常顯示介面由函式的簽名式(函式名稱,引數型別,返回型別)構成。例如:

class Widget {
public:
Widget();
virtual ~Widget();
virtual std::size_t size() const;
virtual void normalize();
void swap(Widget& other);
};

其介面由一個建構函式,解構函式、函式size,normalize,swap及其引數型別、返回型別、常量性構成。當然也包括編譯器產生的copy建構函式和copy操作符。另外也可以包括typedefs.隱式介面就不同了,它並不基於函式簽名式,而是由有效表示式組成,再次看看doProcessing template一開始的條件:

template
void doProcessing(T& w)
{
if(w.size() > 10 && w != someNastyWidget) {
...
}
}

T的隱式介面看起來好像有些約束
**-**  它必須提供一個名為size的成員函式,該函式返回一個整數值。
**-**  它必須支援一個operator!=函式用來比較兩個T物件。這裡我們假設someNastyWidget的型別為T。
但感謝操作符過載帶來的可能性,這兩個約束都不需要滿足。是的,T必須支援size成員函式,然而這個函式也可能從base class繼承而得。這個成員函式不需返回一個整數值,甚至不需返回一個數值型別。它唯一需要的是返回一個型別為x的物件,而x物件加上一個int必須能夠呼叫一個operator>,這個operator>不需要非得取得一個型別為x的引數不可,因為它也可以取得型別Y的引數,只要存在一個隱式轉換能夠將型別x的物件轉換為型別y的物件!
隱式介面僅是有一組有效表示式構成,表示式自身可能看起來很複雜,但他們要求的約束條件一般而言相當直接又明確,例如以下條件:

if(w.size() > 10 && w != someNastyWidget) ...

關於函式size以及一系列的operator身上的約束條件,我們很難說太多,但確認整體表達式約束條件卻很容易,if語句的條件式必須是個布林表示式,所以無論涉及什麼實際型別,都必須與bool相容,這是T隱式介面的一部分,它要求的其他隱式介面:copy建構函式等都必須對T型物件有效。
加諸於template引數身上的隱式介面,就像加諸於class物件身上的顯示介面一樣真是,而且都在編譯期完成檢查。
**請記住**
>1.classes和templates都支援介面和多型
>2.對classes而言介面是顯示的,以函式簽名為中心。多型則是通過virtual函式發生於執行期。
>3.對template引數而言,介面是隱式的,奠基於有效表示式。多型則是通過template具現化和函式過載解析發生於編譯器行為。
#條款42.瞭解typename的雙重意義