C++虚函数表(vtable)和虚函数指针(vfptr)
2018-01-23 21:54
483 查看
编译器会构建一张虚表( vtable ),每一个类都有自己独特的虚表。同时,在这个继承链上,编译器会为基类插入一个隐式的指针(一般是对象的首地址),指向虚表,称为__vptr。然后,子类继承父类时,会获得继承下来的__vptr,再根据自己的类的情况兼容(修改虚函数表里的值、发生偏移等。于是,当我们构建具体的类时,若是基类类型,__vptr就会指向父类的vtable,若是子类类型,__vptr就会指向子类的vtable。
不同的类型,__vptr指向的vtable不同
父类的函数为virtual时,子类继承下来的这个函数也是虚函数,这被称为覆写。以前我们一般会推荐大家在子类也为这个函数标记为virtual,提醒我们这是虚函数,而在C++11中,我们更提倡使用override来提醒我们在覆写父类的虚函数,上面代码中,子类b继承父类a的__vptr,同时根据自己类的情况做出兼容,将_vptr所指向的虚函数表(vtable)覆盖,再在虚函数表的后面添加自己的虚函数(
则,会先获得指针的偏移量以保证vptr的正确性。
结果如下:
派生类虚表结构:
现在更改析构函数:基类虚析构函数修改成虚构函数,子类虚析构函数
则我们有:
结果如下:
派生类虚表结构:
更改析构函数:基类虚析构函数为虚虚构函数,子类虚析构函数改为析构函数
则我们有:
派生类虚表结构:
注意:
(int*)(&obj+1)和((int*)&obj+1)的区别是:前面表示的是地址按对象大小增加,后面表示的是一个对象地址按4个字节增加。即(int*)(&obj+1)的位置就是obj的下一个对象的首地址,而((int*)&obj+1)则是对象obj内vptr后面一个成员变量的地址
虚表中存放的是所有虚函数的首地址,存放顺序和(继承顺序、函数的声明顺序)有关。
当派生类中重载了基类中的函数时,(包括析构函数),这时候虚表中就只会有基类中的虚函数的地址,但是当调用派生类中重载的虚函数时,实现的是重载的功能。
有一个值得思考的地方是在子类虚函数中调用已经被override的父类虚函数:
我们知道,C++比C多了个作用域限定符::
所以,隐藏的不是很深,还是可以揪出来用的
全局函数,变量,类型,enum 常量 被隐藏,可以用 ::引用
名空间内 函数,变量,类型,enum 常量被隐藏,可以用 名空间名:: 引用
类作用域的函数,变量,类型,enum 常量 被隐藏,可以用 类名:: 引用
只有函数 和 函数内部的语句组作用域,名字被隐藏,无法引用
这里你会发现,其实虚函数就是是指这个函数的签名。
而 上面那个虚函数(
如果这个函数是虚函数,此时编译器会把其地址放在虚表中。
所以这个函数有两个入口。
第一个就是上面那样呼叫,和普通的成员函数一致。
第二个是通过引用呼叫,此时编译器会通过虚表来呼叫。
此时需要添加一个修正 this 指针的转换函数。
所以,
这种格式的意思是:不要去虚表找了,直接像普通成员函数一样直接调用基类虚函数。
纯虚函数的原理其实就是:纯虚函数在类的vftable表中对应的表项被赋值为0。也就是指向一个不存在的函数。由于编译器绝对不允许有调用一个不存在的函数的可能,所以该类不能生成对象。在它的派生类中,除非重写此函数,否则也不能生成对象。
纯虚函数的作用:
为了方便使用多态特性,我们常常需要在基类中定义虚拟函数。
在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。
也就是制定标准,统一接口。
如果一个基类中有多个纯虚函数,即基类为抽象类,那么派生类只有实现所有纯虚函数的override,子类才会是能够实例化的具体类,任何一个父类的纯虚函数没有实现override,则子类也为抽象类不能实例化。
不同的类型,__vptr指向的vtable不同
#include <iostream> using namespace std; class A { public: virtual void f() { cout << "A::f()" << endl; } virtual void g() { cout << "A::g()" << endl; } }; class B :public A { public: virtual void f() { cout << "B::f()" << endl; } virtual void h() { cout << "B::h()" << endl; } }; int main() { A a; B b; return 0; }
父类的函数为virtual时,子类继承下来的这个函数也是虚函数,这被称为覆写。以前我们一般会推荐大家在子类也为这个函数标记为virtual,提醒我们这是虚函数,而在C++11中,我们更提倡使用override来提醒我们在覆写父类的虚函数,上面代码中,子类b继承父类a的__vptr,同时根据自己类的情况做出兼容,将_vptr所指向的虚函数表(vtable)覆盖,再在虚函数表的后面添加自己的虚函数(
virtual void h()<
4000
/code>)。
如果是下面的情况,即子类的第二个虚函数override
了父类的第一个虚函数。#include <iostream> using namespace std; class A { public: virtual void g() { cout << "A::g()" << endl; } virtual void h() { cout << "A::h()" << endl; } }; class B :public A { public: virtual void f() { cout << "B::f()" << endl; } virtual void g() { cout << "B::g()" << endl; } }; int main() { A a; B b; return 0; }
则,会先获得指针的偏移量以保证vptr的正确性。
#include <iostream> using namespace std; class Test { public: Test(int a) { data = a; } virtual ~Test() { cout << "Test deconstruct" << endl; }//基类中的虚析构函数 virtual void fun11() { cout << "Test virtual fun11" << endl; }//基类中的虚函数fun11 virtual void fun12() { cout << "Test virtual fun12" << endl; }//基类中的虚函数fun12 int data; }; class Test1 :public Test { public: Test1(int d1, int d2) :Test(d2) { data1 = d1; data2 = d2; } int data1; int data2; virtual ~Test1() { cout << "test1 deconstruct" << endl; }//派生类中的虚析构函数 virtual void fun1() { cout << "test1 virtual fun1" << endl; }//派生类中的虚函数fun1,不是实现基类中的fun11的多态 virtual void fun2() { cout << "test1 virtual fun2" << endl; }//派生类中的虚函数fun2,不是实现基类中的fun12的多态 }; typedef void(*Fun)(void);//指向函数的指针 int main() { Test objt(10); //父类对象 Test1 obj(1, 2);//定义子类对象obj cout << "obj's Size = " << sizeof(obj) << endl; cout << "obj 's Address = " << &obj << endl; cout << "second Test1 object's address = " << &obj + 1 << endl;//为了测试(int*)(&obj+1)和((int*)&obj+1)的区别 cout << "third Test1 object's address = " << &obj + 2 << endl; cout << "base data address and data: " << (int*)&obj + 1 << "\t" << *((int*)&obj + 1) << endl; cout << "derivate data1 address and data1:" << (int*)&obj + 2 << "\t" << *((int*)&obj + 2) << endl; cout << "derivate data2 address and data2:" << (int*)&obj + 3 << "\t" << *((int*)&obj + 3) << endl; //获得虚表指针,显示虚表中的内容 cout << "vtable address = " << (int*)&obj << "\t" << "value = " << *((int*)&obj + 0) << endl; cout << "vtable value0 的地址 = " << ((int*)*(int*)((int*)&obj + 0) + 0) << endl; cout << "vtable value1 的地址 = " << ((int*)*(int*)((int*)&obj + 0) + 1) << endl; cout << "vtable value2 的地址 = " << ((int*)*(int*)((int*)&obj + 0) + 2) << endl; cout << "vtable value3 的地址 = " << ((int*)*(int*)((int*)&obj + 0) + 3) << endl; cout << "vtable value4 的地址 = " << ((int*)*(int*)((int*)&obj + 0) + 4) << endl; cout << "vtable value5 的地址 = " << ((int*)*(int*)((int*)&obj + 0) + 5) << endl; Fun pFun = NULL; pFun = (Fun)*((int*)*(int*)((int*)&obj + 0) + 1); pFun(); pFun = (Fun)*((int*)*(int*)((int*)&obj + 0) + 2); pFun(); pFun = (Fun)*((int*)*(int*)((int*)&obj + 0) + 3); pFun(); pFun = (Fun)*((int*)*(int*)((int*)&obj + 0) + 4); pFun(); return 0; }
结果如下:
派生类虚表结构:
vtable |
---|
virtual ~Test() |
virtual void fun11() |
virtual void fun12() |
virtual void fun1() |
virtual void fun2() |
则我们有:
#include <iostream> using namespace std; class Test { public: Test(int a) { data = a; } ~Test() { cout << "Test deconstruct" << endl; }//基类中的析构函数 virtual void fun11() { cout << "Test virtual fun11" << endl; }//基类中的虚函数fun11 virtual void fun12() { cout << "Test virtual fun12" << endl; }//基类中的虚函数fun12 int data; }; class Test1 :public Test { public: Test1(int d1, int d2) :Test(d2) { data1 = d1; data2 = d2; } int data1; int data2; virtual ~Test1() { cout << "test1 deconstruct" << endl; }//派生类中的虚析构函数 virtual void fun1() { cout << "test1 virtual fun1" << endl; }//派生类中的虚函数fun1,不是实现基类中的fun11的多态 virtual void fun2() { cout << "test1 virtual fun2" << endl; }//派生类中的虚函数fun2,不是实现基类中的fun12的多态 }; typedef void(*Fun)(void);//指向函数的指针 int main() { Test objt(10); Test1 obj(1, 2);//定义对象obj cout << "obj's Size = " << sizeof(obj) << endl; cout << "obj 's Address = " << &obj << endl; cout << "second Test1 object's address = " << &obj + 1 << endl;//为了测试(int*)(&obj+1)和((int*)&obj+1)的区别 cout << "third Test1 object's address = " << &obj + 2 << endl; cout << "base data address and data: " << (int*)&obj + 1 << "\t" << *((int*)&obj + 1) << endl; cout << "derivate data1 address and data1:" << (int*)&obj + 2 << "\t" << *((int*)&obj + 2) << endl; cout << "derivate data2 address and data2:" << (int*)&obj + 3 << "\t" << *((int*)&obj + 3) << endl; //获得虚表指针,显示虚表中的内容 cout << "vtable address = " << (int*)&obj << "\t" << "value = " << *((int*)&obj + 0) << endl; cout << "vtable value0 的地址 = " << ((int*)*(int*)((int*)&obj + 0) + 0) << endl; cout << "vtable value1 的地址 = " << ((int*)*(int*)((int*)&obj + 0) + 1) << endl; cout << "vtable value2 的地址 = " << ((int*)*(int*)((int*)&obj + 0) + 2) << endl; cout << "vtable value3 的地址 = " << ((int*)*(int*)((int*)&obj + 0) + 3) << endl; cout << "vtable value4 的地址 = " << ((int*)*(int*)((int*)&obj + 0) + 4) << endl; cout << "vtable value5 的地址 = " << ((int*)*(int*)((int*)&obj + 0) + 5) << endl; Fun pFun = NULL; pFun = (Fun)*((int*)*(int*)((int*)&obj + 0) + 0); pFun(); pFun = (Fun)*((int*)*(int*)((int*)&obj + 0) + 1); pFun(); pFun = (Fun)*((int*)*(int*)((int*)&obj + 0) + 3); pFun(); pFun = (Fun)*((int*)*(int*)((int*)&obj + 0) + 4); pFun(); return 0; }
结果如下:
派生类虚表结构:
vtable |
---|
virtual void fun11() |
virtual void fun12() |
virtual ~Test1() |
virtual void fun1() |
virtual void fun2() |
则我们有:
#include <iostream> using namespace std; class Test { public: Test(int a) { data = a; } virtual ~Test() { cout << "Test deconstruct" << endl; }//基类中的虚析构函数 virtual void fun11() { cout << "Test virtual fun11" << endl; }//基类中的虚函数fun11 virtual void fun12() { cout << "Test virtual fun12" << endl; }//基类中的虚函数fun12 int data; }; class Test1 :public Test { public: Test1(int d1, int d2) :Test(d2) { data1 = d1; data2 = d2; } int data1; int data2; ~Test1() { cout << "test1 deconstruct" << endl; }//派生类中的析构函数 virtual void fun1() { cout << "test1 virtual fun1" << endl; }//派生类中的虚函数fun1,不是实现基类中的fun11的多态 virtual void fun2() { cout << "test1 virtual fun2" << endl; }//派生类中的虚函数fun2,不是实现基类中的fun12的多态 }; typedef void(*Fun)(void);//指向函数的指针 int main() { Test objt(10); Test1 obj(1, 2);//定义对象obj cout << "obj's Size = " << sizeof(obj) << endl; cout << "obj 's Address = " << &obj << endl; cout << "second Test1 object's address = " << &obj + 1 << endl;//为了测试(int*)(&obj+1)和((int*)&obj+1)的区别 cout << "third Test1 object's address = " << &obj + 2 << endl; cout << "base data address and data: " << (int*)&obj + 1 << "\t" << *((int*)&obj + 1) << endl; cout << "derivate data1 address and data1:" << (int*)&obj + 2 << "\t" << *((int*)&obj + 2) << endl; cout << "derivate data2 address and data2:" << (int*)&obj + 3 << "\t" << *((int*)&obj + 3) << endl; //获得虚表指针,显示虚表中的内容 cout << "vtable address = " << (int*)&obj << "\t" << "value = " << *((int*)&obj + 0) << endl; cout << "vtable value0 的地址 = " << ((int*)*(int*)((int*)&obj + 0) + 0) << endl; cout << "vtable value1 的地址 = " << ((int*)*(int*)((int*)&obj + 0) + 1) << endl; cout << "vtable value2 的地址 = " << ((int*)*(int*)((int*)&obj + 0) + 2) << endl; cout << "vtable value3 的地址 = " << ((int*)*(int*)((int*)&obj + 0) + 3) << endl; cout << "vtable value4 的地址 = " << ((int*)*(int*)((int*)&obj + 0) + 4) << endl; cout << "vtable value5 的地址 = " << ((int*)*(int*)((int*)&obj + 0) + 5) << endl; Fun pFun = NULL; pFun = (Fun)*((int*)*(int*)((int*)&obj + 0) + 1); pFun(); pFun = (Fun)*((int*)*(int*)((int*)&obj + 0) + 2); pFun(); pFun = (Fun)*((int*)*(int*)((int*)&obj + 0) + 3); pFun(); pFun = (Fun)*((int*)*(int*)((int*)&obj + 0) + 4); pFun(); return 0; }
派生类虚表结构:
vtable |
---|
virtual ~Test() |
virtual void fun11() |
virtual void fun12() |
virtual void fun1() |
virtual void fun2() |
(int*)(&obj+1)和((int*)&obj+1)的区别是:前面表示的是地址按对象大小增加,后面表示的是一个对象地址按4个字节增加。即(int*)(&obj+1)的位置就是obj的下一个对象的首地址,而((int*)&obj+1)则是对象obj内vptr后面一个成员变量的地址
虚表中存放的是所有虚函数的首地址,存放顺序和(继承顺序、函数的声明顺序)有关。
当派生类中重载了基类中的函数时,(包括析构函数),这时候虚表中就只会有基类中的虚函数的地址,但是当调用派生类中重载的虚函数时,实现的是重载的功能。
有一个值得思考的地方是在子类虚函数中调用已经被override的父类虚函数:
virtual void v_func1() { base_class::v_func1(); cout << "This is dev_class's v_func1()" << endl; }
我们知道,C++比C多了个作用域限定符::
所以,隐藏的不是很深,还是可以揪出来用的
全局函数,变量,类型,enum 常量 被隐藏,可以用 ::引用
名空间内 函数,变量,类型,enum 常量被隐藏,可以用 名空间名:: 引用
类作用域的函数,变量,类型,enum 常量 被隐藏,可以用 类名:: 引用
只有函数 和 函数内部的语句组作用域,名字被隐藏,无法引用
这里你会发现,其实虚函数就是是指这个函数的签名。
而 上面那个虚函数(
base_class::v_func1();)在类中的实现不是,它是一个实实在在的成员函数,也就是和普通的函数一样的。
如果这个函数是虚函数,此时编译器会把其地址放在虚表中。
所以这个函数有两个入口。
第一个就是上面那样呼叫,和普通的成员函数一致。
第二个是通过引用呼叫,此时编译器会通过虚表来呼叫。
此时需要添加一个修正 this 指针的转换函数。
所以,
base_class::v_func1();
这种格式的意思是:不要去虚表找了,直接像普通成员函数一样直接调用基类虚函数。
纯虚函数的原理其实就是:纯虚函数在类的vftable表中对应的表项被赋值为0。也就是指向一个不存在的函数。由于编译器绝对不允许有调用一个不存在的函数的可能,所以该类不能生成对象。在它的派生类中,除非重写此函数,否则也不能生成对象。
纯虚函数的作用:
为了方便使用多态特性,我们常常需要在基类中定义虚拟函数。
在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。
也就是制定标准,统一接口。
如果一个基类中有多个纯虚函数,即基类为抽象类,那么派生类只有实现所有纯虚函数的override,子类才会是能够实例化的具体类,任何一个父类的纯虚函数没有实现override,则子类也为抽象类不能实例化。
相关文章推荐
- C++虚函数表(vtable)和虚函数指针(vfptr)
- C++多态、虚函数表、动态链接,虚函数指针,RTTI
- C++虚函数多继承的虚函数表指针的测试
- 虚函数与多态(一):虚函数表指针、虚析构函数、object slicing与虚函数、C++对象模型图
- 探究C++中的成员函数指针和虚函数表
- C++多态:虚函数、指针、虚函数表
- C++中的虚函数与虚函数表、虚函数表指针
- C++ vptr vtbl (C++虚表指针,虚函数表,虚函数的实现)很多人都喜欢问这个,写下吧。
- C++从入门到放弃之 函数指针
- C++成员函数指针
- C++调用成员函数需要this指针的情况
- C++指针作为函数的参数进行传递时注意的问题
- 从零开始学C++之虚函数与多态(一):虚函数表指针、虚析构函数、object slicing与虚函数、C++对象模型图
- C++中的函数指针
- C++多态机制中虚函数和指针或引用
- 关于C++中函数指针的使用(包含对typedef用法的讨论)
- C++模板编程->成员函数指针模板参数
- 解析C++中的字符串处理函数和指针
- 学习札记: C++指向函数的指针
- C++学习笔记之函数指针