(一)
首先看下面的類以及函數:
class Widget { public: Widget(); virtual ~Widget(); virtual size_t size() const; virtual void normalize(); virtual 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.h中),看看它是什麼樣子,所以我們稱此為一個顯式接口,也就是它在源碼中明確可見。
Widget某些成員函數是virtual,w對那些函數的調用表現出運行期多態,運行期根據w的動態類型決定究竟調用哪個函數。
(二)template及泛型編程的世界,與面向對象有著根本的不同。隱式接口和編譯期多態移到了前頭:
templatevoid doProcessing(T& w) { if(w.size() > 10 && w != someNastyWidget) { T temp(w); temp.normalize(); temp.swap(w); } }
w必須支持哪一種接口,由template中執行與w身上的操作來決定。本例w的類型T必須支持size,normalize和swap成員函數,copy構造函數、不等比較!=,並非完全正確。這一組表達式便是T必須支持的一組隱式接口。
凡涉及w的任何調用,例如operator!=,有可能造成template的具現化,是這些調用得以成功。這樣的具現化發生在編譯期。“以不同的template參數具現化function template”會導致調用不同的參數,這便是編譯期多態(compile-time polymorphism)。類似於“哪個重載函數被調用”(發生在編譯期)和“哪一個virtual函數被調用”(發生在運行期)之間的差異。
顯式接口由函數的簽名式(名稱、參數類型、返回類型)構成:例如class
Widget{
public:
Widget();
virtual ~Widget();
virtual std::size_t
size() const;
virtual void
normalize();
virtual swap(Widget&
other);
};
除了上面的還有編譯期產生的copy構造函數和copy assignment操作符。另外也可以包括typedefs等。
隱式接口不基於函數簽名式,而是由有效表達式(valid expressions)組成。
templatevoid doProcessing(T& w) { if (w.size() > 10 && w != someNastyWidget){ T temp(w); temp.normalize(); temp.swap(w); } }
T(w的類型)的隱式接口好像有這些約束:
必須提供一個size成員函數;
必須支持operator!=;
真要感謝操作符重載帶來的可能性,這兩個約束都不需要滿足。
原因:T必須支持size成員函數,然而這個函數可以從base class繼承而得。這個函數不需要返回一個整數值,他唯一要做的是返回一個類型為X的對象,而X對象加上一個int(10)必須能夠調用一個operator>。這個operator>可以取得類型為Y的參數,只要存在一個隱式轉換能將類型X的對象轉換為類型Y的對象。
同樣道理T並不需要支持operator!=,operator!=只要階接受一個類型為X和Y的對象,T可以轉換為X而someNastyWidget可以轉換成Y。
加諸於template參數身上的隱式接口,就像加諸於class對象身上的顯式接口一樣真實,都在編譯期完成檢查。
請記住:
(1)class和template都支持接口和多態。
(2)對classes而言,接口是顯式的(explicit),以函數簽名為中心。多態則是通過virtual函數發生在運行期。
(3)對template參數而言,接口是隱式的(implicit),奠基於有效表達式。多態則是通過template具現化和函數重載解析(function overloading resolution)發生在編譯期。