《C++新经典对象模型》之第3章 虚函数

130 篇文章 4 订阅
92 篇文章 18 订阅

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;
}
  • 14
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值