近来看了侯捷的《深入浅出MFC》,读到C++重要性质中的虚函数与多态那部分内容时,顿时有了疑惑。因为书中说了这么一句:使用“基类之指针”指向“派生类之对象”,由该指针只能调用基类所定义的函数,如果要让基类的指针使用派生类中定义的函数,就将该函数定义为虚函数。
但在“Object slicing与虚函数”这一小节给出了一个及其经典的例子,它指出,在向上(即向基类)强制转型时,会造成对象内容的被切割。
下面用示例进行说明:
1 #include "stdafx.h" 2 #include <iostream> 3 using namespace std; 4 5 class A 6 { 7 public: 8 virtual void fn(){cout<<"A fn"<<endl;} 9 }; 10 11 class B: public A 12 { 13 public: 14 virtual void fn(){cout<<"B fn"<<endl;} 15 }; 16 17 int main(int argc, char* argv[]) 18 { 19 B b1; 20 A a1=(A)b1; 21 A * a2=(A*)&b1; 22 a1.fn(); 23 a2->fn(); 24 return 0; 25 }
结果如下:
通过调试分析其内存模型如下:
可知,通过A a1=(A)b1传值时,对象中指向虚函数表的指针__vfptr值不同,但是通过A *a2=(A*)&b1传址操作时,对象中指向虚函数表的指针__vfptr值是相同的,调用的是类B对象的虚函数表中虚函数fn()。注意,对象调用的是哪个类的虚函数就要看类对象的虚函数表中的函数是哪一个。这句话也好理解,a1虚函数表中fn()是A::fn(),a2虚函数表中fn()是B::fn(),所以出现以上结果。
在如下包含父类的父类的继承中:
1 class A 2 { 3 public: 4 virtual void fn(){cout<<"A fn"<<endl;} 5 }; 6 7 class B: public A 8 { 9 public: 10 virtual void fn(){cout<<"B fn"<<endl;} 11 }; 12 13 class C: public B 14 { 15 public: 16 }; 17 C c; 18 A a1=(A)c; 19 A * a2=(A*)&c; 20 a1.fn(); 21 a2->fn();
这时,a1虚函数表中fn()为A::fn(),a2虚函数表中fn()为B::fn(),因为类C对象的虚函数表中fn()为B::fn(),a2中指向虚函数表的指针值与类C对象指向虚函数表的指针值相同。
侯捷的书中有句话是这么对它进行解释的:由于(A)b1.fn()是传值而非传地址操作,编译器以所谓的拷贝构造函数把A对象内容复制了一份,使得b1的虚函数表内容与A对象的虚函数表内容相同。
看来,得多读圣贤书,读懂读透啊!