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

 更新时间:2022年11月25日 09:05:04   作者:易拉罐里的人  
这篇文章主要介绍了C++成员函数如何当作回调函数同时传递this指针,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

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

  • 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指针传递进来了。因此结果也是正确的。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • C++深入详解单例模式与特殊类设计的实现

    C++深入详解单例模式与特殊类设计的实现

    这篇文章主要为大家详细介绍了C++单例模式和特殊类的设计,单例模式这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2022-06-06
  • C语言超详细讲解队列的实现及代码

    C语言超详细讲解队列的实现及代码

    队列(Queue)与栈一样,是一种线性存储结构,它具有如下特点:队列中的数据元素遵循“先进先出”(First In First Out)的原则,简称FIFO结构。在队尾添加元素,在队头删除元素
    2022-04-04
  • 解析C++中多层派生时的构造函数及一些特殊形式

    解析C++中多层派生时的构造函数及一些特殊形式

    这篇文章主要介绍了解析C++中多层派生时的构造函数及一些特殊形式,特殊形式主要针对基类和子对象类型的构造函数内容,需要的朋友可以参考下
    2015-09-09
  • C++分析类的对象作类成员调用构造与析构函数及静态成员

    C++分析类的对象作类成员调用构造与析构函数及静态成员

    终于到了对象的初始化和清理的最后阶段了,在这里分享一个cpp里有多个类时,一个类的对象作为另一个类成员的时候构造函数和析构函数调用的时机。还有一个静态成员也是经常考到的点,在这篇博客将会详解其概念并举出案例巩固,让我们开始
    2022-05-05
  • 基于C++编写一个密码系统

    基于C++编写一个密码系统

    这篇文章主要为大家详细介绍了如何基于C++编写一个简单的密码系统,文中的示例代码讲解详细,具有一定的借鉴价值,感兴趣的小伙伴可以跟随小编一起学习一下
    2023-11-11
  • C/C++ 避免数组越界的方法

    C/C++ 避免数组越界的方法

    这篇文章主要介绍了C/C++ 避免数组越界的方法,文中讲解非常细致,代码帮助大家更好的理解和学习,感兴趣的朋友可以了解下
    2020-06-06
  • C语言求矩阵的各列元素之和的代码示例

    C语言求矩阵的各列元素之和的代码示例

    这篇文章主要介绍了C语言求矩阵的各列元素之和的代码示例,这也是经常作为竞赛和计算机专业考试的基础练习出现的题目,需要的朋友可以参考下
    2016-07-07
  • C++中高性能内存池的实现详解

    C++中高性能内存池的实现详解

    在 C/C++ 中,内存管理是一个非常棘手的问题,我们在编写一个程序的时候几乎不可避免的要遇到内存的分配逻辑。本文将通过C++实现高性能内存池,感兴趣的可以了解一下
    2022-10-10
  • 浅理解C++ 人脸识别系统的实现

    浅理解C++ 人脸识别系统的实现

    这篇文章主要介绍了浅理解C++ 人脸识别系统的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-03-03
  • C++ OpenCV实战之图像全景拼接

    C++ OpenCV实战之图像全景拼接

    本文主要介绍了如何使用OpenCV C++ 进行图像全景拼接,文中的示例代码讲解详细,对我们学习OpenCV有一定的帮助,感兴趣的可以了解一下
    2022-01-01

最新评论