C++中的靜態多態和動態多態

今天的C++已是個多重泛型編程語言(multiparadigm programming lauguage),一個同時支持過程形式(procedural)、面向對象形式(object-oriented)、函數形式(functional)、泛型形式(generic)、元編程形式(metaprogramming)的語言。 這些能力和彈性使C++成爲一個無可匹敵的工具,但也可能引起使用者的某些迷惑,好比多態。在這幾種編程泛型中,面向對象編程、泛型編程以及很新的元編程形式都支持多態的概念,但又有所不一樣。 C++支持多種形式的多態,從表現的形式來看,有虛函數、模板、重載等,從綁定時間來看,能夠分紅靜態多態和動態多態,也稱爲編譯期多態和運行期多態。編程

本文即講述這其中的異同。注意泛型編程和元編程一般都是以模板形式實現的,所以在本文中主要介紹基於面向對象的動態多態和基於模板編程的靜態多態兩種形式。另外其實宏也能夠認爲是實現靜態多態的一種方式,實現原理就是全文替換,但C++語言自己就不喜歡宏,這裏也忽略了「宏多態」。編程語言

什麼是動態多態?

動態多態的設計思想:對於相關的對象類型,肯定它們之間的一個共同功能集,而後在基類中,把這些共同的功能聲明爲多個公共的虛函數接口。各個子類重寫這些虛函數,以完成具體的功能。客戶端的代碼(操做函數)經過指向基類的引用或指針來操做這些對象,對虛函數的調用會自動綁定到實際提供的子類對象上去。ide

從上面的定義也能夠看出,因爲有了虛函數,所以動態多態是在運行時完成的,也能夠叫作運行期多態,這造就了動態多態機制在處理異質對象集合時的強大威力(固然,也有了一點點性能損失)。函數

看代碼:工具

 1 namespace DynamicPoly
 2 {
 3     class Geometry
 4     {
 5     public:
 6         virtual void Draw()const = 0;
 7     };
 8 
 9     class Line : public Geometry
10     {
11     public:
12         virtual void Draw()const{    std::cout << "Line Draw()\n";    }
13     };
14 
15     class Circle : public Geometry
16     {
17     public:
18         virtual void Draw()const{    std::cout << "Circle Draw()\n";    }
19     };
20 
21     class Rectangle : public Geometry
22     {
23     public:
24         virtual void Draw()const{    std::cout << "Rectangle Draw()\n";    }
25     };
26 
27     void DrawGeometry(const Geometry *geo)
28     {
29         geo->Draw();
30     }
31 
32     //動態多態最吸引人之處在於處理異質對象集合的能力
33     void DrawGeometry(std::vector<DynamicPoly::Geometry*> vecGeo)
34     {
35         const size_t size = vecGeo.size();
36         for(size_t i = 0; i < size; ++i)
37             vecGeo[i]->Draw();
38     }
39 }
40 
41 void test_dynamic_polymorphism()
42 {
43     DynamicPoly::Line line;
44     DynamicPoly::Circle circle;
45     DynamicPoly::Rectangle rect;
46     DynamicPoly::DrawGeometry(&circle);
47 
48     std::vector<DynamicPoly::Geometry*> vec;
49     vec.push_back(&line);
50     vec.push_back(&circle);
51     vec.push_back(&rect);
52     DynamicPoly::DrawGeometry(vec);
53 }

動態多態本質上就是面向對象設計中的繼承、多態的概念。動態多態中的接口是顯式接口(虛函數),好比,性能

1 void DoSomething(Widget& w)
2 {
3     if( w.size() > 0 && w != someNastyWidget)
4     {
5         Widget temp(w);
6         temp.normalize();
7         temp.swap(w);
8     }
9 }

對於上面的代碼,這要求:測試

  1. 因爲w的類型被聲明爲Widget,因此w必須支持Widget接口,且一般能夠在源碼中找出這些接口(好比Widget.h),所以這些接口也就是顯示接口;
  2. Widget可能只是一個基類,他有子類,也就是說Widget的接口有多是虛函數(好比上面的normalize),此時對接口的調用就表現出了運行時多態;

什麼是靜態多態?

靜態多態的設計思想:對於相關的對象類型,直接實現它們各自的定義,不須要共有基類,甚至能夠沒有任何關係。只須要各個具體類的實現中要求相同的接口聲明,這裏的接口稱之爲隱式接口。客戶端把操做這些對象的函數定義爲模板,當須要操做什麼類型的對象時,直接對模板指定該類型實參便可(或經過實參演繹得到)。優化

相對於面向對象編程中,以顯式接口和運行期多態(虛函數)實現動態多態,在模板編程及泛型編程中,是以隱式接口和編譯器多態來實現靜態多態。spa

