《C++新经典对象模型》之第5章 函数语义学

《C++新经典对象模型》之第5章 函数语义学

5.1 普通成员函数调用方式

成员函数与全局函数一样,有独立的内存地址,编译时就确定好了。
编译器内部会将对成员函数的调用转换成一种对全局函数的调用。

struct MYACLS
{
	int m_i;
	void myfunc(int abc)
	{
		m_i += abc;
	}
};

//编译器视角
	void myfunc(MYACLS *const this, int abc)
	{
		this->m_i += abc;
	}

编译器会对成员函数名进行适当的处理。

05.01.cpp
#include <iostream>
#include <time.h>
#include <cstdio>
using namespace std;

struct MYACLS
{
	int m_i;
	void myfunc(int abc)
	{
		m_i += abc;
	}
};

// 全局函数gmyfunc
void gmyfunc(MYACLS *ptmp, int abc)
{
	ptmp->m_i += abc;
}

int main()
{
	{
		MYACLS myacls;
		myacls.myfunc(18);	  // 调用成员函数
		gmyfunc(&myacls, 18); // 调用全局函数
		printf("MYACLS::myfunc地址=%p\n", &MYACLS::myfunc);
	}

	cout << "Over!\n";
	return 0;
}

5.2 虚成员函数与静态成员函数调用方式

5.2.1 虚成员函数调用方式


struct MYACLS
{
	virtual void myvirfunc()
	{
		printf("myvirfunc()被调用,this = %p\n", this);
		// myvirfunc2();//通过虚函数表
		MYACLS::myvirfunc2();//直接调用,压制住了虚拟机制
	}
	virtual void myvirfunc2()
	{
		printf("myvirfunc2()被调用,this = %p\n", this);
	}
};

		MYACLS  myacls;
		myacls.myvirfunc();//普通成员函数调用,非多态
		
		MYACLS* pmyacls = new MYACLS;
		pmyacls->myvirfunc();//虚函数表指针找到虚函数表,然后找到虚函数地址,调用
		//等价于(*pmyacls->vptr[0])(pmyacls);
		delete pmyacls;

5.2.2 静态成员函数调用方式

类名,对象名或者对象指针来调用静态成员函数,都一样,转换为普通成员函数调用,且不会插入this形参。

struct MYACLS
{
	int m_i;
	void myfunc(int abc)
	{
		// m_i += abc;
		mystfunc(); // 这绝对没有问题
	}
	virtual void myvirfunc()
	{
		printf("myvirfunc()被调用,this = %p\n", this);
		// myvirfunc2();
		MYACLS::myvirfunc2();
	}
	virtual void myvirfunc2()
	{
		printf("myvirfunc2()被调用,this = %p\n", this);
	}
	static void mystfunc()
	{
		printf("mystfunc()被调用\n");
	}
};
		MYACLS myacls;
		myacls.mystfunc();
		
		MYACLS *pmyacls = new MYACLS;
		pmyacls->mystfunc();
		
		((MYACLS *)0)->mystfunc(); // 这样调用静态成员函数没问题
		((MYACLS *)0)->myfunc(12);//未使用12可以这样调用
		
		printf("MYACLS::mystfunc()地址 = %p\n", MYACLS::mystfunc);

静态成员函数特性:
(1)无this指针。
(2)无法存取类中普通的非静态成员变量。
(3)末尾不能增加const,也不能设置virtual。
(4)可用类对象来调用。
(5)等同于全局函数,可作为回调函数。

05.02.cpp
#include <iostream>
#include <time.h>
#include <cstdio>
using namespace std;

struct MYACLS
{
public:
	int m_i;
	void myfunc(int abc)
	{
		// m_i += abc;
		mystfunc(); // 这绝对没有问题
	}
	virtual void myvirfunc()
	{
		printf("myvirfunc()被调用,this = %p\n", this);
		// myvirfunc2();
		MYACLS::myvirfunc2();
	}
	virtual void myvirfunc2()
	{
		printf("myvirfunc2()被调用,this = %p\n", this);
	}
	static void mystfunc()
	{
		printf("mystfunc()被调用\n");
	}
};

// 全局函数gmyfunc
void gmyfunc(MYACLS *ptmp, int abc)
{
	ptmp->m_i += abc;
}

int main()
{
	{
		MYACLS myacls;
		myacls.myvirfunc();

		MYACLS *pmyacls = new MYACLS;
		pmyacls->myvirfunc();
		delete pmyacls;
	}

	{
		MYACLS myacls;
		myacls.mystfunc();

		MYACLS *pmyacls = new MYACLS;
		pmyacls->mystfunc();

		((MYACLS *)nullptr)->mystfunc(); // 这样调用静态成员函数没问题
		((MYACLS *)0)->myfunc(12);

		printf("MYACLS::mystfunc()地址 = %p\n", MYACLS::mystfunc);
	}

	cout << "Over!\n";
	return 0;
}

5.3 虚函数地址问题的vcall引入

虚函数在内存中也有固定地址,编译时已确定。

class MYACLS
{
public:
	virtual void myvirfunc1()
	{
		cout << "虚函数MYACLS::myvirfunc1()执行了" << endl;
	}
	virtual void myvirfunc2()
	{
		cout << "虚函数MYACLS::myvirfunc2()执行了" << endl;
	}
};
//msvc编译器通过一系列的vcall函数(唯一对应虚函数),跳转到真正的虚函数去。
//输出vacll函数地址,不是真正的虚函数地址
		printf("MYACLS::myvirfunc()地址 = %p\n", &MYACLS::myvirfunc1);
		printf("MYACLS::myvirfunc()地址 = %p\n", &MYACLS::myvirfunc2);
		cout << sizeof(MYACLS) << endl; // 8字节

