今天的C++已经是个多重泛型编程语言(multiparadigm programming lauguage),一个同时支持过程形式(procedural)、面向对象形式(object-oriented)、函数形式(functional)、泛型形式(generic)、元编程形式(metaprogramming)的语言。 这些能力和弹性使C++成为一个无可匹敌的工具,但也可能引发使用者的某些迷惑,比如多态。在这几种编程泛型中,面向对象编程、泛型编程以及很新的元编程形式都支持多态的概念,但又有所不同。 C++支持多种形式的多态,从表现的形式来看,有虚函数、模板、重载等,从绑定时间来看,可以分成静态多态和动态多态,也称为编译期多态和运行期多态。
本文即讲述这其中的异同。注意泛型编程和元编程通常都是以模板形式实现的,因此在本文中主要介绍基于面向对象的动态多态和基于模板编程的静态多态两种形式。另外其实宏也可以认为是实现静态多态的一种方式,实现原理就是全文替换,但C++语言本身就不喜欢宏,这里也忽略了“宏多态”。
动态多态的设计思想:对于相关的对象类型,确定它们之间的一个共同功能集,然后在基类中,把这些共同的功能声明为多个公共的虚函数接口。各个子类重写这些虚函数,以完成具体的功能。客户端的代码(操作函数)通过指向基类的引用或指针来操作这些对象,对虚函数的调用会自动绑定到实际提供的子类对象上去。
从上面的定义也可以看出,由于有了虚函数,因此动态多态是在运行时完成的,也可以叫做运行期多态,这造就了动态多态机制在处理异质对象集合时的强大威力(当然,也有了一点点性能损失)。
看代码:
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 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 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 }