# C++中多态的实现

14 篇文章 0 订阅

为了方便说明，下面举一个简单的使用多态的例子(From [1] )：

Cpp代码
1. class Shape
2. {
3. protected:
4. int m_x;    // X coordinate
5. int m_y;  // Y coordinate
6. public:
7. // Pure virtual function for drawing
8. virtual void Draw() = 0;
9. // A regular virtual function
10. virtual void MoveTo(int newX, int newY);
11. // Regular method, not overridable.
12. void Erase();
13. // Constructor for Shape
14.   Shape(int x, int y);
15. // Virtual destructor for Shape
16. virtual ~Shape();
17. };
18. // Circle class declaration
19. class Circle : public Shape
20. {
21. private:
23. public:
24. // Override to draw a circle
25. virtual void Draw();
26. // Constructor for Circle
27.    Circle(int x, int y, int radius);
28. // Destructor for Circle
29. virtual ~Circle();
30. };
31. // Shape constructor implementation
32. Shape::Shape(int x, int y)
33. {
34.    m_x = x;
35.    m_y = y;
36. }
37. // Shape destructor implementation
38. Shape::~Shape()
39. {
40. //...
41. }
42. // Circle constructor implementation
43. Circle::Circle(int x, int y, int radius) : Shape (x, y)
44. {
46. }
47. // Circle destructor implementation
48. Circle::~Circle()
49. {
50. //...
51. }
52. // Circle override of the pure virtual Draw method.
53. void Circle::Draw()
54. {
56. }
57. int main()
58. {
59. // Define a circle with a center at (50,100) and a radius of 25
60.   Shape *pShape = new Circle(50, 100, 25);
61. // Define a circle with a center at (5,5) and a radius of 2
62.   Circle aCircle(5,5, 2);
63. // Various operations on a Circle via a Shape pointer
64. //Polymorphism
65.   pShape->Draw();
66.   pShape->MoveTo(100, 100);
67.   pShape->Erase();
68. delete pShape;
69. // Invoking the Draw method directly
70.   aCircle.Draw();
71. }
class Shape{protected:  int m_x;    // X coordinate  int m_y;  // Y coordinatepublic:  // Pure virtual function for drawing  virtual void Draw() = 0;    // A regular virtual function  virtual void MoveTo(int newX, int newY); // Regular method, not overridable.  void Erase();  // Constructor for Shape  Shape(int x, int y);  // Virtual destructor for Shape  virtual ~Shape();};// Circle class declarationclass Circle : public Shape{private:   int m_radius;    // Radius of the circle public:   // Override to draw a circle   virtual void Draw();       // Constructor for Circle   Circle(int x, int y, int radius);  // Destructor for Circle   virtual ~Circle();};// Shape constructor implementationShape::Shape(int x, int y){   m_x = x;   m_y = y;}// Shape destructor implementationShape::~Shape(){//...} // Circle constructor implementationCircle::Circle(int x, int y, int radius) : Shape (x, y){   m_radius = radius;}// Circle destructor implementationCircle::~Circle(){//...}// Circle override of the pure virtual Draw method.void Circle::Draw(){   glib_draw_circle(m_x, m_y, m_radius);}int main(){  // Define a circle with a center at (50,100) and a radius of 25  Shape *pShape = new Circle(50, 100, 25);  // Define a circle with a center at (5,5) and a radius of 2  Circle aCircle(5,5, 2);  // Various operations on a Circle via a Shape pointer  //Polymorphism  pShape->Draw();  pShape->MoveTo(100, 100);  pShape->Erase();  delete pShape; // Invoking the Draw method directly  aCircle.Draw();}

virtual！！
Clever！！正是virtual这个关键字一手导演了这一出“乾坤大挪移”的好戏。说道这里，我们先要明确两个概念：静态绑定和动态绑定。
2、动态绑定（dynamic binding），也叫晚期绑定，与静态绑定不同，在编译期间，编译器并不能明确知道究竟要调用的是哪一个方法，而这，要知道运行期间使用的具体是哪个对象才能决定。
好了，有了这两个概念以后，我们就可以说，virtual的作用就是告诉编译器：我要进行动态绑定！编译器当然会尊重你的意见，而且为了完成你这个要求，编译器还要做很多的事情：编译器自动在声明了virtual方法的类中插入一个指针vptr和一个数据结构VTable（vptr用以指向VTable；VTable是一个指针数组，里面存放着函数的地址），并保证二者遵守下面的规则：
1、VTable中只能存放声明为virtual的方法，其它方法不能存放在里面。在上面的例子中，Shape的VTable中就只有Draw，MoveTo和~Shape。方法Erase的地址并不能存放在VTable中。此外，如果方法是纯虚函数，如 Draw，那么同样要在VTable中保留相应的位置，但是由于纯虚函数没有函数体，因此该位置中并不存放Draw的地址，而是可以选择存放一个出错处理的函数的地址，当该位置被意外调用时，可以用出错函数进行相应的处理。
2、派生类的VTalbe中记录的从基类中继承下来的虚函数地址的索引号必须跟该虚函数在基类VTable中的索引号保持一致。如在上例中，如果在Shape的VTalbe中，Draw为 1 号， MoveTo 2 号，~Shape为 3 号，那么，不管这些方法在Circle中是按照什么顺序定义的，Circle的VTable中都必须保证Draw为 1 号，MoveTo为 2号。至于 3号，这里是~Circle。为什么不是~Shape啊？嘿嘿，忘啦，析构函数不会继承的。
3、vptr是由编译器自动插入生成的，因此编译器必须负责为其进行初始化。初始化的时间选在对象创建时，而地点就在构造函数中。因此，编译器必须保证每个类至少有一个构造函数，若没有，自动为其生成一个默认构造函数。
你看，天下果然没有免费的午餐，为了实现动态绑定，编译器要为我们默默干了这么多的脏话累活。如果你想体验一下编译器的辛劳，那么可以尝试用C语言模拟一下上面的行为，【1】中就有这么一个例子。好了，现在万事具备，只欠东风了。编译，连接，载入，GO！当程序执行到 pShape->Draw()的时候，上面的设施也开始起作用了。。
前面已经提到，晚期绑定时之所以不能确定调用哪个函数，是因为具体的对象不确定。好了，当运行到pShape->Draw()时，对象出来了，它由pShape指针标出。我们找到这个对象后，就可以找到它里面的vptr（在对象的起始处），有了vptr后，我们就找到了VTable，调用的函数就在眼前了。。等等，VTable中方法那么多，我究竟使用哪个呢？不用着急，编译器早已为我们做好了记录：编译器在创建VTable时，已经为每个virtual函数安排好了座次，并且把这个索引号记录了下来。因此，当编译器解析到pShape->Draw()的时候，它已经悄悄的将函数的名字用索引号来代替了。这时候，我们通过这个索引号就可以在VTable中得到一个函数地址，Call it！
在这里，我们就体会到为什么会有第二条规定了，通常，我们都是用基类的指针来引用派生类的对象，但是不管具体对象是哪个派生类的，我们都可以使用相同的索引号来取得对应的函数实现。
现实中有一个例子其实跟这个蛮像的：报警电话有110，119，120（VTable中不同的方法）。不同地方的人拨打不同的号码所产生的结果都是不一样的。譬如，在三环外的一个人（具体对象）跟一环内的一个人（另外一个具体对象）打119，最后调用的消防队肯定是不一样的，这就是多态了。这是怎么实现的呢，每个人都知道一个报警中心（VTable，里面有三个方法 110，119，120）。如果三环外的一个人需要火警抢险（一个具体对象）时，它就拨打119，但是他肯定不知道最后是哪一个消防队会出现的。这得有报警中心来决定，报警中心通过这个具体对象（例子中就是具体位置了）以及他说拨打的电话号码（可以理解成索引号），报警中心可以确定应该调度哪一个消防队进行抢险（不同的动作）。
这样，通过vptr和VTable的帮助，我们就实现了C++的动态绑定。当然，这仅仅是单继承时的情况，多重继承的处理要相对复杂一点，下面简要说一下最简单的多重继承的情况，至于虚继承的情况，有兴趣的朋友可以看看 Lippman的《Inside the C++ Object Model》，这里暂时就不展开了。（主要是自己还没搞清楚，况且现在多重继承都不怎么使用了，虚继承应用的机会就更少了）
首先，我要先说一下多重继承下对象的内存布局，也就是说该对象是如何存放本身的数据的。

Cpp代码
1. class Cute
2. {
3. public:
4. int i;
5. virtual void cute(){ cout<<"Cute cute"<<endl; }
6. };
7. class Pet
8. {
9. public:
10. int j;
11. virtual void say(){ cout<<"Pet say"<<endl;  }
12. };
13. class Dog : public Cute,public Pet
14. {
15. public:
16. int z;
17. void cute(){ cout<<"Dog cute"<<endl; }
18. void say(){ cout<<"Dog say"<<endl;  }
19. };
class Cute{public: int i; virtual void cute(){ cout<<"Cute cute"<<endl; }};class Pet{public:   int j;   virtual void say(){ cout<<"Pet say"<<endl;  }};class Dog : public Cute,public Pet{public: int z; void cute(){ cout<<"Dog cute"<<endl; } void say(){ cout<<"Dog say"<<endl;  }};