看代碼:設計

 1 namespace StaticPoly
 2 {
 3     class Line
 4     {
 5     public:
 6         void Draw()const{    std::cout << "Line Draw()\n";    }
 7     };
 8 
 9     class Circle
10     {
11     public:
12         void Draw(const char* name=NULL)const{    std::cout << "Circle Draw()\n";    }
13     };
14 
15     class Rectangle
16     {
17     public:
18         void Draw(int i = 0)const{    std::cout << "Rectangle Draw()\n";    }
19     };
20 
21     template<typename Geometry>
22     void DrawGeometry(const Geometry& geo)
23     {
24         geo.Draw();
25     }
26 
27     template<typename Geometry>
28     void DrawGeometry(std::vector<Geometry> vecGeo)
29     {
30         const size_t size = vecGeo.size();
31         for(size_t i = 0; i < size; ++i)
32             vecGeo[i].Draw();
33     }
34 }
35 
36 void test_static_polymorphism()
37 {
38     StaticPoly::Line line;
39     StaticPoly::Circle circle;
40     StaticPoly::Rectangle rect;
41     StaticPoly::DrawGeometry(circle);
42 
43     std::vector<StaticPoly::Line> vecLines;
44     StaticPoly::Line line2;
45     StaticPoly::Line line3;
46     vecLines.push_back(line);
47     vecLines.push_back(line2);
48     vecLines.push_back(line3);
49     //vecLines.push_back(&circle); //編譯錯誤,已再也不可以處理異質對象
50     //vecLines.push_back(&rect);    //編譯錯誤,已再也不可以處理異質對象
51     StaticPoly::DrawGeometry(vecLines);
52 
53     std::vector<StaticPoly::Circle> vecCircles;
54     vecCircles.push_back(circle);
55     StaticPoly::DrawGeometry(circle);
56 }

靜態多態本質上就是模板的具現化。靜態多態中的接口調用也叫作隱式接口,相對於顯示接口由函數的簽名式(也就是函數名稱、參數類型、返回類型)構成,隱式接口一般由有效表達式組成, 好比,

 1 template<typename Widget,typename Other>
 2 void DoSomething(Widget& w, const Other& someNasty)
 3 {
 4     if( w.size() > 0 && w != someNasty) //someNastyT多是是T類型的某一實例,也可能不是
 5     {
 6         Widget temp(w);
 7         temp.normalize();
 8         temp.swap(w);
 9     }
10 }

這看似要求:

  1. 類型T須要支持size、normalize、swap函數,copy構造函數,能夠進行不等比較
  2. 類型T是在編譯期模板進行具現化時才表現出調用不一樣的函數,此時對接口的調用就表現出了編譯期時多態。

可是,

  1. size函數並不須要返回一個整型值以和10比較,甚至都不須要返回一個數值類型,惟一的約束是它返回一個類型爲X的對象,且X對象和int類型(數值10的類型)能夠調用一個operator >,這個operator>也不必定非要一個X類型的參數不可,它能夠經過隱式轉換能將X類型轉爲Y類型對象,而只須要Y類型能夠和int類型比較便可(好繞口,請看,這也側面印證了模板編程編譯錯誤很難解決)。
  2. 一樣類型T並不須要支持operator!=,而只須要T能夠轉爲X類型對象,someNastyT能夠轉爲Y類型對象,而X和Y能夠進行不等比較便可。

動態多態和靜態多態的比較

靜態多態

優勢:

  1. 因爲靜多態是在編譯期完成的,所以效率較高,編譯器也能夠進行優化;
  2. 有很強的適配性和鬆耦合性,好比能夠經過偏特化、全特化來處理特殊類型;
  3. 最重要一點是靜態多態經過模板編程爲C++帶來了泛型設計的概念,好比強大的STL庫。

缺點:

  1. 因爲是模板來實現靜態多態,所以模板的不足也就是靜多態的劣勢,好比調試困難、編譯耗時、代碼膨脹、編譯器支持的兼容性
  2. 不可以處理異質對象集合

動態多態

優勢:

  1. OO設計,對是客觀世界的直覺認識;
  2. 實現與接口分離,可複用
  3. 處理同一繼承體系下異質對象集合的強大威力

缺點:

  1. 運行期綁定,致使必定程度的運行時開銷;
  2. 編譯器沒法對虛函數進行優化
  3. 笨重的類繼承體系,對接口的修改影響整個類層次;

不一樣點:

  1. 本質不一樣,靜態多態在編譯期決定,由模板具現完成,而動態多態在運行期決定,由繼承、虛函數實現;
  2. 動態多態中接口是顯式的,以函數簽名爲中心,多態經過虛函數在運行期實現,靜態多臺中接口是隱式的,以有效表達式爲中心,多態經過模板具如今編譯期完成