vcall thunk,一段代码或者一段函数,两个作用:
(1)调整this指针。
(2)跳到真正的虚函数去。

05.03.cpp
#include <iostream>
#include <time.h>
#include <cstdio>
using namespace std;

class MYACLS
{
public:
	virtual void myvirfunc1()
	{
		cout << "虚函数MYACLS::myvirfunc1()执行了" << endl;
	}
	virtual void myvirfunc2()
	{
		cout << "虚函数MYACLS::myvirfunc2()执行了" << endl;
	}
};

int main()
{
	{
		printf("MYACLS::myvirfunc()地址 = %p\n", &MYACLS::myvirfunc1);
		printf("MYACLS::myvirfunc()地址 = %p\n", &MYACLS::myvirfunc2);
		cout << sizeof(MYACLS) << endl; // 8字节
		
		MYACLS *pmyobj = new MYACLS();
		pmyobj->myvirfunc1();
		pmyobj->myvirfunc2();
		delete pmyobj;
	}

	cout << "Over!\n";
	return 0;
}

5.4 静动态类型、绑定、坑点与多态体现深谈

5.4.1 静态类型和动态类型

静态类型:对象定义时的类型,编译期间就确定了。

Base base;	//静态类型Base
Derive derive;	//静态类型Derive 
Base *pbase;	//无论指向,定义时已确定,静态类型Base*  
Base *pbase2 = new Derive();//静态类型Base*
Base *pbase3 = new Derive2();//静态类型Base*

动态类型:对象目前指向的类型(运行时决定)。只有指针或引用才有动态类型,通常值父类的指针或引用。

  • base和derive无动态类型,非指针非引用。
  • pbase无动态类型,没有指向对象。
  • pbase2动态类型Derive。
  • pbase3动态类型Derive2.

动态类型执行过程中可改变。

pbase = pbase2; //pbase动态类型Derive
pbase = pbase3; //pbase动态类型Derive2

5.4.2 静态绑定和动态绑定

静态绑定:绑定的是静态类型,所对应的函数或属性依赖于对象的静态类型,发生在编译期。
动态绑定:绑定的是动态类型,所对应的函数或属性依赖于对象的动态类型,发生在运行期。
(1)普通成员函数是静态绑定,而虚函数是动态绑定。
(2)缺省函数一般是静态绑定。

5.4.3 继承的非虚函数坑

class Base
{
public:
	void myfunc()
	{
		cout << "Base::myfunc()" << endl;
	}
};
class Derive : public Base
{
public:
	void myfunc()
	{
		cout << "Derive::myfunc()" << endl;
	}
};
		Derive derive;
		Derive *pderive = &derive;
		pderive->myfunc(); // Derive::myfunc()

		Base *pbase = &derive;
		pbase->myfunc(); // Base::myfunc(),这里调用的居然是父类的myfunc,是个陷阱,写程序时一定要注意

普通成员函数是静态绑定,具体执行哪个myfunc成员函数取决于调用者的静态类型。
不应该在子类中重新定义一个继承来的非虚成员函数。

5.4.4 虚函数的动态绑定

class Base
{
public:
	virtual void myvirfunc()
	{
		cout << "Base::myvirfunc()" << endl;
	}
};
class Derive : public Base
{
public:
	virtual void myvirfunc()
	{
		cout << "Derive::myvirfunc()" << endl;
	}
};
		Base base;
		Derive derive;
		
		Derive *pderive = &derive;
		pderive->myvirfunc(); // Derive::myvirfunc()
		
		Base *pbase = &derive;
		pbase->myvirfunc();	  // Derive::myvirfunc()
		
		pbase = &base;
		pbase->myvirfunc(); // Base::myvirfunc()

虚函数是动态绑定,myvirfunc具体执行取决于调用者的动态类型。

5.4.5 重新定义虚函数的缺省参数坑

class Base
{
public:
	virtual void myvirfunc(int value = 1)
	{
		cout << "Base::myvirfunc(), value=" << value << endl;
	}
};
class Derive : public Base
{
public:
	virtual void myvirfunc(int value = 2)
	{
		cout << "Derive::myvirfunc(), value=" << value << endl;
	}
};
		Base base;
		Derive derive;
		
		Derive *pderive = &derive;
		pderive->myvirfunc(); // Derive::myvirfunc(), value=2
		
		Base *pbase = &derive;
		pbase->myvirfunc();	  // Derive::myvirfunc(), value=1
		
		pbase = &base;
		pbase->myvirfunc(); // Base::myvirfunc(), value=1

缺省参数是静态绑定,考虑执行期间效率问题,好实现。
不要在子类中重新定义虚函数缺省参数的值。

5.4.6 C++中的多态性

多态必须存在虚函数且调用虚函数。
(1)代码实现上
调用虚函数时,通过查询虚函数表找到虚函数入口地址,然后去执行的路线,就是多态。无论是否继承,有无派生。

class A
{
public:
	virtual void myvirfunc(){}
};
A *pa=new A();
pa->myvirfunc();//多态

A a;
a.myvirfunc();//非多态

A *ya=&a;
ya->myvirfunc();//多态

(2)表现形式上(最终也通过代码体现)

  • 有父类和子类(继承关系),父类必须含有虚函数,子类重写父类虚函数。
  • 父类指针指向子类对象或者父类引用绑定(指向)子类对象。
  • 父类指针或引用调用子类重写的虚函数。
		Derive derive;
		Base *pbase = &derive;
		pbase->myvirfunc();	  // Derive::myvirfunc()
		
		Base *pbase2 = new Derive();
		pbase2->myvirfunc();	// Derive::myvirfunc()
		delete pbase2;
		
		Derive derive2;
		Base &yinbase = derive2;
		yinbase.myvirfunc(); // Derive::myvirfunc()
05.04.cpp
#include <iostream>
#include <time.h>
#include <cstdio>
using namespace std;

