C++ template —— 動多態與靜多態(六)


前面的幾篇博文介紹了模板的基礎知識,並且也深入的講解了模板的特性。接下來的博文中,將會針對模板與設計進行相關的介紹。
------------------------------------------------------------------------------------------------------------
與傳統的語言構造相比,模板的不同之處在於:它允許我們在代碼中對類型和函數進行參數化。把(1)局部特化和(2)遞歸實例化組合起來,將會產生強大威力。接下來的幾篇博文,我們通過下面的一些設計技術來展示這些強大威力:
(1)泛型編程
(2)trait
(3)policy class
(4)metaprogramming
(5)表達式模板

------------------------------------------------------------------------------------------------------------
第14章 模板的多態威力
多態是一種能夠令單一的泛型標記關聯不同特定行為的能力。對面向對象的程序設計范例而言,多態可以說是一塊基石。在C++中,這塊基石主要是通過繼承和虛函數來實現的。由於這兩個機制(繼承和虛函數)都是(至少一部分)在運行期進行處理的,因此我們把這種多態稱為動多態;我們平常所談論的C++多態指的就是這種動多態。然而,模板也允許我們使用單一的泛型標記,來關聯不同的特定行為;但這種(借助於模板的)關聯是在編譯期進行處理的,因此我們把這種(借助於模板的)多態稱為靜多態,從而和上面的動多態區分開來。
------------------------------------------------------------------------------------------------------------
14.1 動多態

使用繼承和虛函數,在這種情況下,多態的設計思想主要在於:對於幾個相關對象的類型,確定它們之間的一個共同功能集;然后在基類中,把這些共同的功能聲明為多個虛函數接口。每個具體類都派生自基類,生成了具體對象之后,客戶端代碼就可以通過指向基類的引用或指針來操作這些對象,並且能夠通過這些引用或者指針來實現虛函數的調度機制。也就是說,利用一個指向基類(子對象)的指針或者引用來調用虛成員函數,實際上將可以調用(指針或者引用實際上所代表的)具體類對象的相應成員。這種動多態是C++程序設計里面最常見的,這里不過多的闡述。

14.2 靜多態 模板也能夠被用於實現多態。如下例子:

// poly/statichier.hpp
#include "coord.hpp"

// 具體的幾何對象類Circle
// - 並沒有派生自任何其他的類
class Circle
{
    public:
        void draw() const;
        Coord center_of_gravity() const;
        ...
};

// 具體的幾何對象類Line
// - 並沒有派生自任何其他的類
class Line
{
    public:
        void draw() const;
        Coord center_of_gravity() const;
        ...
};
....

現在,使用這些類的應用程序看起來如下所示:

// poly/staticpoly.cpp
#include "statichier.hpp"
#include <vector>

// 畫出任意GeoObj
// method2
template <typename GeoObj>
void myDraw(GeoObj const& obj)     // GeoObj是模板參數
{
    obj.draw();      // 根據對象的類型調用相應的draw()
}
......

int main()
{
    Line l;
    Circle c;

    myDraw(l);         // myDraw<Line>(GeoObj&) => Line::draw()
    myDraw(c);         // myDraw<Circle>(GeoObj&) => Circle::draw()
}

// method1:如果使用動多態,myDraw函數會是如下形式:
void myDraw(GeoObj const& obj)      // GeoObj是一個抽象基類
{
    obj.draw();
}

通過比較myDraw()的這兩個實現,我們可以看出:主要的區別在於method2的GeoObj的規范是模板參數,而不是一個公共基類。然而,在這個現象的背后,還存在更多本質的差別。例如,使用動多態(method1),我們在運行期只具有一個myDraw()函數,而如果使用模板,我們則可能具有多個不同的函數,諸如myDraw<Line>()和myDraw<Circle>()。

14.3 動多態和靜多態

我們來對多態進行分類,並對這兩種多態進行比較。

14.3.1 術語

動多態和靜多態為不同的C++編程idioms提供了支持:
(1)通過繼承實現的多態是綁定的和動態的:
1. 綁定的含義是:對於參與多態行為的類型,它們(具有多態行為)的接口是在公共基類的設計中就預先確定的(有時候也把綁定這個概念稱為入侵的或者插入的)。
2. 多態的含義是:接口的綁定是在運行期(動態)完成的。
(2)通過模板實現的多態是非綁定的和靜態的:
1. 非綁定的含義是:對於參與多態行為的類型,它們的接口是沒有預先確定的(有時也稱這個概念為非入侵的或者非插入的)。
2. 靜態的含義是:接口的綁定是在編譯期(靜態)完成的。

