C++成员函数当作回调函数同时传递this指针

C++成员函数当作回调函数同时传递this指针,第1张

就我目前了解所知,有三种函数可以作为回调函数:

1.普通函数

2.静态函数(我用得少没有写,直接跳过)

3.成员函数

1.普通函数作为注册函数

普通函数作为回调函数,比较简单,只要函数签名(返回值类型+参数类型)一致就可以了。


因为普通函数不是类成员函数,如果想要访问类成员,在执行回调函数的时候,要把对象指针传给回调函数,如下代码:

namespace yy0
{
	//普通全局函数
	void call_back(void* pointer);

	class A
	{
	public:
		A() {
			//初始化指针
			p_call_back = NULL;
			//单数最大的数
			num = 9;
		}
		~A() {}
	public:
		//打印这个数来验证是否正常调用回调函数
		int num;
	private:
		//指向回调函数的地址的指针
		void(*p_call_back)(void*);

	public:
		//用于注册回调函数
		void register_call_back(void(*p)(void*)) {
			if (p)
				p_call_back = p;
		}

		//执行回调函数
		void run_call_back() {
			if (p_call_back)
			{
				//把对象指针传递出去
				p_call_back(this);
			}		
		}

		//测试函数
		void test() {
			//注册
			register_call_back(call_back);
			//执行
			run_call_back();
		}
	};

	void call_back(void* pointer)
	{
		if (pointer)
		{
			//需要进行指针转换
			A* p = (A*)pointer;
			cout << "打印的值:" << p->num;
		}
	}
}


int main()
{
	yy0::A a;
	a.test();
	getchar();
	return 0;
}

结果正确打印,说明回调函数正常调用:

也可以定义一个全局的类对象指针:

namespace yy0
{
	//前置声明
	class A;
	//普通全局函数
	void call_back();
	//全局的类对象指针
	A* pointer = NULL;
	class A
	{
	public:
		A() {
			//给全局类对象指针赋值
			pointer = this; 
			//初始化指针
			p_call_back = NULL;
			//单数最大的数
			num = 9;
		}
		~A() {}
	public:
		//打印这个数来验证是否正常调用回调函数
		int num;
	private:
		//指向回调函数的地址的指针
		void(*p_call_back)();

	public:
		//用于注册回调函数
		void register_call_back(void(*p)()) {
			if (p)
				p_call_back = p;
		}

		//执行回调函数
		void run_call_back() {
			if (p_call_back)
			{
				//把对象指针传递出去
				p_call_back();
			}		
		}

		//测试函数
		void test() {
			//注册
			register_call_back(call_back);
			//执行
			run_call_back();
		}
	};

	
	void call_back()
	{
		if (pointer)
		{
			//需要进行指针转换
			A* p = (A*)pointer;
			cout << "打印的值:" << p->num;
		}
	}
}

这也可以正确执行,但是这种定义全局的对象指针有风险。


如果只创建一个A的对象,就可以正常使用,不会出现什么太大问题。


但是,一旦创建的对象个数≥2,那么就造成数据读取错误的问题。


可以想象一下,创建对象a1时,全局对象指针pointer是指向a1的位置,那么读取的pointer->num,是a1对象的num。


然后再创建a2,那么全局对象指针pointer就变成了指向a2的位置(因为pointer是个全局变量,从始至终只有一个这个变量),那么执行a2.text(),pointer->num读取的是a2的num。


如果执行a1.text(),那么此时,pointer->num读取的也是a2的num,而不是a1的num。


更严重的是,一旦删除了a1或者a2,就会造成另外一个对象访问内存失败的问题。


2.静态函数作为注册函数

这个就自行上网查看吧,我用的少就不写了。


3.成员函数作为注册函数

假设场景:A类成员函数作为B类回调函数

《深度探索C++对象模型》这本书讲到,类成员函数都有一个隐藏参数用于传递this指针,这个this传递给函数由编译器来完成,不需要用户来做。


直接上代码:

namespace yy3
{

	class B
	{
	public:
		B() {
			pointer = NULL;
		}
		~B() {}

	public:
		//存放A类的this指针
		void* pointer;
		//指向回调函数
		void(__stdcall *pCallBack)(void*);
	public:
		/*
		@函数作用:注册回调
		@输入参数:
		void(*p)(void*)			-- 输入A类的回调函数的地址
		void* p_this			-- 输入A类的this指针
		*/
        //②
		void register_fun(void(__stdcall *p)(void*), void* p_this) {
			pCallBack = p;
			pointer = p_this;
		}

		//执行回调
        //③
		void run_call_back() {
			if (pCallBack)
				pCallBack(pointer);
		}

	};


	class A
	{
	public:
		A() {
			a = 5;
		}
		A(int num) {
			a = num;
		};
		~A() {}
	public:
		//在A类中定义一个B类的变量
		B b;
		//拿来测试的变量
		double a;

		//定义联合,不知道原理,网上查到的技巧
		union for_callback {
			void(__stdcall *fun_int_c)(void*);
			void (A::*fun_in_class)(void*);
		}fp;
	public:
		//要拿来注册的回调函数
		void call_back(void* p) {
			A* pointer = (A*)p;
			//能打印出正确的a值就对了
			cout << "a:" << pointer->a << endl;
		}

		//测试函数
        //①
		void test() {
			fp.fun_in_class = &A::call_back;
			b.register_fun(fp.fun_int_c, this);
			b.run_call_back();
		}

	};
}

int main()
{
	yy3::A a;
	a.test();
	getchar();
	return 0;
}

