1. 程式人生 > >Item 41:隱式介面與編譯期多型

Item 41:隱式介面與編譯期多型

Item 41: Understand implicit interfaces and compile-time polymorphism.

面向物件設計中的類(class)考慮的是顯式介面(explicit interface)和執行時多型, 而模板程式設計中的模板(template)考慮的是隱式介面(implicit interface)和編譯期多型

  • 對類而言,顯式介面是由函式簽名表徵的,執行時多型由虛擬函式實現;
  • 對模板而言,隱式介面是由表示式的合法性表徵的,編譯期多型由模板初始化和函式過載的解析實現。

顯式介面和執行時多型

一個類的顯式介面是由public成員函式簽名(包括函式名、引數型別、返回值型別等)、型別定義(typedef)、public資料成員構成的。

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

對於doProcesing中的w,我們可以知道:

  • w應支援Widget的介面,包括:normalize(), swap()等。這些介面在原始碼中是可以找到的,稱為顯式介面。
  • Widget有些成員函式是virtual的,會表現出執行時多型:具體的被呼叫者會根據Widget的動態型別而決定。

隱式介面和編譯期多型

在模板和類屬程式設計(generic programming)中這一點完全不同,在這裡隱式介面和編譯期多型更為重要:

template<typename T>
void doProcessing(T& w){
    if(w.size() > 10
&& w!= someOne){ T tmp(w); tmp.normalize(); tmp.swap(w); } }

現在的doProcessing是一個函式模板,其中的w有所不同:

  • w應支援的介面取決於模板中w上的操作。比如:w(型別T)必須支援size, normalize, swap方法;拷貝建構函式;不等運算子。 總之,這些表示式必須合法而且通過編譯構成了w應支援的介面。
  • 其中的operator>和operator!=要呼叫成功可能需要例項化一些模板,而使用不同的模板引數例項化模板的過程就是編譯期多型。

具體來講,T的隱式介面應滿足:

  • 必須包含一個返回值為整型的成員函式;
  • 支援一個接受T型別的operator!=函式。

但由於C++的運算子過載和隱式型別轉換特性,上述兩個條件都不需要滿足。 首先size可能是繼承來的函式而非T的成員函式,但它不需要返回一個int,甚至不需要返回一個數字型別,返回型別也不需要定義operator>。 它需要返回的型別X只需滿足:operator>可以接受X和int。但operator>的第一個引數型別可以不是X,只要X能隱式轉換為它的第一個引數型別即可。 類似地,operator!=介面也有極大的靈活性。

當你想到這些約束時可能真的會頭大,但實踐中比這些直觀的多,介面只是由合法的表示式構成的。 例如下面的表示式看起來就很直觀:

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

總之隱式介面和顯式介面一樣地真實存在,在編譯時都會進行檢查。正如你錯誤地使用顯式介面會導致編譯錯一樣, 物件不支援模板所要求的隱式介面也會導致編譯錯。