深度探索C++对象模型(22)——函数语义学(6)——成员函数指针和vcall

1.指向成员函数的指针

成员函数地址,编译时就确定好的。但是,调用成员函数是需要通过对象来调用的;

所有常规(非静态)成员函数,要想调用,都需要一个对象来调用它;

所以对于使用成员函数指针调用普通成员函数,需要使用对象进行调用

代码:

#include <iostream>

using namespace std;

namespace _nmsp1 //命名空间
{

	class A
	{
	public:
		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;
		}
	};

	void func()
	{

		//--------------------使用成员函数指针调用普通成员函数,需要对象来调用------------
		A mya;
		void (A::*pmypoint)(int tempvalue) = &A::myfunc1; //定义一个成员函数指针并给初值
		pmypoint = &A::myfunc2; //给成员函数指针赋值

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

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

		//编译器视角
		//pmypoint(&mya, 15);
		//pmypoint(pmya, 20);

		//--------------------使用普通函数指针调用静态成员函数------------
		void(*pmyspoint)(int tempvalue) = &A::mysfunc; //一个普通的函数指针,而不是成员函数指针
		pmyspoint(80);

		//通过成员函数指针对常规的成员函数调用的成本,和通过普通的函数指针来调用静态成员函数,成本上差不多;

	}
}

int main()
{
	_nmsp1::func();
}

2.指向虚成员函数的指针及vcall深谈

代码:

#include <iostream>

using namespace std;
namespace _nmsp2
{
	class A
	{
	public:
		void myfunc1(int tempvalue1)
		{
			cout << "tempvalue1 = " << tempvalue1 << endl;
		}
		void myfunc2(int tempvalue2)
		{
			cout << "tempvalue2 = " << tempvalue2 << endl;
		}

		virtual void myvirfuncPrev(int tempvalue)
		{
			cout << "A::myvirfuncPrev()虚成员函数--tempvalue = " << tempvalue << endl;
		}

		virtual void myvirfunc(int tempvalue)
		{
			cout << "A::myvirfunc()虚成员函数--tempvalue = " << tempvalue << endl;
		}

	};

	void func()
	{
		void (A::*pmyvirfunc)(int tempvalue) = &A::myvirfunc; //成员函数指针  -- vcall(vcall trunk)地址(虚函数)

		A *pvaobj = new A;
		pvaobj->myvirfunc(190);         //通过对象指针调用,走虚函数表
		(pvaobj->*pmyvirfunc)(190);		//通过成员函数指针调用,同样也走虚函数表
		printf("%p\n", &A::myvirfunc);  //实际打印的是vcall地址

		pmyvirfunc = &A::myfunc2;  //真正的成员函数地址
		(pvaobj->*pmyvirfunc)(33);

		delete pvaobj;

	}
}
int main()
{
	_nmsp2::func();
}

反汇编:

对于上述vcall的理解:

vcall (vcall trunk) = virtual call:虚调用,它代表一段要执行的代码的地址,这段代码引导咱们去执行正确的虚函数,或者我们直接把vcall看成虚函数表,如果这么看待的话,那么vcall{0}代表的就是虚函数表里的第一个函数,vcall{4}就代表虚函数表里的第二个虚函数。

&A::myvirfunc:打印出来的是一个地址,这个地址中有一段代码,这个代码中记录的是该虚函数在虚函数表中的一个偏移值有了这个偏移值,再有了具体的对象指针(this指针),我们就能够知道调用的是哪个虚函数表里边的哪个虚函数了;

结论:

对于虚函数的调用,使用函数指针来调用的话,和普通函数一样使用成员函数指针来调用,但是在它的成员函数指针里,保存的是一个vcall(vcall trunk)地址,而在调用普通成员函数的时候成员函数指针保存的是一个真正的成员函数地址,如果是一个vcall地址,那vcall能够引导编译器找出正确的虚函数表中的虚函数地址进行调用

所以printf("%p\n", &A::myvirfunc);这句代码实际打印的是vcall地址

3.vcall在继承关系中的体现

代码:

#include <iostream>

using namespace std;
namespace _nmsp3
{
	//三:vcall在继承关系中的体现
	class A
	{
	public:

		virtual void myvirfunc(int tempvalue)
		{
			cout << "A::myvirfunc()虚成员函数--tempvalue = " << tempvalue << endl;
		}
		virtual ~A()
		{

		}
	};

	class B :public A
	{
	public:
		virtual void myvirfunc(int tempvalue)
		{
			cout << "B::myvirfunc()虚成员函数--tempvalue = " << tempvalue << endl;
		}
		virtual ~B() {}
	};

	void func()
	{
		B *pmyb = new B();   //pmyb:对象指针
		void (B::*pmyvirfunc)(int tempvalue) = &A::myvirfunc; //定义一个B成员函数指针,初始化为A的虚函数的vcall地址
		(pmyb->*pmyvirfunc)(190);    //会调用A的虚函数还是B的虚函数呢?

		printf("%p\n", &A::myvirfunc); //vcall地址 和下个vcall地址不一样
		printf("%p\n", &B::myvirfunc);
	}
}
int main()
{
	_nmsp3::func();
}

运行结果:

解释:

对于虚成员函数的调用,对象决定调用哪个类的虚函数表,vcall决定了偏移,所以对于虚函数myvirfunc()不管是A中vcall还是B中vcall都是相同的偏移,而这里的成员函数指针,传入的对象是B,偏移是vcall{0},所以最终调用的是B的虚函数myvirfunc(),同时这里不管是赋值给A的myvirfunc()的vcall还是B的myvirfunc()的vcall,都是调用的是B的虚函数myvirfunc()

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值