C++ Virtual总结

虚函数表
C++中的虚函数的实现一般是通过虚函数表(C++规范并没有规定具体用哪种方法,但大部分的编译器厂商都选择此方法)。
类的虚函数表是一块连续的内存,每个内存单元中记录一个JMP指令的地址。
注意的是,编译器会为每个有虚函数的类创建一个虚函数表,该虚函数表将被该类的所有对象共享。类的每个虚成员占据虚函数表中的一行。如果类中有N个虚函数,那么其虚函数表将有N*4字节的大小。
虚函数(Virtual Function)是通过一张虚函数表来实现的。简称为V-Table。在这个表中,主要是一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证其真实反应实际的函数。这样,在有虚函数的类的实例中分配了指向这个表的指针的内存,所以,当用父类的指针来操作一个子类的时候,这张虚函数表就显得尤为重要了,它就像一个地图一样,指明了实际所应该调用的函数。
编译器应该是保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保证取到虚函数表的有最高的性能——如果有多层继承或是多重继承的情况下)。 这意味着可以通过对象实例的地址得到这张虚函数表,然后就可以遍历其中函数指针,并调用相应的函数。

===================================================================================================================

c++是一门面向对象的语言,但是它和c#,java不同,它没有反射机制。没有反射机制使得c++在语言的一些设计方面与其他语言有点不一样,主要体现在智能化方面,许多东西得程序员明确指定,例如本文要讲的virtual关键字。virtual是在运行时才体现的,而c++在运行时无法使用反射来确定当前类的父类是否有此方法,此方法是否被重载等信息,所以必须在写代码时用virtual来明确指明,然后通过编译器做一些特殊处理(也就是使用虚表)。

我们见到virtual最多的地方是在c++里面的多态实现。

  1. // dynamic_poly.h  
  2. #include <iostream>  
  3. // 公共抽象基类Vehicle  
  4. class Vehicle  
  5. {  
  6. public:  
  7.     virtual void run() const = 0;  
  8. };  
  9. // 派生于Vehicle的具体类Car  
  10. class Car: public Vehicle  
  11. {  
  12. public:  
  13.     virtual void run() const  
  14.     {  
  15.         std::cout << "run a car/n";  
  16.     }  
  17. };  
  18. // 派生于Vehicle的具体类Airplane  
  19. class Airplane: public Vehicle  
  20. {  
  21. public:  
  22.     virtual void run() const  
  23.     {  
  24.         std::cout << "run a airplane/n";  
  25.     }  
  26. };   
 

  1. // dynamic_poly_1.cpp  
  2. #include <iostream>  
  3. #include <vector>  
  4. #include "dynamic_poly.h"  
  5. // 通过指针run任何vehicle  
  6. void run_vehicle(const Vehicle* vehicle)  
  7. {  
  8.     vehicle->run();            // 根据vehicle的具体类型调用对应的run()  
  9. }  
  10. int main()  
  11. {  
  12.     Car car;  
  13.     Airplane airplane;  
  14.     run_vehicle(&car);         // 调用Car::run()  
  15.     run_vehicle(&airplane);    // 调用Airplane::run()  
  16. }  
 

上面的例子来自于http://www.vckbase.com/document/viewdoc/?id=948,这篇文章里面还提到了多态的一些别的例子。关于多态的文章还有孟岩写的一篇http://blog.csdn.net/wuliming_sc/archive/2009/01/31/3855906.aspx,可以看看。

在很多多态的例子中,我们都可以看到将基类的方法声明为纯虚函数(virtual void run() const = 0;),这样可以要求子类必须实现这个方法,同时可以体现面向接口编程。

对于使用了virtual之后编译器会做些什么,我觉得用代码更容易说明问题。下面的例子来自http://www.cppblog.com/zhangyq/archive/2009/06/13/87597.html

1 编译器会为这个类的虚函数添加一个虚表,类似下面的: 
// Pseudo-code (not C++, not C) for a static table defined within file Base.cpp 
// Pretend FunctionPtr is a generic pointer to a generic member function 
// (Remember: this is pseudo-code, not C++ code) 
FunctionPtr Base::__vtable[5] = { 
   &Base::virt0, &Base::virt1, &Base::virt2, &Base::virt3, &Base::virt4 
};

2 然后增加一个指向虚表的指针为每一个类对象,这个指针是隐藏的 
// Your original C++ source code 
class Base { 
public: 
   ... 
   FunctionPtr* __vptr;  ← supplied by the compiler, hidden from the programmer 
   ... 
}; 
3 编译器在构造中初始化这个指针 
Base::Base(...arbitrary params...) 
   : __vptr(&Base::__vtable[0])  ← supplied by the compiler, hidden from the programmer 
   ... 

   ... 

在派生类中,它也会增加一个隐藏的虚表,但是它可以overrides基类的虚函数如: 
// Pseudo-code (not C++, not C) for a static table defined within file Der.cpp 
// Pretend FunctionPtr is a generic pointer to a generic member function 
// (Remember: this is pseudo-code, not C++ code) 
FunctionPtr Der::__vtable[5] = { 
   &Der::virt0, &Der::virt1, &Der::virt2, &Base::virt3, &Base::virt4 
};   

从上面的代码我们可以非常容易的划出class的结构图,当然可以用/d1reportSingleClassLayout来显示class的布局图(http://blog.csdn.net/chief1985/archive/2009/10/23/4720191.aspx)。

使用virtual的地方还有虚拟继承和虚拟析构函数。

虚拟继承的例子如下,来自http://blog.csdn.net/wuliming_sc/archive/2009/01/31/3855607.aspx

  1. class Point2d{  
  2. public:  
  3. //...  
  4. protected:  
  5. float _x;  
  6. float _y;  
  7. };  
  8. class Vertex : public virtual Point2d{  
  9. public:  
  10. //...  
  11. protected:  
  12. Vertex *next;  
  13. };  
  14. class Point3d : public virtual Point2d{  
  15. public:  
  16. //...  
  17. protected:  
  18. float _z;  
  19. };  
  20. class Vertex3d: public Vertex, public Point3d{  
  21. public:  
  22. //...  
  23. protected:  
  24. float mumble;  
  25. };  
 

image

使用虚拟继承一般都是出现了菱形继承(c++允许多重继承),这种情况下若不使用虚拟继承,便会导致以下情况:

1.公共基类子对象的重复创建问题。

2.成员函数的名字冲突问题

3. 数据成员的名字冲突问题

发布了11 篇原创文章 · 获赞 9 · 访问量 7万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章