 Dog Vptr1 Cute::i Vptr2 Pet::j Dog::z

Cpp代码
1. int main()
2. {
3. Dog* d = new Dog();
4. cout<<"Dog object addr : "<<d<<endl;
5. Cute* c = d;
6. cout<<"Cute type addr : "<<c<<endl;
7. Pet* p = d;
8. cout<<"Pet type addr : "<<p<<endl;
9. delete d;
10. }
11. output:
12. Dog object addr : 0x3d24b0
13. Cute type addr : 0x3d24b0
14. Pet type addr : 0x3d24b8   // 正好指向Dog对象的vptr2处，也就是Pet的数据
int main(){ Dog* d = new Dog(); cout<<"Dog object addr : "<<d<<endl; Cute* c = d; cout<<"Cute type addr : "<<c<<endl; Pet* p = d; cout<<"Pet type addr : "<<p<<endl; delete d;}output:Dog object addr : 0x3d24b0Cute type addr : 0x3d24b0Pet type addr : 0x3d24b8   // 正好指向Dog对象的vptr2处，也就是Pet的数据

好了，既然编译器帮我们自动完成了不同父类的地址转换，我们调用虚函数的过程也就跟单继承统一起来了：通过具体对象，找到vptr（通常指针的起始位置，因此Cute找到的是vptr1，而Pet找到的是vptr2），通过vptr，我们找到VTable，然后根据编译时得到的VTable索引号，我们取得相应的函数地址，接着就可以马上调用了。

在这里，顺便也提一下两个特殊的方法在多态中的特别之处吧：第一个是构造函数，在构造函数中调用虚函数是不会有多态行为的，例子如下：

Cpp代码
1. class Pet
2. {
3. public:
4.    Pet(){ sayHello(); }
5. void say(){ sayHello(); }
6. virtual void sayHello()
7.    {
8.      cout<<"Pet sayHello"<<endl;
9.    }
10. };
11. class Dog : public Pet
12. {
13. public:
14.    Dog(){};
15. void sayHello()
16.    {
17.      cout<<"Dog sayHello"<<endl;
18.    }
19. };
20. int main()
21. {
22. Pet* p = new Dog();
23. p->sayHello();
24. delete p;
25. }
26. output:
27. Pet sayHello //直接调用的是Pet的sayHello()
28. Dog sayHello //多态
class Pet{public:   Pet(){ sayHello(); }   void say(){ sayHello(); }   virtual void sayHello()   {     cout<<"Pet sayHello"<<endl;   }   };class Dog : public Pet{public:   Dog(){};   void sayHello()   {     cout<<"Dog sayHello"<<endl;   }};int main(){ Pet* p = new Dog(); p->sayHello(); delete p;}output:Pet sayHello //直接调用的是Pet的sayHello()Dog sayHello //多态

第二个就是析构函数，使用多态的时候，我们经常使用基类的指针来引用派生类的对象，如果是动态创建的，对象使用完后，我们使用delete来释放对象。但是，如果我们不注意的话，会有意想不到的情况发生。

Cpp代码
1. class Pet
2. {
3. public:
4.    ~Pet(){ cout<<"Pet destructor"<<endl;  }
5. //virtual ~Pet(){ cout<<"Pet virtual destructor"<<endl;  }
6. };
7. class Dog : public Pet
8. {
9. public:
10.    ~Dog(){ cout<<"Dog destructor"<<endl;};
11. //virtual ~Dog(){ cout<<"Dog virtual destructor"<<endl;  }
12. };
13. int main()
14. {
15. Pet* p = new Dog();
16. delete p;
17. }
18. output：
19. Pet destructor  //糟了，Dog的析构函数没有调用，memory leak!
20. 如果我们将析构函数改成virtual以后，结果如下
21. Dog virtual destructor
22. Pet virtual destructor   // That's OK!
class Pet{public:   ~Pet(){ cout<<"Pet destructor"<<endl;  }  //virtual ~Pet(){ cout<<"Pet virtual destructor"<<endl;  }};class Dog : public Pet{public:   ~Dog(){ cout<<"Dog destructor"<<endl;};   //virtual ~Dog(){ cout<<"Dog virtual destructor"<<endl;  }};int main(){ Pet* p = new Dog(); delete p;}output：Pet destructor  /a/糟了，Dog的析构函数没有调用，memory leak!如果我们将析构函数改成virtual以后，结果如下Dog virtual destructorPet virtual destructor   // That's OK!

• 0
点赞
• 0
收藏
觉得还不错? 一键收藏
• 0
评论
08-30 1万+
04-18 205
06-01 1045
08-08
08-08
08-31
06-23

### “相关推荐”对你有帮助么？

• 非常没帮助
• 没帮助
• 一般
• 有帮助
• 非常有帮助

1.余额是钱包充值的虚拟货币，按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载，可以购买VIP、C币套餐、付费专栏及课程。