//1.cpp 1. #include <iostream.h> 2. class shape{ 3. public: 4. void draw(){cout<<"I am shape"<<endl;} 5. void fun(){draw();} 6. }; 7. class circle:public shape{ 8. public: 9. void draw(){cout<<"I am circle"<<endl;} 10. }; 11. main(){ 12. class circle oneshape; 13. oneshape.fun(); 14. }
程序的输出结果我们希望是"I am circle",但事实上却输出了"I am shape"的结果,造成这个结果的原因是静态联编。静态联编需要在编译时候就确定函数的实现,但事实上编译器在仅仅知道shape的地址时候无法获取正确的调用函数,它所知道的仅是shape::draw(),最终结果只能是draw操作束缚到shape类上。产生"I am shape"的结果就不足为奇了。
为了能够引起动态联编,我们只需要将需要动态联编的函数声明为虚函数即可。动态联编只对虚函数起作用。我们在通过基类而且只有通过基类访问派生类的时候,只要这个基类中直接的或者间接(从上上层继承)的包含虚函数,动态联编将自动唤醒。下面我们将上面的程序稍微改一下。
//2.cpp 1. #include <iostream.h> 2. class shape{ 3. public: 4. virtual void draw(){cout<<"I am shape"<<endl;} 5. void fun(){draw();} 6. }; 7. class circle:public shape{ 8. public: 9. void draw(){cout<<"I am circle"<<endl;} 10. }; 11. main(){ 12. class circle oneshape; 13. fun(&oneshape); 14. }
程序执行得到了正确的结果"I am circle"。代码在VC6.0中执行。
到目前为止我们不清楚动态联编的执行机制,但我们可以做个猜测。正如上面所说,对于函数的实际的对象类型不同,联编结果也应该不同。在静态联编中,执行的困难在于无法通过基类知道需要联编的子对象的确切类型。在1.cpp中shape的派生类既可能是circle,也可能是其余的rectangle或者square等等,到底应该静态联编哪一个呢。迷惑正在于此。动态联编在编译的时候应该也是不知道联编的确切对象类型的,(如果知道的话就成了静态联编了),因此它只能通过一定的机制,使得在执行时候能够找到和调用正确的函数体。可以想象,为了达到这个目的,一些相关信息应该封装在对象自身中。这些信息有点象身份证明,标识自己,这样在动态联编的时候,编译器可以根据这些标记找到相应的函数体,"不要跑,就是你了"。
实际上的动态联编过程是什么样的呢。
二 对象类型信息
为了证明我们的猜想,我们用下面的一个程序进行测试,下面的程序将获取普通的类和包含虚函数的类的字节大小。程序代码如下。
//3.cpp 1. #include <iostream.h> 2. class shape_novirtual{ 3. int a; 4. public: 5. void draw(){cout<<"shape_novirtual::draw()"<<endl;} 6. }; 7. class shape_virtual1{ 8. int a; 9. public: 10. virtual void draw(){cout<<"shape_virtual::draw()"<<endl;} 11. }; 12. class shape_virtual2{ 13. int a; 14. public: 15. virtual void draw(){cout<<"shape_virtual2::draw()"<<endl;} 16. virtual void draw1(){cout<<"shape_virtual2::draw1()"<<endl;} 17. }; 18. main(){ 19. cout<<"sizeof(int)"<<sizeof(int)<<endl; 20. cout<<"sizeof(class shape_novirtual):"<<sizeof(shape_novirtual)<<endl; 21. cout<<"sizeof(void*):"<<sizeof(void*)<<endl; 22. cout<<"sizeof(class shape_virtual):"<<sizeof(shape_virtual)<<endl; 23. cout<<"sizeof(class shape_virtual2):"<<sizeof(shape_virtual2)<<endl; 24. }
VC6.0中运行结果如下:
sizeof(int)4 sizeof(class shape_novirtual):4 sizeof(void*):4 sizeof(class shape_virtual1):8 sizeof(class shape_virtual2):8 Press any key to continue
从上面可以看出,没有虚函数的类shape_novirtual的大小为4,正好为int a的大小。而带有虚函数的类shape_virtual1和shape_virtual2的大小除了int a的大小还多出了4格个字节的大小,这个大小正好是void*指针的大小。到现在为止我们基本上可以说带有虚函数的对象自身确实插入了一些指针信息,而且这个指针信息并不随着虚函数的增加而增大。
如果我们将每个类的成员变量int a去掉,VC6.0运行结果就会变成下面的情况。
sizeof(int)4 sizeof(class shape_novirtual):1 sizeof(void*):4 sizeof(class shape_virtual1):4 sizeof(class shape_virtual2):4 Press any key to continue
上面的运行结果应该让人感到例外。既然size(int)为4,现在没有了这个成员变量,类shape_novirtual应该字节大小为0,但事实上C++编译器不允许对象为零长度。试想一个长度为0的对象在内存中怎么存放?怎么获取它的地址?为了避免这种情况,C++强制给这种类插入一个缺省成员,长度为1。如果有自定义的变量,变量将取代这个缺省成员。
作者信息:
电子邮件: [email protected]