class Base
{
public:
	void myfunc()
	{
		cout << "Base::myfunc()" << endl;
	}
	/*virtual void myvirfunc()
	{
		cout << "Base::myvirfunc()" << endl;
	}*/
	virtual void myvirfunc(int value = 1)
	{
		cout << "Base::myvirfunc(),value=" << value << endl;
	}
};
class Derive : public Base
{
public:
	void myfunc()
	{
		cout << "Derive::myfunc()" << endl;
	}
	// virtual void myvirfunc()
	//{
	//	cout << "Derive::myvirfunc()" << endl;
	// }
	virtual void myvirfunc(int value = 2)
	{
		cout << "Derive::myvirfunc(),value=" << value << endl;
	}
};
class Derive2 : public Base
{
public:
};

class A
{
public:
	virtual void myvirfunc() {}
};

int main()
{
	//{
	//	Base base;   //静态类型是Base
	//	Derive derive; //静态类型是Derive
	//	Base* pbase;  //别管指向啥,反正定义的时候定义的是Base *,所以静态类型是Base *
	//	Base* pbase2 = new Derive();//静态类型依旧是Base *
	//	Base* pbase3 = new Derive2();//静态类型依旧是Base *
	//}

	{
		Derive derive;
		Derive *pderive = &derive;
		pderive->myfunc(); // Derive::myfunc()

		Base *pbase = &derive;
		pbase->myfunc(); // Base::myfunc(),这里调用的居然是父类的myfunc,是个陷阱,写程序时一定要注意

		Base base;
		pderive->myvirfunc(); // Derive::myvirfunc()
		pbase->myvirfunc();	  // Derive::myvirfunc()
		pbase = &base;
		pbase->myvirfunc(); // Base::myvirfunc()
	}

	{
		A *pa = new A();
		pa->myvirfunc(); // 这是不是多态,是多态的

		A a;
		a.myvirfunc(); // 这个就不是多态的

		A *ya = &a;
		ya->myvirfunc(); // 这个也是多态
	}
	{
		Base *pbase2 = new Derive(); // 释放内存请读者自行释放,笔者在这里没演示
		pbase2->myvirfunc();		 // Derive::myvirfunc()
	}

	{
		Derive derive2;
		Base &yinbase = derive2;
		yinbase.myvirfunc(); // Derive::myvirfunc()
	}

	cout << "Over!\n";
	return 0;
}

5.5 单继承虚函数趣味性测试和回顾

5.5.1 单继承下的虚函数

子类虚函数表项与父类虚函数表项排列顺序一致。执行期间,调用虚函数只需要知道父类或者子类的虚函数表。

struct Base
{
	virtual ~Base() {}
	virtual void f() { cout << "Base::f()" << endl; }
	virtual void g() { cout << "Base::g()" << endl; }
	virtual void h() { cout << "Base::h()" << endl; }
};

struct Derive :  Base
{
	virtual void i() { cout << "Derive::i()" << endl; }
	virtual void g() { cout << "Derive::g()" << endl; }
	void myselffunc() { cout << "Derive::myselffunc()" << endl; } // 只属于Derive的函数
};
{
		Derive myderive;
		Derive* pmyderive = &myderive;
		//注意下列虚函数的调用顺序
		pmyderive->f();
		pmyderive->g();
		pmyderive->h();
		pmyderive->i();
		pmyderive->myselffunc();
}
{
		Base *pb = new Derive(); // 基类指针指向一个子类对象
		pb->g(); //(*pb->vptr[1])(pb);编译器编译成虚函数表指针调用
		delete pb;

		Derive myderive;
		Base &yb = myderive; // 基类引用引用的是一个子类对象
		yb.g();
}

5.5.2 回顾和一些小测试

虚函数地址编译期间确定,固定写在可执行文件中。
虚函数表编译期间构建。
虚函数表中,顺序记录每个虚函数的首地址。编译器会向类中加入隐含的成员变量:虚函数表指针,并在类的构造函数中安插代码给虚函数表指针赋值。
即使子类不重写任何虚函数,与父类的虚函数表不是一个表,尽管它们的表项相同。
纯虚函数也会在虚函数表中占据一个表项。

05.05.cpp
#include <iostream>
#include <time.h>
#include <cstdio>
using namespace std;

struct Base
{
	virtual ~Base() {}
	virtual void f() { cout << "Base::f()" << endl; }
	virtual void g() { cout << "Base::g()" << endl; }
	virtual void h() { cout << "Base::h()" << endl; }
};

struct Derive :  Base
{
	virtual void i() { cout << "Derive::i()" << endl; }
	virtual void g() { cout << "Derive::g()" << endl; }
	void myselffunc() { cout << "Derive::myselffunc()" << endl; } // 只属于Derive的函数
};

struct BCXu
{
	virtual void pvfunc() = 0;
};

int main()
{
	{
		Derive myderive;
		Derive* pmyderive = &myderive;
		//注意下列虚函数的调用顺序
		pmyderive->f();
		pmyderive->g();
		pmyderive->h();
		pmyderive->i();
		pmyderive->myselffunc();
	}

	{
		Base *pb = new Derive(); // 基类指针指向一个子类对象
		pb->g();
		delete pb;

		Derive myderive;
		Base &yb = myderive; // 基类引用引用的是一个子类对象
		yb.g();
	}
	{
		Base b1;
		Derive a1;
		Derive a2;
		Derive *pa3 = new Derive();
		delete pa3;
	}
	{
		cout << sizeof(Base) << endl;
		cout << sizeof(Derive) << endl;
		cout << sizeof(BCXu) << endl;
	}

	cout << "Over!\n";
	return 0;
}

5.6 多继承虚函数深释、第二基类与虚析构必加

5.6.1 多继承下的虚函数

struct Base
{
	virtual void f() { cout << "Base::f()" << endl; }
	virtual void g() { cout << "Base::g()" << endl; }
	virtual void h() { cout << "Base::h()" << endl; }
};

struct Base2
{
	virtual void hBase2() { cout << "Base2::hBase2()" << endl; }
};

struct Derive : Base, Base2
{
	virtual void i() { cout << "Derive::i()" << endl; }
	virtual void g() { cout << "Derive::g()" << endl; }
	void myselffunc() { cout << "Derive::myselffunc()" << endl; } // 只属于Derive的函数
};

{
	Base2 *pb2 = new Derive();
	//this指针调整,编译器转换
	//Derive *tmp = new Derive();
	//Base2 *pb2 = (Base2 *)((char *)(tmp) + sizeof(Base));
	delete pb2;//程序异常
}

5.6.2 如何成功删除用第二基类指针new出来的子类对象

若Derive类有虚析构函数,编译器会插入调用Base、Base2父类析构函数的代码。
若Base或Base2父类有虚析构函数,Derive会合成虚析构函数。

thunk一般用于多重继承中(第二个虚函数表开始可能就有),用于this指针调整。
thunk其实是一段代码(一个代码块),能做两件事:

  • 调整this指针。
  • 调用Derive析构函数

执行delete pb2;代码行时,系统会调用(执行)thunk这段代码。
编译器的设计者在虚函数表项目中嵌入了一个thunk代码块(实际是一个指向一个代码块的指针)专门处理delete pb2;这种代码的对象释放问题。

5.6.3 父类非虚析构函数时导致的内存泄露演示

struct ParentClass // 父类
{
	ParentClass()
	{
		cout << "ParentClass::ParentClass()" << endl;
	}
	virtual ~ParentClass()
	{
		cout << "ParentClass::~ParentClass()" << endl;
	}
};
struct SonClass : ParentClass // 子类
{
	char *m_p = nullptr;
	SonClass()
	{
		cout << "SonClass::SonClass()" << endl;
		m_p = new char[100]; // 这里分配了内存
	}
	~SonClass()
	{
		cout << "SonClass::~SonClass()" << endl;
		delete m_p; // 这里要释放内存,否则会导致内存泄漏
	}
};
if (1)
	{
		SonClass sonobj;

		ParentClass *parobj = new SonClass(); // 创建SonClass对象
		delete parobj;						  // 删除SonClass对象
	}

delete指向子类对象的父类指针(parobj)时:

  1. 若父类析构函数不是虚函数时,不会触发动态绑定,只会调用父类的析构函数而不会调用子类的析构函数,从而可能导致内存泄漏(若子类中存在delete代码时)。
  2. 若父类析构函数是虚函数,则子类析构函数一定是虚函数,会触发系统的动态绑定。同时编译器在子类的析构函数中(函数体后面)插入调用父类析构函数的代码。
05.06.cpp
#include <iostream>
#include <time.h>
#include <cstdio>
using namespace std;

struct Base
{
	virtual ~Base() { cout << "Base::~Base()" << endl; }
	virtual void f() { cout << "Base::f()" << endl; }
	virtual void g() { cout << "Base::g()" << endl; }
	virtual void h() { cout << "Base::h()" << endl; }
};

struct Base2
{
	virtual ~Base2() { cout << "Base2::~Base2()" << endl; }
	virtual void hBase2() { cout << "Base2::hBase2()" << endl; }
};

struct Derive : Base, Base2
{
	virtual ~Derive() { cout << "Derive::~Derive()" << endl; }

	virtual void i() { cout << "Derive::i()" << endl; }
	virtual void g() { cout << "Derive::g()" << endl; }
	void myselffunc() { cout << "Derive::myselffunc()" << endl; } // 只属于Derive的函数
};

struct ParentClass // 父类
{
	ParentClass()
	{
		cout << "ParentClass::ParentClass()" << endl;
	}
	virtual ~ParentClass()
	{
		cout << "ParentClass::~ParentClass()" << endl;
	}
};
struct SonClass : ParentClass // 子类
{
	char *m_p = nullptr;
	SonClass()
	{
		cout << "SonClass::SonClass()" << endl;
		m_p = new char[100]; // 这里分配了内存
	}
	~SonClass()
	{
		cout << "SonClass::~SonClass()" << endl;
		delete m_p; // 这里要释放内存,否则会导致内存泄漏
	}
};

int main()
{
	if (0)
	{
		Derive *temp = new Derive();
		Base2 *pb2 = (Base2 *)((char *)(temp) + sizeof(Base));
	}

	if (0)
	{
		Base2 *pb2 = new Derive();
		delete pb2;
	}

	if (0)
	{
		Base *pbm = new Base();
		delete pbm;

		Base2 *pb222 = new Base2();
		delete pb222;

		Derive *p11212 = new Derive();
		p11212->g(); // Derive::g()
		p11212->i(); // Derive::i(),走虚函数表,查询虚函数表得到虚函数地址并调用虚函数
		delete p11212;

		Derive dddd;
		dddd.i(); // Derive::i(),不走虚函数表,直接调用虚函数
	}

	if (1)
	{
		SonClass sonobj;

		ParentClass *parobj = new SonClass(); // 创建SonClass对象
		delete parobj;						  // 删除SonClass对象
	}

	cout << "Over!\n";
	return 0;
}

5.7 多继承第二基类虚函数支持与虚继承带虚函数

5.7.1 多重继承第二基类对虚函数支持的影响(this指针调整作用)

this指针的调整目的就是让对象指针正确地指向对象首地址,从而能正确调用对象的成员函数或者正确确定成员变量的存储位置。
多重继承下,this指针调整的几种情况:

  1. 通过指向第二个基类的指针调用继承类的虚函数时
struct Base
{
	virtual ~Base() { cout << "Base::~Base()" << endl; }

	virtual void f() { cout << "Base::f()" << endl; }
	virtual void g() { cout << "Base::g()" << endl; }
	virtual void h() { cout << "Base::h()" << endl; }

	virtual Base *clone() const { return new Base(); }
};

struct Base2
{
	virtual ~Base2() { cout << "Base2::~Base2()" << endl; }

	virtual void hBase2() { cout << "Base2::hBase2()" << endl; }

	virtual Base2 *clone() const { return new Base2(); }
};

struct Derive : Base, Base2
{
	virtual ~Derive() { cout << "Derive::~Derive()" << endl; }

	virtual void i() { cout << "Derive::i()" << endl; }
	virtual void g() { cout << "Derive::g()" << endl; }

	void myselffunc() { cout << "Derive::myselffunc()" << endl; } // 只属于Derive的函数

	virtual Derive *clone() const { return new Derive(); }
};
if (1)
	{
		Base2 *pb2 = new Derive();
		delete pb2; // 这里调用了Derive类的虚析构函数
	}
  1. 一个指向派生类的指针调用第二个基类中的虚函数时
	if (1)
	{
		Derive* pd2 = new Derive();
		pd2->hBase2();//第二个基类的成员函数,this指针调整,指向Base2类子对象的首地址
		delete pd2;
	}
  1. 允许虚函数的返回值类型有所变化时
	if (1)
	{
		Base2 *pb1 = new Derive();
		Base2 *pb2 = pb1->clone(); // this调整,执行Derive::clone();返回时this调整,指向Base2类。
		delete pb2;
		delete pb1;
	}

5.7.2 虚继承下的虚函数

05.07.cpp
#include <iostream>
#include <time.h>
#include <cstdio>
using namespace std;

struct Base
{
	virtual ~Base() { cout << "Base::~Base()" << endl; }

	virtual void f() { cout << "Base::f()" << endl; }
	virtual void g() { cout << "Base::g()" << endl; }
	virtual void h() { cout << "Base::h()" << endl; }

	virtual Base *clone() const { return new Base(); }
};

struct Base2
{
	virtual ~Base2() { cout << "Base2::~Base2()" << endl; }

	virtual void hBase2() { cout << "Base2::hBase2()" << endl; }

	virtual Base2 *clone() const { return new Base2(); }
};

struct Derive : Base, Base2
{
	virtual ~Derive() { cout << "Derive::~Derive()" << endl; }

	virtual void i() { cout << "Derive::i()" << endl; }
	virtual void g() { cout << "Derive::g()" << endl; }

	void myselffunc() { cout << "Derive::myselffunc()" << endl; } // 只属于Derive的函数

	virtual Derive *clone() const { return new Derive(); }
};

struct Base0
{
	// 8(虚函数表指针)
	// 4(4)
	int m_basei;
	virtual ~Base0() {}
	virtual void f() {}
};

struct Derive0 : virtual Base0
{
	// 8(虚基类表指针)
	// 4(4,m_derivei)
	// 8(虚函数表指针)
	// 4(4,m_basei)
	virtual ~Derive0() {}
	int m_derivei;
};

int main()
{
	if (0)
	{
		Base2 *pb2 = new Derive();
		delete pb2; // 这里调用了Derive类的虚析构函数
	}

	if (0)
	{
		Derive *pd2 = new Derive();
		pd2->hBase2(); // 第二个基类的成员函数,this指针调整,指向Base2类子对象的首地址
		delete pd2;
	}

	if (0)
	{
		Base2 *pb1 = new Derive();
		Base2 *pb2 = pb1->clone(); // 执行Derive::clone()
		delete pb2;
		delete pb1;
	}

	if (0)
	{
		cout << sizeof(Base0) << endl;	 // 16
		cout << sizeof(Derive0) << endl; // 32

		Derive0 dobj;
		dobj.m_basei = 2;	// 13-16字节
		dobj.m_derivei = 5; // 5-8字节

		Derive0 *pdobj = new Derive0();
		pdobj->f();
		delete pdobj;
	}

	if (0)
	{
		Base0 *pbase = new Derive0();
		pbase->m_basei = 6;
		delete pbase;
	}

	if (1)
	{
		Derive0 *pderive = new Derive0();
		Base0 *pbase2 = (Base0 *)pderive;
		pbase2->m_basei = 7;
		delete pderive;
	}

	cout << "Over!\n";
	return 0;
}

5.8 RTTI(运行时类型识别)回顾与存储位置简介

5.8.1 RTTI(运行时类型识别)简单回顾

父类中至少存在一个虚函数,才能获取准确的RTTI信息。
RTTI手段,可以在执行期间查询一个多态指针或者多态引用的信息。
RTTI能力,通过typeid或者dynamic_cast运算符体现。

struct Base
{
	virtual ~Base() {}
	virtual void f() { cout << "Base::f()" << endl; }
	virtual void g() { cout << "Base::g()" << endl; }
	virtual void h() { cout << "Base::h()" << endl; }
};

struct Derive :  Base
{
	virtual ~Derive() {}
	virtual void g() { cout << "Derive::g()" << endl; }
	void myselffunc() {} // 只属于Derive的函数
};

if (1)
	{
		Base *pb = new Derive(); // 基类指针指向一个子类对象
		pb->g();

		Derive myderive;
		Base &yb = myderive; // 基类引用引用的是一个子类对象
		yb.g();

		cout << typeid(*pb).name() << endl;
		cout << typeid(yb).name() << endl;

		Derive *pderive = dynamic_cast<Derive *>(pb);
		if (pderive != nullptr)
		{
			cout << "pb指向的实际是一个Derive类型" << endl;
			pderive->myselffunc(); // 调用属于子类自己的函数
		}
	}

5.8.2 RTTI实现原理

RTTI的目的是让程序运行时根据基类的指针或者引用来获得这个指针或者引用所指向的对象的实际类型。

		const std::type_info &tp = typeid(*pb);

		Base *pb2 = new Derive();
		const std::type_info &tp2 = typeid(*pb2);
		if (tp == tp2) // 条件成立
			cout << "很好,类型相同" << endl;
		delete pb2;
const std::type_info &tp = typeid(*pb);

	if (1)
	{
		cout << typeid(int).name() << endl;
		cout << typeid(Base).name() << endl;
		cout << typeid(Derive).name() << endl;
		Derive *pa3 = new Derive();
		cout << typeid(pa3).name() << endl;
		delete pa3;
	}

RTTI的测试(检验能力)与基类中是否存在虚函数有关,基类中无虚函数,则基类无虚函数表,RTTI无法得到正确结果。
typeid运算符在运行过程中拿到其指向的对象的实际类型。
虚函数表首地址前12个字节放置与RTTI有关的结构信息。

if (1)
	{
		Base *pb = new Derive();

		Derive myderive;
		Base &yb = myderive;

		cout << typeid(*pb).name() << endl; // class Derive
		cout << typeid(yb).name() << endl;	// class Derive

		Base *pb2 = new Derive();
		const std::type_info &tp2 = typeid(*pb2);
		cout << tp2.name() << endl;

		//---------------------------------------
		printf("tp2地址为 :%p\n", &tp2);

		long long *pvptr = (long long *)pb2;	   // 指向对象pb2的指针转成long *型,大家注意,目前pb2对象里只有虚函数表指针
		long long *vptr = (long long *)(*pvptr); // 指向虚函数表(虚函数表首地址)

		printf("虚函数表首地址为: %p\n", vptr);
		printf("虚函数表首地址之前一个地址为: %p\n", vptr - 1); // 注意,long long在当前这个环境是8字节,所以vptr-1,其实得到的地址是往回走了8个地址

		// 取得这个地址中的内容
		long long *prttiiinfo = (long long *)(*(vptr - 1));
		prttiiinfo += 3; // 跳过8*3个字节
		long long *ptypeinfoaddr = (long long *)(*prttiiinfo);
		const std::type_info *ptypeinfoaddrreal = (const std::type_info *)ptypeinfoaddr;
		printf("ptypeinfoaddrreal地址为 :%p\n", ptypeinfoaddrreal);
		//cout << ptypeinfoaddrreal->name() << endl; // 结果class Derive
		cout << typeid(int).name() << endl; // 输出int
	}

5.8.3 vptr、vtbl与RTTI的type_info信息创建时机

虚函数表指针vptr是类的成员变量,编译器编译时插进去,构造函数中赋值,指向该类的虚函数表。
虚函数表vtbl编译期间决定,跟着类走。
RTTI的type_info信息编译期间创建,读取,一般放在虚函数表之前的一个位置。

05.08.cpp
#include <iostream>
#include <time.h>
#include <cstdio>
using namespace std;

struct Base
{
	virtual ~Base() {}
	virtual void f() { cout << "Base::f()" << endl; }
	virtual void g() { cout << "Base::g()" << endl; }
	virtual void h() { cout << "Base::h()" << endl; }
};

struct Derive : Base
{
	virtual ~Derive() {}
	virtual void g() { cout << "Derive::g()" << endl; }
	void myselffunc() {} // 只属于Derive的函数
};

int main()
{
	if (0)
	{
		Base *pb = new Derive(); // 基类指针指向一个子类对象
		pb->g();

		Derive myderive;
		Base &yb = myderive; // 基类引用引用的是一个子类对象
		yb.g();

		cout << typeid(*pb).name() << endl;
		cout << typeid(yb).name() << endl;

		Derive *pderive = dynamic_cast<Derive *>(pb);
		if (pderive != nullptr)
		{
			cout << "pb指向的实际是一个Derive类型" << endl;
			pderive->myselffunc(); // 调用属于子类自己的函数
		}
		const std::type_info &tp = typeid(*pb);

		Base *pb2 = new Derive();
		const std::type_info &tp2 = typeid(*pb2);
		if (tp == tp2) // 条件成立
			cout << "很好,类型相同" << endl;
		delete pb2;

		delete pb;
	}

	if (0)
	{
		cout << typeid(int).name() << endl;
		cout << typeid(Base).name() << endl;
		cout << typeid(Derive).name() << endl;
		Derive *pa3 = new Derive();
		cout << typeid(pa3).name() << endl;
		delete pa3;
	}

	if (1)
	{
		Base *pb = new Derive();

		Derive myderive;
		Base &yb = myderive;

		cout << typeid(*pb).name() << endl; // class Derive
		cout << typeid(yb).name() << endl;	// class Derive

		Base *pb2 = new Derive();
		const std::type_info &tp2 = typeid(*pb2);
		cout << tp2.name() << endl;

		//---------------------------------------
		printf("tp2地址为 :%p\n", &tp2);

		long long *pvptr = (long long *)pb2;	   // 指向对象pb2的指针转成long *型,大家注意,目前pb2对象里只有虚函数表指针
		long long *vptr = (long long *)(*pvptr); // 指向虚函数表(虚函数表首地址)

		printf("虚函数表首地址为: %p\n", vptr);
		printf("虚函数表首地址之前一个地址为: %p\n", vptr - 1); // 注意,long long在当前这个环境是8字节,所以vptr-1,其实得到的地址是往回走了8个地址

		// 取得这个地址中的内容
		long long *prttiiinfo = (long long *)(*(vptr - 1));
		prttiiinfo += 3; // 跳过8*3个字节
		long long *ptypeinfoaddr = (long long *)(*prttiiinfo);
		const std::type_info *ptypeinfoaddrreal = (const std::type_info *)ptypeinfoaddr;
		printf("ptypeinfoaddrreal地址为 :%p\n", ptypeinfoaddrreal);
		//cout << ptypeinfoaddrreal->name() << endl; // 结果class Derive
		cout << typeid(int).name() << endl; // 输出int
	}

	cout << "Over!\n";
	return 0;
}

5.9 函数调用与继承关系性能

5.9.1 函数调用中编译器的循环代码优化

__int64 mytest(int mv)
{
	__int64 icout = 0;
	for (int i = 1; i <= 1000000; i++)
	{
		icout += 1;
		if (i == 10000 && mv == 999)
			printf("---\n");
	}
	return icout;
}
if (1)
	{
		__int64 mycount = 1;
		clock_t start = clock();
		for (int i = 1; i <= 1000; i++)
		{
			mycount += mytest(6); // 参数如果是固定值,那么就很快,参数如果是一个i可变值就很慢
			// mycount += mytest(i); //参数如果是固定值,那么就很快,参数如果是一个i可变值就很慢
		}
		clock_t end = clock();
		cout << "用时(毫秒): " << end - start << endl;
		cout << "mycount = " << mycount << endl;
	}

5.9.2 继承关系深度增加,开销也增加


struct A
{
	A() { cout << "A::A()" << endl; }

	virtual void myvirfunc() {}
};

struct A1
{
	A1() { cout << "A1::A1()" << endl; }
};

struct B : A
// struct B : A,  A1
{
};

struct C : B
{
	C() { cout << "C::C()" << endl; }
};

5.9.3 继承关系深度增加,虚函数导致的开销增加

05.09.cpp
#include <iostream>
#include <ctime>
#include <cstdio>
using namespace std;

__int64 mytest(int mv)
{
	__int64 icout = 0;
	for (int i = 1; i <= 1000000; i++)
	{
		icout += 1;
		if (i == 10000 && mv == 999)
			printf("---\n");
	}
	return icout;
}

struct A
{
	A() { cout << "A::A()" << endl; }

	virtual void myvirfunc() {}
};

struct A1
{
	A1() { cout << "A1::A1()" << endl; }
};

struct B : A
// struct B : A,  A1
{
};

struct C : B
{
	C() { cout << "C::C()" << endl; }
};

int main()
{
	if (0)
	{
		__int64 mycount = 1;
		clock_t start = clock();
		for (int i = 1; i <= 1000; i++)
		{
			mycount += mytest(6); // 参数如果是固定值,那么就很快,参数如果是一个i可变值就很慢
								  // mycount += mytest(i); //参数如果是固定值,那么就很快,参数如果是一个i可变值就很慢
		}
		clock_t end = clock();
		cout << "用时(毫秒): " << end - start << endl;
		cout << "mycount = " << mycount << endl;
	}

	if (1)
	{
		C c;
	}

	cout << "Over!\n";
	return 0;
}

5.10 指向成员函数的指针和vcall

5.10.1 不用类对象能否调用类的虚函数和普通成员函数

// 将一个成员函数的地址转换成普通地址
template <typename dst_type, typename src_type>
dst_type pointer_cast(src_type src)
{
	return *static_cast<dst_type *>(static_cast<void *>(&src));
}
struct TDF
{
	void myf(int val) { cout << "myf()成员函数执行了" << endl; }
};	

if (1)
	{
		printf("TDF::myf的地址是%p\n", &TDF::myf);
		typedef void (*Func)();
		Func fun1 = pointer_cast<Func>(&TDF::myf);
		void *func2 = pointer_cast<void *>(&TDF::myf);
		fun1(); // 把成员函数当普通函数调用
	}

5.10.2 指向成员函数的指针

struct A
{
	virtual ~A() {}

	void myfunc1(int tempvalue1)
	{
		cout << "tempvalue1 = " << tempvalue1 << endl;
	}
	void myfunc2(int tempvalue2)
	{
		cout << "tempvalue2 = " << tempvalue2 << endl;
	}

	static void mysfunc(int tempvalue)
	{
		cout << "A::mysfunc()静态成员函数执行了,tempvalue = " << tempvalue << endl;
	}

	virtual void myvirfuncPrev(int tempvalue)
	{
		cout << "A::myvirfuncPrev()虚成员函数执行了,tempvalue = " << tempvalue << endl;
	}
	virtual void myvirfunc(int tempvalue)
	{
		cout << "A::myvirfunc()虚成员函数执行了,tempvalue = " << tempvalue << endl;
	}
};

if (1)
	{
		void (A::*pmypoint)(int tempvalue) = &A::myfunc1; // 定义一个成员函数指针并给初值
		pmypoint = &A::myfunc2;							  // 给成员函数指针赋值

		// 通过成员函数指针来调用成员函数
		A mya;
		(mya.*pmypoint)(15); // 要使用成员函数指针来调用成员函数,必须要对象介入

		A *pmya = new A();
		(pmya->*pmypoint)(20); // 用对象指针介入来使用成员函数指针 来调用成员函数
	}

	if (1)
	{
		void (*pmyspoint)(int tempvalue) = &A::mysfunc; // 一个普通的函数指针,而不是成员函数指针
		pmyspoint(80);									// 从编译器角度看也是这个代码,不需要有个所谓的this指针
	}

5.10.3 指向虚成员函数的指针和vcall

virtual call,虚调用,代表一段要执行的代码的首地址,这段代码引导程序的执行流程去执行正确的虚函数(以及进行this指针调整等),或者可以简单地把vcall看成虚函数表。

	if (1)
	{
		void (A::*pmyvirfunc)(int tempvalue) = &A::myvirfunc;

		A *pvaobj = new A;
		pvaobj->myvirfunc(190);		// 通过虚函数表
		(pvaobj->*pmyvirfunc)(190); // 通过虚函数表
		printf("%p\n", &A::myvirfunc);

		pmyvirfunc = &A::myfunc2;
		(pvaobj->*pmyvirfunc)(33);
	}

&A::myvirfunc代表一个地址,这个地址中有一段代码,记录该虚函数在虚函数表中的一个偏移值。加上具体对象指针,就能调用虚函数表里面的虚函数。

5.10.4 vcall在继承关系中的体现

struct B : A
{
	virtual ~B() {}

	virtual void myvirfunc(int tempvalue)
	{
		cout << "B::myvirfunc()虚成员函数执行了,tempvalue = " << tempvalue << endl;
	}
};
if (1)
	{
		B *pmyb = new B();
		void (B::*pmyvirfunc)(int tempvalue) = &A::myvirfunc; // 隐藏了偏移信息
		// void (B:: * pmyvirfunc)(int tempvalue) = &B::myvirfunc;
		(pmyb->*pmyvirfunc)(190);//最终执行B::myvirfunc(int tempvalue)

		//vcall thunk地址值不一样
		printf("%p\n", &A::myvirfunc);
		printf("%p\n", &B::myvirfunc);
	}
05.10.cpp
#include <iostream>
#include <time.h>
#include <cstdio>
using namespace std;

// 将一个成员函数的地址转换成普通地址
template <typename dst_type, typename src_type>
dst_type pointer_cast(src_type src)
{
	return *static_cast<dst_type *>(static_cast<void *>(&src));
}
struct TDF
{
	void myf(int val) { cout << "myf()成员函数执行了" << endl; }
};

struct A
{
	virtual ~A() {}

	void myfunc1(int tempvalue1)
	{
		cout << "tempvalue1 = " << tempvalue1 << endl;
	}
	void myfunc2(int tempvalue2)
	{
		cout << "tempvalue2 = " << tempvalue2 << endl;
	}

	static void mysfunc(int tempvalue)
	{
		cout << "A::mysfunc()静态成员函数执行了,tempvalue = " << tempvalue << endl;
	}

	virtual void myvirfuncPrev(int tempvalue)
	{
		cout << "A::myvirfuncPrev()虚成员函数执行了,tempvalue = " << tempvalue << endl;
	}
	virtual void myvirfunc(int tempvalue)
	{
		cout << "A::myvirfunc()虚成员函数执行了,tempvalue = " << tempvalue << endl;
	}
};

struct B : A
{
	virtual ~B() {}

	virtual void myvirfunc(int tempvalue)
	{
		cout << "B::myvirfunc()虚成员函数执行了,tempvalue = " << tempvalue << endl;
	}
};

int main()
{
	if (0)
	{
		printf("TDF::myf的地址是%p\n", &TDF::myf);
		typedef void (*Func)();
		Func fun1 = pointer_cast<Func>(&TDF::myf);
		void *func2 = pointer_cast<void *>(&TDF::myf);
		fun1(); // 把成员函数当普通函数调用
	}

	if (0)
	{
		void (A::*pmypoint)(int tempvalue) = &A::myfunc1; // 定义一个成员函数指针并给初值
		pmypoint = &A::myfunc2;							  // 给成员函数指针赋值

		// 通过成员函数指针来调用成员函数
		A mya;
		(mya.*pmypoint)(15); // 要使用成员函数指针来调用成员函数,必须要对象介入

		A *pmya = new A();
		(pmya->*pmypoint)(20); // 用对象指针介入来使用成员函数指针 来调用成员函数
	}

	if (0)
	{
		void (*pmyspoint)(int tempvalue) = &A::mysfunc; // 一个普通的函数指针,而不是成员函数指针
		pmyspoint(80);									// 从编译器角度看也是这个代码,不需要有个所谓的this指针
	}

	if (1)
	{
		void (A::*pmyvirfunc)(int tempvalue) = &A::myvirfunc;

		A *pvaobj = new A;
		pvaobj->myvirfunc(190);		// 通过虚函数表
		(pvaobj->*pmyvirfunc)(190); // 通过虚函数表
		printf("%p\n", &A::myvirfunc);

		pmyvirfunc = &A::myfunc2;
		(pvaobj->*pmyvirfunc)(33);
	}

	if (1)
	{
		B *pmyb = new B();
		void (B::*pmyvirfunc)(int tempvalue) = &A::myvirfunc; // 成员函数指针
		// void (B:: * pmyvirfunc)(int tempvalue) = &B::myvirfunc;
		(pmyb->*pmyvirfunc)(190);

		printf("%p\n", &A::myvirfunc);
		printf("%p\n", &B::myvirfunc);
	}

	cout << "Over!\n";
	return 0;
}

5.11 inline函数回顾和扩展

5.11.1 inline函数回顾

inline只是程序员对编译器的建议,决定权在编译器。
inline函数优点是执行成本比常规函数调用和函数返回低,提高程序执行效率,缺点是会导致代码的膨胀。

5.11.2 inline扩展

05.11.cpp
#include <iostream>
#include <time.h>
#include <cstdio>
using namespace std;

inline int myfunc0(int testv)
{
	return testv * (5 + 4) * testv;
	// int tempvalue = testv * (5 + 4) * testv;
	// return tempvalue;
}

inline int myfunc(int testv)
{
	if (testv > 10)
	{
		testv--;
		myfunc(testv);
	}
	return testv;
}

int rtnvalue()
{
	return 5;
}

int main()
{
	{
		int i0 = myfunc(12);
		int i1 = myfunc(12 + 15);
		int a = 80;
		int i2 = myfunc(a + 15);

		int i3 = myfunc(rtnvalue() + 15);

		int i4 = myfunc(12);
		cout << i4 << endl;
	}

	cout << "Over!\n";
	return 0;
}
  • 19
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值