一、前言
最近在研究虚函数,发现网上很多文章都没有把虚函数表说清楚,即便有的说清楚了,实例代码的详细解释也不清楚,而且还有错误,现对虚函数表发表个人拙见,请各位同仁不吝赐教。
说到虚函数表就不得不说虚函数,虚函数的定义:在某基类中声明为virtual并在一个或多个派生类中被重新定义的成员函数,此处需注意基类和子类中的该函数都是虚函数。
虚函数是C++中用于实现多态机制。关于多态,就是用父类型的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。这是一种泛型技术,所谓泛型技术就是试图使用不变的代码来实现可变的算法。
二、虚函数表
C++中的虚函数的实现一般是通过虚函数表(C++规范并没有规定具体用哪种方法,但大部分的编译器厂商都选择此方法)。
类的虚函数表是一块连续的内存,也就是数组,这里要注意的是,编译器会为每个有虚函数的类创建一个虚函数表,该虚函数表将被该类的所有对象共享。类的每个虚成员占据虚函数表的一行。如果类中有N个虚函数,那么其虚函数表将有N*4(64位系统N*8,即指针大小)字节的大小。
虚函数是通过一张虚函数表vtbl来实现的。在这个表中,主要是一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证其真实反映实际的函数。这样在有虚函数的类的实例中分配了指向这个表的指针的内存,所以当用父类的指针来操作一个子类的时候,这张虚函数表就显得尤为重要了,它就像一个地图一样,指明了实际所应该调用的函数。
编译器应该是保证虚函数表的指针存在于对象实例中最前面的位置。这意味着可以通过对象实例的地址得到这张虚函数表,然后就可以遍历其中的函数指针,并调用相应的函数。这也是本文主要阐述的内容。
实例代码:
1 #include <iostream> 2 using namespace std; 3 4 class Base 5 { 6 public: 7 virtual void f() { cout << "11 Base::f" << endl; } 8 virtual void g() { cout << "12 Base::g" << endl; } 9 virtual void h() { cout << "13 Base::h" << endl; } 10 }; 11 12 typedef void (*Fun)(void); 13 14 int main(void) 15 { 16 Fun pfun = NULL; 17 Base b; 18 cout << "1 pointer size:" << sizeof(&b) << ", int size:" << sizeof(int) << ", long size:" << sizeof(long) << endl; 19 cout << "2 &b: " << &b << endl; 20 cout << "3 (long*)(&b): " << (long*)(&b) << endl; 21 cout << "4 *(long*)(&b): " << *(long*)(&b) << endl; 22 cout << "5 (long*)*(long*)(&b): " << (long*)*(long*)(&b) << endl; 23 cout << "6 *(long*)*(long*)(&b): " << *(long*)*(long*)(&b) << endl; 24 cout << endl; 25 26 cout << "7 虚函数表地址即vptr的值(指针形式):" << (long*)*(long*)(&b) << endl; 27 cout << "8 虚函数表 第一个元素的值即第一个函数的地址(指针形式):" << (long*)*((long*)*(long*)(&b)+0) << endl; 28 cout << "9 虚函数表 第二个元素的值即第二个函数的地址(指针形式):" << (long*)*((long*)*(long*)(&b)+1) << endl; 29 cout << "10 虚函数表 第三个元素的值即第三个函数的地址(指针形式):" << (long*)*((long*)*(long*)(&b)+2) << endl; 30 // Base::f() 31 pfun = (Fun)*((long*)*(long*)(&b)+0); 32 pfun(); 33 // Base::g() 34 pfun = (Fun)*((long*)*(long*)(&b)+1); 35 pfun(); 36 // Base::h() 37 pfun = (Fun)*((long*)*(long*)(&b)+2); 38 pfun(); 39 return 0; 40 }
g++ 编译生成a.out,运行程序./a.out
运行结果:
代码解释:
1、此处为何要将指针大小,int类型大小,long类型大小打印出来是为了区分32位操作系统和64位操作系统,实例代码是在CentOS6.4 64位系统中编写的,在32位系统中需要将long改为int
2、对*((long*)*(long*)(&b))的理解,此处注意虚函数表中存放的是虚函数的指针:
&b: 对象b的地址,同时也是指向虚函数表的指针vptr的地址,他们只是值相同;
(long*)(&b): 其值仍为对象b的地址,同时也是指向虚函数表的指针vptr的地址,将Base类型的指针强制转换为long类型指针,对其进行强制类型转换成(long*)的目的是当对其执行间接访问操作时,可以访问到以该地址开始的连续8个字节的内存空间,即截取b对象占用内存空间的前8个字节,意在截取,对其解引用就是vptr的值,因为64位操作系统指针总是要占8个字节的。
*(long*)(&b): 本例中作为右值使用,其值为虚函数表的首地址即vptr,同时也是虚函数表中第一个函数指针的地址。当做左值使用时为指向虚函数表的指针vptr本身,直接打印时其值为十进制数。
(long*)*(long*)(&b): 其值仍为虚函数表的首地址,同时也是虚函数表中第一个函数指针的地址,将long类型十进制数强制转换为long类型指针形式,此时可以把vptr以指针形式打印出来,对其进行强制类型转换成(long*)的目的是当对其执行间接访问操作时,可以访问到以该地址开始的连续8个字节的内存空间,此处vptr共占内存为8字节,即全部截取vptr占用的内存空间,意在转换为指针。
*(long*)*(long*)(&b)作右值使用时为虚函数表中第一个函数指针,可以调用虚函数。
结合下图更好理解:
注意:在上面这个图中,我在虚函数表的最后多加了一个结点,这是虚函数表的结束结点,就像字符串的结束符“/0”一样,其标志了虚函数表的结束。这个结束标志的值在不同的编译器下是不同的。在WinXP+VS2003下,这个值是NULL。而在Ubuntu 7.10 + Linux 2.6.22 + GCC 4.1.3下,这个值是如果1,表示还有下一个虚函数表,如果值是0,表示是最后一个虚函数表。
三、单继承
下面,再让我们看看继承时虚函数表是什么样的。
1、无虚函数覆盖:
假设有如下所示的一个继承关系:
请注意,在这个继承关系中,子类没有重载任何父类的函数。那么,在派生类的实例中,其虚函数表如下所示:
对于实例:Derive d;的虚函数表如下:
我们可以看到下面几点:
1) 虚函数按照其声明顺序放于表中;
2) 父类的虚函数在子类的虚函数前面;
实例代码:
1 #include <iostream> 2 using namespace std; 3 4 class Base 5 { 6 public: 7 virtual void f() { cout << "Base::f" << endl; } 8 virtual void g() { cout << "Base::g" << endl; } 9 virtual void h() { cout << "Base::h" << endl; } 10 }; 11 12 class Derive: public Base 13 { 14 public: 15 virtual void f1() { cout << "Derive::f1" << endl; } 16 virtual void g1() { cout << "Derive::g1" << endl; } 17 virtual void h1() { cout << "Derive::h1" << endl; } 18 }; 19 20 typedef void (*Fun)(void); 21 22 int main(void) 23 { 24 Fun pfun = NULL; 25 Derive d; 26 27 pfun = (Fun)*((long*)*(long*)(&d)+0); // Base::f() 28 pfun(); 29 pfun = (Fun)*((long*)*(long*)(&d)+1); // Base::g() 30 pfun(); 31 pfun = (Fun)*((long*)*(long*)(&d)+2); // Base::h() 32 pfun(); 33 pfun = (Fun)*((long*)*(long*)(&d)+3); // Derive::f1() 34 pfun(); 35 pfun = (Fun)*((long*)*(long*)(&d)+4); // Derive::g1() 36 pfun(); 37 pfun = (Fun)*((long*)*(long*)(&d)+5); // Derive::h1() 38 pfun(); 39 40 return 0; 41 }
2、有虚函数覆盖:
覆盖父类的虚函数是很显然的事情,不然,虚函数变得毫无意义。下面我们来看一下,如果子类中有虚函数重写了父类的虚函数,会是一个什么样子?假设我们有下面这样的一个继承关系。
为了让大家看到被继承过后的效果,在这个类的设计中,我只覆盖了父类的一个函数:f()。那么,对于派生类的实例,其虚函数表会是下面的一个样子:
我们从表中可以看到下面几点:
1) 覆盖的f()函数被放到了虚函数表中原来父类虚函数的位置;
2) 没有被覆盖的函数依旧;
这样,我们就可以看到对于下面这样的程序,
Base *b = new Derive();
b->f();
由b所指的内存中的虚函数表的f()的位置已经被Derive::f()函数地址所取代,于是在实际调用发生时,是Derive::f()被调用了。这就实现了多态。
实例代码:
1 #include <iostream> 2 using namespace std; 3 4 class Base 5 { 6 public: 7 virtual void f() { cout << "Base::f" << endl; } 8 virtual void g() { cout << "Base::g" << endl; } 9 virtual void h() { cout << "Base::h" << endl; } 10 }; 11 12 class Derive: public Base 13 { 14 public: 15 virtual void f() { cout << "Derive::f" << endl; } 16 virtual void g1() { cout << "Derive::g1" << endl; } 17 virtual void h1() { cout << "Derive::h1" << endl; } 18 }; 19 20 typedef void (*Fun)(void); 21 22 int main(void) 23 { 24 Fun pfun = NULL; 25 Derive d; 26 27 pfun = (Fun)*((long*)*(long*)(&d)+0); // Derive::f() 28 pfun(); 29 pfun = (Fun)*((long*)*(long*)(&d)+1); // Base::g() 30 pfun(); 31 pfun = (Fun)*((long*)*(long*)(&d)+2); // Base::h() 32 pfun(); 33 pfun = (Fun)*((long*)*(long*)(&d)+3); // Derive::g1() 34 pfun(); 35 pfun = (Fun)*((long*)*(long*)(&d)+4); // Derive::h1() 36 pfun(); 37 38 return 0; 39 }
四、多重继承
1、无虚函数覆盖:
下面,再让我们来看看多重继承中的情况,假设有下面这样一个类的继承关系。注意:子类并没有覆盖父类的函数。
对于子类实例中的虚函数表,是下面这个样子:
我们可以看到:
1) 每个父类都有自己的虚表。
2) 子类的成员函数被放到了第一个父类的表中。(所谓的第一个父类是按照声明顺序来判断的)
这样做就是为了解决不同的父类类型的指针指向同一个子类实例,而能够调用到实际的函数。
实例代码:
1 #include <iostream> 2 using namespace std; 3 4 class Base1 5 { 6 public: 7 virtual void f() { cout << "Base1::f" << endl; } 8 virtual void g() { cout << "Base1::g" << endl; } 9 virtual void h() { cout << "Base1::h" << endl; } 10 }; 11 12 class Base2 13 { 14 public: 15 virtual void f() { cout << "Base2::f" << endl; } 16 virtual void g() { cout << "Base2::g" << endl; } 17 virtual void h() { cout << "Base2::h" << endl; } 18 }; 19 20 class Base3 21 { 22 public: 23 virtual void f() { cout << "Base3::f" << endl; } 24 virtual void g() { cout << "Base3::g" << endl; } 25 virtual void h() { cout << "Base3::h" << endl; } 26 }; 27 28 class Derive: public Base1, public Base2, public Base3 29 { 30 public: 31 virtual void f1() { cout << "Derive::f1" << endl; } 32 virtual void g1() { cout << "Derive::g1" << endl; } 33 virtual void h1() { cout << "Derive::h1" << endl; } 34 }; 35 36 typedef void (*Fun)(void); 37 38 int main(void) 39 { 40 Fun pfun = NULL; 41 Derive d; 42 43 pfun = (Fun)*((long*)*((long*)(&d)+0)+0); // Base1::f() 44 pfun(); 45 pfun = (Fun)*((long*)*((long*)(&d)+0)+1); // Base1::g() 46 pfun(); 47 pfun = (Fun)*((long*)*((long*)(&d)+0)+2); // Base1::h() 48 pfun(); 49 pfun = (Fun)*((long*)*((long*)(&d)+0)+3); // Derive::f1() 50 pfun(); 51 pfun = (Fun)*((long*)*((long*)(&d)+0)+4); // Derive::g1() 52 pfun(); 53 pfun = (Fun)*((long*)*((long*)(&d)+0)+5); // Derive::h1() 54 pfun(); 55 56 pfun = (Fun)*((long*)*((long*)(&d)+1)+0); // Base2::f() 57 pfun(); 58 pfun = (Fun)*((long*)*((long*)(&d)+1)+1); // Base2::g() 59 pfun(); 60 pfun = (Fun)*((long*)*((long*)(&d)+1)+2); // Base2::h() 61 pfun(); 62 63 pfun = (Fun)*((long*)*((long*)(&d)+2)+0); // Base3::f() 64 pfun(); 65 pfun = (Fun)*((long*)*((long*)(&d)+2)+1); // Base3::g() 66 pfun(); 67 pfun = (Fun)*((long*)*((long*)(&d)+2)+2); // Base3::h() 68 pfun(); 69 70 return 0; 71 }
2、有虚函数覆盖
下面我们再来看看,如果发生虚函数覆盖的情况。下图中,我们在子类中覆盖了父类的f()函数。
下面是对于子类实例中的虚函数表的图:
我们可以看见,三个父类虚函数表中的f()的位置被替换成了子类的函数指针。这样,我们就可以任一静态类型的父类来指向子类,并调用子类的f()了。如:
Derive d;
Base1 *b1 = &d;
Base2 *b2 = &d;
Base3 *b3 = &d;
b1->f(); //Derive::f()
b2->f(); //Derive::f()
b3->f(); //Derive::f()
b1->g(); //Base1::g()
b2->g(); //Base2::g()
b3->g(); //Base3::g()
实例代码:
1 #include <iostream> 2 using namespace std; 3 4 class Base1 5 { 6 public: 7 virtual void f() { cout << "Base1::f" << endl; } 8 virtual void g() { cout << "Base1::g" << endl; } 9 virtual void h() { cout << "Base1::h" << endl; } 10 }; 11 12 class Base2 13 { 14 public: 15 virtual void f() { cout << "Base2::f" << endl; } 16 virtual void g() { cout << "Base2::g" << endl; } 17 virtual void h() { cout << "Base2::h" << endl; } 18 }; 19 20 class Base3 21 { 22 public: 23 virtual void f() { cout << "Base3::f" << endl; } 24 virtual void g() { cout << "Base3::g" << endl; } 25 virtual void h() { cout << "Base3::h" << endl; } 26 }; 27 28 class Derive: public Base1, public Base2, public Base3 29 { 30 public: 31 void f() { cout << "Derive::f" << endl; } 32 virtual void g1() { cout << "Derive::g1" << endl; } 33 virtual void h1() { cout << "Derive::h1" << endl; } 34 }; 35 36 typedef void (*Fun)(void); 37 38 int main(void) 39 { 40 Fun pfun = NULL; 41 Derive d; 42 43 pfun = (Fun)*((long*)*((long*)(&d)+0)+0); // Derive::f() 44 pfun(); 45 pfun = (Fun)*((long*)*((long*)(&d)+0)+1); // Base1::g() 46 pfun(); 47 pfun = (Fun)*((long*)*((long*)(&d)+0)+2); // Base1::h() 48 pfun(); 49 pfun = (Fun)*((long*)*((long*)(&d)+0)+3); // Derive::g1() 50 pfun(); 51 pfun = (Fun)*((long*)*((long*)(&d)+0)+4); // Derive::h1() 52 pfun(); 53 54 pfun = (Fun)*((long*)*((long*)(&d)+1)+0); // Derive::f() 55 pfun(); 56 pfun = (Fun)*((long*)*((long*)(&d)+1)+1); // Base2::g() 57 pfun(); 58 pfun = (Fun)*((long*)*((long*)(&d)+1)+2); // Base2::h() 59 pfun(); 60 61 pfun = (Fun)*((long*)*((long*)(&d)+2)+0); // Derive::f() 62 pfun(); 63 pfun = (Fun)*((long*)*((long*)(&d)+2)+1); // Base3::g() 64 pfun(); 65 pfun = (Fun)*((long*)*((long*)(&d)+2)+2); // Base3::h() 66 pfun(); 67 68 return 0; 69 }
五、安全性
一、通过父类型的指针访问子类的虚函数
子类没有重写父类的虚函数是件毫无意义的事情。因为多态也是要基于函数重写的。任何妄图使用父类指针调用子类中未覆盖父类的成员函数的行为都会被编译器视为非法,所以,这样的程序无法编译通过。但在运行时我们可以通过指针的方式访问虚函数表来达到违反C++语义的行为。
二、访问non-public的虚函数
父类中private或protected的虚函数同样会存在于虚函数表中,所以我们同样可以使用方位虚函数表的方式来访问这些non-public的虚函数,但是使用基类或子类,
实例代码:
1 #include <iostream> 2 using namespace std; 3 4 class Base 5 { 6 private: 7 virtual void h() { cout << "Base::h" << endl; } 8 public: 9 virtual void f() { cout << "Base::f" << endl; } 10 protected: 11 virtual void g() { cout << "Base::g" << endl; } 12 }; 13 14 class Derive: public Base 15 { 16 public: 17 virtual void f() { cout << "Derive::f" << endl; } 18 protected: 19 virtual void g1() { cout << "Derive::g1" << endl; } 20 private: 21 virtual void h1() { cout << "Derive::h1" << endl; } 22 }; 23 24 typedef void (*pFun)(void); 25 26 int main(void) 27 { 28 pFun pfun = NULL; 29 Derive d; 30 // 通过访问虚函数表,在类外可以访问基类和子类中私有、保护类型的虚函数 31 pfun = (pFun)*((long*)*(long*)(&d)+0); // Base::h() 32 pfun(); 33 pfun = (pFun)*((long*)*(long*)(&d)+1); // Derive::f() 34 pfun(); 35 pfun = (pFun)*((long*)*(long*)(&d)+2); // Base::g() 36 pfun(); 37 pfun = (pFun)*((long*)*(long*)(&d)+3); // Derive::g1() 38 pfun(); 39 pfun = (pFun)*((long*)*(long*)(&d)+4); // Derive::h1() 40 pfun(); 41 42 Base *b = new Derive(); 43 // 基类指针无法访问子类中未覆盖父类的成员函数 44 b->g1(); // compile error: class Base has no member named g1 45 46 // 基类中的私有虚函数会被子类继承 47 d.h(); // compile error: virtual void Base::h() is private 48 d.g(); // compile error: virtual void Base::g() is protected 49 50 return 0; 51 }