14.3.2 優點和缺點
(1)C++的動多態具有下列優點:
1. 能夠優雅地處理異類集合;
2. 可執行代碼的大小通常比較小(因為只需要一個多態函數,但對於靜多態而言,為了處理不同的類型,必須生成多個不同的模板實例);
3. 可以對代碼進行完全編譯;因此並不需要發布實現源碼(但是,分布模板庫通常都需要同時發布模板實現的源代碼);
(2)另一方面,C++靜多態則具有下列優點:

1. 可以很容易地實現內建類型的集合。更廣義地說,並不需要通過公共基類來表達接口的共同性;

2. 所生成的代碼效率通常都比較高(因為並不存在通過指針的間接調用,而且,可以進行演繹的非虛擬函數具有更多的內聯機會);

3. 對於只提供部分接口的具體類型,如果在應用程序中只是使用到這一部分接口,那么也可以使用該具體類型,而不必在乎該類型是否提供其他部分的接口。

通常而言,與動多態相比,靜多態被認為具有更好的類型安全性:因為靜多態在編譯期會對所有的綁定操作進行檢查。例如,假設我們嘗試把一個錯誤類型的對象插入到一個容器中,如果這個容器是根據模板實例化而生產的話,那么幾乎不會有危險,因為在編譯期就可以檢查出這個錯誤;但如果該容器所期望的元素是指向公共基類的指針,那么這些指針最后很有可能會指向不同類型的完整對象,而這就有可能會插入錯誤類型的對象。

在實際應用中,對於看起來相同的接口,如果在它們背后隱藏着一些語義假設的話,那么模板實例化體(靜多態)有時也會導致一些問題。例如,對於一個假設具有關聯運算符 + 的模板,如果基於一個沒有關聯該運算符的類型來實例化這個模板,那么就會出現一些問題。然而,基於繼承體系的多態則很少會出現這種語義非匹配的問題,因為公共接口規范已經在基類中(更加)顯式地指定了。

14.3.3 組合這兩種多態

顯然,你可以組合這兩種形式的多態。例如,你可以從一個公共基類派生出不同種類的幾何對象類,從而能夠處理屬於異類集合的不同幾何對象。另一方面,你仍然可以使用模板來編寫針對某種幾何對象的代碼。

在后面的博文xxxx中將進一步闡述繼承和模板的組合。在第16章中,我們將看到:如何對成員函數的虛擬性進行參數化;當使用基於繼承的奇異遞歸模板模式的時候,靜多態要犧牲哪些額外的靈活性。

14.4 新形式的設計模板

這種新形式的靜多態帶來了實現設計模式的新方法。例如,以在C++程序設計中扮演重要角色的橋模式為例。我們使用橋模式的目的是為了能夠在同一接口的多個不同實例中進行切換。我們通常可以使用一個指針來引用具體的實現,然后把所有的調用都委托給這個(包含具體實現的)類,從而達到我們的目的(見圖14.3)

 

然而,如果實現的類型在編譯期就已經是確定的,那么我們就可以借助於模板的方法來實現橋模式(見圖14.4)。這將可以帶來更好的類型安全性,並且也能避免使用指針,而且還能帶來更高的效率。

 

14.5 泛型程序設計
到目前為止,在C++泛型程序設計領域中,最顯著的貢獻就是STL(Standard Template Library),它后來被采納並引入到C++標准庫中。STL借助於迭代器對操作進行了參數化,從而避免了操作定義在數量上的過度膨脹。在此,你並不需要為每個容器都實現每一個操作,只需要實現某個算法一次,就可以把該算法應用到每個容器中。換句話說,泛型程序設計的“粘合劑”就是:由容器提供的並且能被算法所使用的迭代器。迭代器之所以能夠肩負這樣的任務,是由於容器為迭代器提供了一些特定的接口,而算法所使用的正是這些接口。我們通常也把每個這樣的接口稱為一個concept(即約束),它說明一個模板(即容器)如果要並入這個框架(即STL),就必須履行或者實現這些約束(也即,符合STL框架標准)。
從原則上講,也可以使用動多態來實現這些類似於STL的功能。然而,用動多態實現的功能使用起來肯定會很受限制,因為與迭代器的概念相比,動多態的虛函數調用機制將會是一種重量級的實現機制,這就會對效率產生很大的影響。譬如增加一層基於虛函數的接口層,通常就會影響操作的效率,而且這種影響的程度可能是幾個數量級的(甚至更加嚴重)。
事實上,泛型程序設計是相當實用的,因為它所依賴的是靜多態,而靜多態會要求在編譯期對接口進行解析。另一方面,這種要求(即對接口在編譯期進行解析)還會帶來一些與面向對象程序設計原則截然不同的新設計原則。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM