《C++新经典对象模型》之第3章 虚函数
3.1 虚函数表指针位置分析
类有虚函数,就会产生虚函数表。
该类对象就会存在虚函数表指针,保存虚函数表的起始地址。
虚函数表指针位于对象内存的开头。
03.01.cpp
#include <iostream>
using namespace std;
class A
{
public:
int i;
virtual void testfunc() {}
};
int main()
{
A a;
char *p1 = reinterpret_cast<char *>(&a); // 0x006ff730 类型转换,这属于硬转,a是对象首地址
char *p2 = reinterpret_cast<char *>(&(a.i)); // 0x006ff734
if (p1 == p2)
cout << "Virtual function table pointer located at the end of object memory" << endl;
else
cout << "Virtual function table pointer located at the start of object memory" << endl; // 本条件会成立
cout << "Over!\n";
return 0;
}
3.2 继承关系作用下虚函数的手工调用
通过虚函数表指针来调用虚函数。
父类与子类的虚函数表不同,内部保存的虚函数指针有差异(子类覆盖父类)。
03.02.cpp
#include <cstdio>
#include <iostream>
using namespace std;
class Base
{
public:
virtual void f()
{
cout << "Base::f()" << endl;
}
virtual void g()
{
cout << "Base::g()" << endl;
}
virtual void h()
{
cout << "Base::h()" << endl;
}
};
class Derive : public Base
{
public:
void g()
{
cout << "Derive::g()" << endl;
}
};
int main()
{
cout << sizeof(Base) << endl; // 8字节,虚函数表指针,x64平台
cout << sizeof(Derive) << endl; // 8字节
Derive *d = new Derive(); // 类指针
// Base *d = new Derive();//等价
long ***pvptr = (long ***)d; // 类指针转为虚函数表指针(三级指针)
long **vptr = *pvptr; // 虚函数表指针取值得到虚函数表(二级指针)
for (int i = 0; vptr[i] != nullptr; i++)
printf("vptr[%d] = 0x:%p\n", i, vptr[i]); // 虚函数地址(一级指针)
typedef void (*Func)(void); // 定义Func是函数指针类型
Func f = (Func)vptr[0]; // 函数指针变量
f(); // Base::f()
Func g = (Func)vptr[1];
g(); // Derive::g() 子类覆盖父类的虚函数
Func h = (Func)vptr[2];
h(); // Base::h()
Func i = (Func)vptr[3];
// i(); // 运行异常
//---------------------------
Base *dpar = new Base();
long ***pvptrpar = (long ***)dpar;
long **vptrpar = *pvptrpar;
for (int i = 0; vptrpar[i] != nullptr; i++)
printf("vptr_Base [%d] = 0x:%p\n", i, vptrpar[i]);
Func fpar = (Func)vptrpar[0]; // project4.exe!Base::f(void)}
Func gpar = (Func)vptrpar[1]; // project4.exe!Base::g(void)}
Func hpar = (Func)vptrpar[2]; // project4.exe!Base::h(void)}
Func ipar = (Func)vptrpar[3];
fpar(); // base::f
gpar(); // base::g
hpar(); // base::h
//ipar(); //运行异常
cout << "Over!\n";
return 0;
}
3.3 虚函数表分析
(1)含虚函数的类才有虚函数表(二级指针,内部多个虚函数【一级函数指针】编译时已经固定,子类覆盖父类),各个类对象(实例)有各自的vptr(虚函数表指针,三级指针),地址不同,但都指向类的虚函数表。
(2)父类有虚函数则子类也有虚函数。
(3)子类无新的虚函数时,则与父类的虚函数表的内容相同,虚函数表的地址不同。
(4)超出虚函数表部分内容不可知也不可预测,不一定为nullptr。
Base base = drive;
//生成base对象
//用derive初始化base对象的值
//derive初始化base对象时,derive的虚函数表指针值未覆盖base对象的虚函数表指针值
面向对象模型——OOM(Object-Oriented Model),通过类对象的指针和引用来支持多态。
基于对象——OB(Object-Based),抽象数据模型ADT(Abstract Datatype Model),不支持多态,运行更快,函数调用编译时就解析完成,内存空间更紧凑。
03.03.cpp
#include <cstdio>
#include <iostream>
using namespace std;
class Base
{
public:
virtual void f()
{
cout << "Base::f()" << endl;
}
virtual void g()
{
cout << "Base::g()" << endl;
}
virtual void h()
{
cout << "Base::h()" << endl;
}
};
class Derive : public Base
{
public:
void g()
{
cout << "Derive::g()" << endl;
}
};
int main()
{
cout << sizeof(Base) << endl; // 8字节,虚函数表指针,x64平台
cout << sizeof(Derive) << endl; // 8字节
Derive *d = new Derive(); // 类指针
// Base *d = new Derive();//等价
long ***pvptr = (long ***)d; // 类指针转为虚函数表指针(三级指针)
long **vptr = *pvptr; // 虚函数表指针取值得到虚函数表(二级指针)
for (int i = 0; vptr[i] != nullptr; i++)
printf("vptr[%d] = 0x:%p\n", i, vptr[i]); // 虚函数地址(一级指针)
typedef void (*Func)(void); // 定义Func是函数指针类型
Func f = (Func)vptr[0]; // 函数指针变量
f(); // Base::f()
Func g = (Func)vptr[1];
g(); // Derive::g() 子类覆盖父类的虚函数
Func h = (Func)vptr[2];
h(); // Base::h()
Func i = (Func)vptr[3];
// i(); // 运行异常
//---------------------------
Base *dpar = new Base();
long ***pvptrpar = (long ***)dpar;
long **vptrpar = *pvptrpar;
for (int i = 0; vptrpar[i] != nullptr; i++)
printf("vptr_Base [%d] = 0x:%p\n", i, vptrpar[i]);
Func fpar = (Func)vptrpar[0]; // project4.exe!Base::f(void)}
Func gpar = (Func)vptrpar[1]; // project4.exe!Base::g(void)}
Func hpar = (Func)vptrpar[2]; // project4.exe!Base::h(void)}
Func ipar = (Func)vptrpar[3];
fpar(); // base::f
gpar(); // base::g
hpar(); // base::h
//ipar(); //运行异常
cout << "Over!\n";
return 0;
}
3.4 多重继承虚函数表分析
(1)虚函数表跟着类,虚函数表指针跟着对象。
(2)类有多个含虚函数的父类,则该对象有多个虚函数表指针(类最多只有一个虚函数表)。
(3)虚函数表指针按继承顺序放置在内存空间中,且子类与第一个父类共用同一个虚函数表指针。
(4)子类虚函数覆盖父类同名虚函数。
03.04.cpp
#include <stdio.h>
#include <iostream>
using namespace std;
// 父类1
class Base1
{
public:
virtual void f()
{
cout << "base1::f()" << endl;
}
virtual void g()
{
cout << "base1::g()" << endl;
}
};
// 父类2
class Base2
{
public:
virtual void h()
{
cout << "base2::h()" << endl;
}
virtual void i()
{
cout << "base2::i()" << endl;
}
};
// 子类
class Derived : public Base1, public Base2
{
public:
virtual void f()
{
cout << "derived::f()" << endl; // 只覆盖base1的f
}
virtual void i()
{
cout << "derived::i()" << endl; // 只覆盖base2的i
}
// 如下三个是本类自己的虚函数
virtual void mh()
{
cout << "derived::mh()" << endl;
}
virtual void mi()
{
cout << "derived::mi()" << endl;
}
virtual void mj()
{
cout << "derived::mj()" << endl;
}
};
int main()
{
cout << sizeof(Base1) << endl; // 8
cout << sizeof(Base2) << endl; // 8
cout << sizeof(Derived) << endl; // 16字节,两个虚函数表指针(2个vptr)
Derived ins; // 定义一个子类对象
Base1 &b1 = ins;
Base2 &b2 = ins;
Derived &d = ins;
b1.f(); // derived::f(),父类引用,但引用的是子类,所以这里执行子类所覆盖的父类的虚函数
b2.i(); // dervied::i(),父类引用,但引用的是子类,所以这里执行子类所覆盖的父类的虚函数
d.f(); // derived::f()
d.i(); // derived::i()
d.mh(); // derived::mh()
cout << "********************\n";
long ***pderived1 = (long ***)(&ins);
long **vptr1 = pderived1[0]; // 第一个虚函数表指针,根据继承顺序,和base1对应
long **vptr2 = pderived1[1]; // 往后走8个字节, 第二个虚函数表指针
typedef void (*Func)(void);
// 第一个父类虚函数(子类重写则覆盖)+子类虚函数
Func f1 = (Func)vptr1[0]; // 0x00f31550 {project100.exe!Derived::f(void)}
f1();
Func f2 = (Func)vptr1[1]; // 0x00f31596 {project100.exe!Base1::g(void)}
f2();
Func f3 = (Func)vptr1[2]; // 0x00f31578 {project100.exe!Derived::mh(void)}
f3();
Func f4 = (Func)vptr1[3]; // 0x00f31582 {project100.exe!Derived::mi(void)}
f4();
Func f5 = (Func)vptr1[4]; // 0x00f3157d {project100.exe!Derived::mj(void)}
f5();
Func f6 = (Func)vptr1[5]; // 0x0029aa64 {project100.exe!const Derived::`RTTI Complete Object Locator'{for `Base2'}}
f6();
Func f7 = (Func)vptr1[6];
// f7(); //error
// 第二个父类虚函数(子类重写则覆盖)
Func f11 = (Func)vptr2[0]; // 0x00291587 {project100.exe!Base2::h(void)}
f11();
Func f22 = (Func)vptr2[1]; // 0x00291591 {project100.exe!Derived::i(void)}
f22();
Func f33 = (Func)vptr2[2]; // 非法
// f33(); // error
cout << "Over!\n";
return 0;
}
3.5 辅助工具与vptr、vtbl创建时机
3.5.1 使用辅助工具查看虚函数表
//打出MyProject.cpp文件中,类Derived的内存布局
cl /d1 reportSingleClassLayoutDerived MyProject.cpp
//生成.class文件,里面有类的布局信息
g++ -fdump-class-hierarchy -fsyntax-only MyProject.cpp
3.5.2 虚函数表的创建时机
(1)编译器在编译期间(不是运行期间),为每个含虚函数的类确定好了对应的虚函数表vtbl的内容。
(2)同样在编译期间在类构造函数中添加给虚函数表指针vptr赋值的语句,这样运行时,生成类对象,调用构造函数时,会为vptr赋值。
(3)可执行程序装载到内存后,会分配各种内存空间。代码段、数据段、堆区、栈区。
Derived *obj = new Derived();
obj指针在栈上,指向一个堆区(Derived实例),该实例中的虚函数表指针在执行类构造函数后,指向数据段中的该类的虚函数表,而虚函数表中每一项指向代码段中的虚函数。
3.6 单纯的类不纯时引发的虚函数调用问题
单纯类
class X0
{
public:
int x;
int y;
int z;
X0()
{
memset(this, 0, sizeof(X));
cout << "X0:X0()" << endl;
}
X0(const X0 &tm)
{
memcpy(this, &tm, sizeof(X));
cout << "X0:X0(const X&)" << endl;
}
};
X0 x0;
x0.x = 100;
x0.y = 200;
x0.z = 300;
X0 x1(x0);
cout << "x1.x=" << x1.x << ", x1.y=" << x1.y << ", x1.z=" << x1.z << endl;
不单纯类,含有隐藏的成员变量,它的赋值往往在执行构造或者拷贝构造函数体之前。
构造函数中,memset时,隐藏成员变量值也会清零。
拷贝构造函数中,memset时,隐藏成员变量值会一致(实际可能应该不一致)。
//无继承关系时,虚函数和普通函数没有区别
//虚函数和函数一样,跟着类走
class X
{
public:
int x;
int y;
int z;
/*X() : x(0), y(0), z(0)
{
cout << "X:X()" << endl;
}*/
X()
{
memset(this, 0, sizeof(X));
cout << "X:X()" << endl;
}
/*X(const X &tm) : x(tm.x), y(tm.y), z(tm.z)
{
cout << "X:X(const X&)" << endl;
}*/
X(const X &tm)
{
memcpy(this, &tm, sizeof(X));
cout << "X:X(const X&)" << endl;
}
public:
virtual ~X()
{
cout << "X:virtual ~X()" << endl;
}
virtual void virfunc()
{
cout << "virtual virfunc()" << endl;
}
void ptfunc()
{
cout << "ptfunc()" << endl;
}
};
{
X x0;
x0.x = 100;
x0.y = 200;
x0.z = 300;
X x1(x0);
cout << "x1.x=" << x1.x << ", x1.y=" << x1.y << ", x1.z=" << x1.z << endl;
x0.virfunc();
}
{
X *px0 = new X();
px0->ptfunc(); // 可以正常调用普通该函数
// px0->virfunc(); // 无法正常调用该虚函数
// delete px0; // 无法正常调用析构函数
}
{
int i = 9;
printf("&i = %p\n", &i); // 会发现每次i的地址输出出来都不一样
//普通函数和虚函数地址编译时已经确定,不会变化
printf("&X::ptfunc = %p\n", &X::ptfunc); // 正常函数地址可以这样输出
printf("&X::virfunc = %p\n", &X::virfunc); // 正常函数地址可以这样输出
X x0;
long ***pvptr = (long ***)(&x0);
long **vptr = *pvptr;
printf("vptr[1] = %p\n", vptr);
x0.ptfunc(); //
x0.virfunc(); // 虚函数是可以正常调用的(编译时已固定地址,绑定,未使用虚函数表)
}
{
X *pX0 = new X();
pX0->ptfunc(); //直接调用,静态联编
// pX0->virfunc();//error,通过虚函数表查找调用
X x1;
X &x1y = x1; // 引用
x1y.virfunc(); //
}
(1)静态联编:编译时就确定调用的函数,调用语句和调用函数绑定在一起。
(2)动态联编:程序运行时,绑定调用语句和调用函数,多态和虚函数情况下存在。
03.06.cpp
#include <cstring>
#include <iostream>
using namespace std;
class X0
{
public:
int x;
int y;
int z;
X0()
{
memset(this, 0, sizeof(X0));
cout << "X0:X0()" << endl;
}
X0(const X0 &tm)
{
memcpy(this, &tm, sizeof(X0));
cout << "X0:X0(const X0&)" << endl;
}
};
class X
{
public:
int x;
int y;
int z;
/*X() : x(0), y(0), z(0)
{
cout << "X:X()" << endl;
}*/
X()
{
memset(this, 0, sizeof(X));
cout << "X:X()" << endl;
}
/*X(const X &tm) : x(tm.x), y(tm.y), z(tm.z)
{
cout << "X:X(const X&)" << endl;
}*/
X(const X &tm)
{
memcpy(this, &tm, sizeof(X));
cout << "X:X(const X&)" << endl;
}
public:
virtual ~X()
{
cout << "X:virtual ~X()" << endl;
}
virtual void virfunc()
{
cout << "virtual virfunc()" << endl;
}
void ptfunc()
{
cout << "ptfunc()" << endl;
}
};
int main()
{
{
X0 x0;
x0.x = 100;
x0.y = 200;
x0.z = 300;
X0 x1(x0);
cout << "x1.x=" << x1.x << ", x1.y=" << x1.y << ", x1.z=" << x1.z << endl;
}
{
X x0;
x0.x = 100;
x0.y = 200;
x0.z = 300;
X x1(x0);
cout << "x1.x=" << x1.x << ", x1.y=" << x1.y << ", x1.z=" << x1.z << endl;
x0.virfunc();
}
{
X *px0 = new X();
px0->ptfunc(); // 可以正常调用普通该函数
// px0->virfunc(); // 无法正常调用该虚函数
// delete px0; // 无法正常调用析构函数
}
{
int i = 9;
printf("&i = %p\n", &i); // 会发现每次i的地址输出出来都不一样
printf("&X::ptfunc = %p\n", &X::ptfunc); // 正常函数地址可以这样输出
printf("&X::virfunc = %p\n", &X::virfunc); // 正常函数地址可以这样输出
X x0;
long ***pvptr = (long ***)(&x0);
long **vptr = *pvptr;
printf("vptr[1] = %p\n", vptr);
x0.ptfunc();
x0.virfunc(); // 虚函数是可以正常调用的
}
{
X *pX0 = new X();
pX0->ptfunc();
// pX0->virfunc();//error
X x1;
X &x1y = x1; // 引用
x1y.virfunc(); //
}
cout << "Over!\n";
return 0;
}