首先来解释一地方:

1.__stdcall声明:这个看情况,我在公司电脑写的时候不需要加这个关键字,自己的电脑就要加这个。


就是一个传参约定,可以上网查。


2.

//定义联合,不知道原理,网上查到的技巧
union for_callback {
    void(__stdcall *fun_int_c)(void*);
    void (A::*fun_in_class)(void*);
}fp;

使用union,这个说是为了逃避编译器检查,原理我也不太懂,如果有知道原理的大神,麻烦告诉一下下,感谢感谢。


我直接就拿来用了。


3.前面说了成员函数有个隐含传递指针的参数,所以函数指针:

//指向回调函数
void(__stdcall *pCallBack)(void*);

需要定义参数为void*的函数指针,用于传递A类的this指针

4.因为函数指针是B类的成员,而函数指针接受的参数是A类的this指针,我们不能直接这样使用:

void run_call_back() {
	if (pCallBack)
		pCallBack(this);
}

这个pCallBack(this)中的this是指向B类对象的地址而非A类对象的地址,因此,在B类定义一个成员:void* pointr,用于保存A类对象的指针,然后这样使用

//执行回调
void run_call_back() {
	if (pCallBack)			
    pCallBack(pointer);
}

这样就运行回调函数,同时传递A类对象指针。


5.(无参这一点单独在这里说)当然,虽然成员函数有自带隐藏参数,我们也可以把它转换成无参的函数,修改这些地方:

//【1】
//指向回调函数
void(__stdcall *pCallBack)(void*);
//修改为
void(__stdcall *pCallBack)();

//【2】
void register_fun(void(__stdcall *p)(void*), void* p_this) {
	pCallBack = p;
	pointer = p_this;
}
//修改为
void register_fun(void(__stdcall *p)(), void* p_this) {
	pCallBack = p;
	pointer = p_this;
}

//【3】
//执行回调
void run_call_back() {
	if (pCallBack)
		pCallBack(pointer);
}
//修改为
void run_call_back() {
	if (pCallBack)
		pCallBack();
}

//【4】
union for_callback {
	void(__stdcall *fun_int_c)(void*);
	void (A::*fun_in_class)(void*);
}fp;
//修改为
union for_callback {
	void(__stdcall *fun_int_c)();
	void (A::*fun_in_class)();
}fp;

//【5】
//要拿来注册的回调函数修改为
void call_back() {
	cout << "a:" << this->a << endl;
}

这种情况编译能通过,但是void call_back()使用this指针,是无法正确读取内存的值,如下

言归正传。


成员函数转为带一个void*参数的函数运行情况如下:

 结果也是一个不正确的值,因此进行调试查看,把断点放在这个函数上,发现了一个奇怪的问题:

		//要拿来注册的回调函数
		void call_back(void* p) 
		{
			A* pointer = (A*)p;
			//能打印出正确的a值就对了
			cout << "a:" << pointer->a << endl;	
		}

 

pointer是A类对象的指针,pointer通过函数指针pCallBack(pointr)传递给了call_back(void* p),从理论上讲,p的值要与pointer保持一致才对。


但是p的值与pCaalBack相同,也就是p是函数指针,特别奇怪。


我也不知道什么原因,所以如果有人知道,麻烦跟我讲一下,在这里先谢谢了。


我无法解决这个问题,所以尝试了将函数指针转为带有两个void*参数的函数,竟然可以传递正确的this指针,算是瞎猫碰上死耗子吧,代码跟上面类似,如下:

namespace yy3
{

	class B
	{
	public:
		B() {
			pointer = NULL;
		}
		~B() {}

	public:
		//存放A类的this指针
		void* pointer;
		//指向回调函数
		void(__stdcall *pCallBack)(void*, void*);
	public:
		/*
		@函数作用:注册回调
		@输入参数:
		void(*p)(void*,void*)	-- 输入A类的回调函数的地址
		void* p_this			-- 输入A类的this指针
		*/
		void register_fun(void(__stdcall *p)(void*, void*), void* p_this) {
			pCallBack = p;
			pointer = p_this;
		}

		//执行回调
		void run_call_back() {
			if (pCallBack)
				//需要两个指针作为参数,干脆就传递两个pointer吧
				pCallBack(pointer,pointer);
		}

	};


	class A
	{
	public:
		A() {
			a = 5;
		}
		A(int num) {
			a = num;
		};
		~A() {}
	public:
		//在A类中定义一个B类的变量
		B b;
		//拿来测试的变量
		double a;

		//定义联合,不知道原理,网上查到的技巧
		union for_callback {
			void(__stdcall *fun_int_c)(void*, void*);
			void (A::*fun_in_class)(void*, void*);
		}fp;
	public:
		//要拿来注册的回调函数
		void call_back(void* p, void* pp)
		{
			A* pointer = (A*)p;
			//能打印出正确的a值就对了
			cout << "a:" << pointer->a << endl;	
		}

		//测试函数
		void test() {
			fp.fun_in_class = &A::call_back;
			b.register_fun(fp.fun_int_c, this);
			b.run_call_back();
		}

	};
}

结果是正确的:

 从图上可知,pCallBack(函数指针)的值,与p和pp都不同,无论是p还是pp,这两个值都是A类对象的地址,也就是说,已经成功把A的this指针传递进来了。


因此结果也是正确的。


分享这次奇怪的经历,感谢阅读。


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/578122.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-11
下一篇 2022-04-11

发表评论

登录后才能评论

评论列表(0条)

保存