日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

C++成員函數如何當作回調函數同時傳遞this指針_C 語言

作者:易拉罐里的人 ? 更新時間: 2022-12-23 編程語言

就我目前了解所知,有三種函數可以作為回調函數:

  • 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指針傳遞進來了。因此結果也是正確的。

原文鏈接:https://blog.csdn.net/weixin_45416828/article/details/124074506

欄目分類
最近更新