相同點:

  1. 都可以實現多態性,靜態多態/編譯期多態、動態多態/運行期多態;
  2. 都可以使接口和實現相分離,一個是模板定義接口,類型參數定義實現,一個是基類虛函數定義接口,繼承類負責實現;

 

附上本次測試的全部代碼:

  1 namespace DynamicPoly
  2 {
  3     class Geometry
  4     {
  5     public:
  6         virtual void Draw()const = 0;
  7     };
  8 
  9     class Line : public Geometry
 10     {
 11     public:
 12         virtual void Draw()const{    std::cout << "Line Draw()\n";    }
 13     };
 14 
 15     class Circle : public Geometry
 16     {
 17     public:
 18         virtual void Draw()const{    std::cout << "Circle Draw()\n";    }
 19     };
 20 
 21     class Rectangle : public Geometry
 22     {
 23     public:
 24         virtual void Draw()const{    std::cout << "Rectangle Draw()\n";    }
 25     };
 26 
 27     void DrawGeometry(const Geometry *geo)
 28     {
 29         geo->Draw();
 30     }
 31 
 32     //動態多態最吸引人之處在於處理異質對象集合的能力
 33     void DrawGeometry(std::vector<DynamicPoly::Geometry*> vecGeo)
 34     {
 35         const size_t size = vecGeo.size();
 36         for(size_t i = 0; i < size; ++i)
 37             vecGeo[i]->Draw();
 38     }
 39 }
 40 
 41 namespace StaticPoly
 42 {
 43     class Line
 44     {
 45     public:
 46         void Draw()const{    std::cout << "Line Draw()\n";    }
 47     };
 48 
 49     class Circle
 50     {
 51     public:
 52         void Draw(const char* name=NULL)const{    std::cout << "Circle Draw()\n";    }
 53     };
 54 
 55     class Rectangle
 56     {
 57     public:
 58         void Draw(int i = 0)const{    std::cout << "Rectangle Draw()\n";    }
 59     };
 60 
 61     template<typename Geometry>
 62     void DrawGeometry(const Geometry& geo)
 63     {
 64         geo.Draw();
 65     }
 66 
 67     template<typename Geometry>
 68     void DrawGeometry(std::vector<Geometry> vecGeo)
 69     {
 70         const size_t size = vecGeo.size();
 71         for(size_t i = 0; i < size; ++i)
 72             vecGeo[i].Draw();
 73     }
 74 }
 75 
 76 void test_dynamic_polymorphism()
 77 {
 78     DynamicPoly::Line line;
 79     DynamicPoly::Circle circle;
 80     DynamicPoly::Rectangle rect;
 81     DynamicPoly::DrawGeometry(&circle);
 82 
 83     std::vector<DynamicPoly::Geometry*> vec;
 84     vec.push_back(&line);
 85     vec.push_back(&circle);
 86     vec.push_back(&rect);
 87     DynamicPoly::DrawGeometry(vec);
 88 }
 89 
 90 void test_static_polymorphism()
 91 {
 92     StaticPoly::Line line;
 93     StaticPoly::Circle circle;
 94     StaticPoly::Rectangle rect;
 95     StaticPoly::DrawGeometry(circle);
 96 
 97     std::vector<StaticPoly::Line> vecLines;
 98     StaticPoly::Line line2;
 99     StaticPoly::Line line3;
100     vecLines.push_back(line);
101     vecLines.push_back(line2);
102     vecLines.push_back(line3);
103     //vecLines.push_back(&circle); //編譯錯誤,已再也不可以處理異質對象
104     //vecLines.push_back(&rect);    //編譯錯誤,已再也不可以處理異質對象
105     StaticPoly::DrawGeometry(vecLines);
106 
107     std::vector<StaticPoly::Circle> vecCircles;
108     vecCircles.push_back(circle);
109     StaticPoly::DrawGeometry(circle);
110 }
111 
112 /**沒法編譯經過,所以Widget要求有顯式接口,但如今看不到*/
113 //void DoSomething(Widget& w)
114 //{
115 //    if( w.size() > 0 && w != someNastyWidget)
116 //    {
117 //        Widget temp(w);
118 //        temp.normalize();
119 //        temp.swap(w);
120 //    }
121 //}
122 
123 /**能夠編譯經過,所以此處只是要求了只有在模板具現時需保證下面可編譯(無調用,無具現)*/
124 template<typename Widget,typename Other>
125 void DoSomething(Widget& w, const Other& someNasty)
126 {
127     if( w.size() > 0 && w != someNasty) //someNastyT多是是T類型的某一實例,也可能不是
128     {
129         Widget temp(w);
130         temp.normalize();
131         temp.swap(w);
132     }
133 }
Static_Dynamic_Polymorphism.cpp
相關文章
相關標籤/搜索