C++成員函數(shù)如何當(dāng)作回調(diào)函數(shù)同時傳遞this指針
就我目前了解所知,有三種函數(shù)可以作為回調(diào)函數(shù):
- 1.普通函數(shù)
- 2.靜態(tài)函數(shù)(我用得少沒有寫,直接跳過)
- 3.成員函數(shù)
1.普通函數(shù)作為注冊函數(shù)
普通函數(shù)作為回調(diào)函數(shù),比較簡單,只要函數(shù)簽名(返回值類型+參數(shù)類型)一致就可以了。
因為普通函數(shù)不是類成員函數(shù),如果想要訪問類成員,在執(zhí)行回調(diào)函數(shù)的時候,要把對象指針傳給回調(diào)函數(shù),如下代碼:
namespace yy0 { //普通全局函數(shù) void call_back(void* pointer); class A { public: A() { //初始化指針 p_call_back = NULL; //單數(shù)最大的數(shù) num = 9; } ~A() {} public: //打印這個數(shù)來驗證是否正常調(diào)用回調(diào)函數(shù) int num; private: //指向回調(diào)函數(shù)的地址的指針 void(*p_call_back)(void*); public: //用于注冊回調(diào)函數(shù) void register_call_back(void(*p)(void*)) { if (p) p_call_back = p; } //執(zhí)行回調(diào)函數(shù) void run_call_back() { if (p_call_back) { //把對象指針傳遞出去 p_call_back(this); } } //測試函數(shù) void test() { //注冊 register_call_back(call_back); //執(zhí)行 run_call_back(); } }; void call_back(void* pointer) { if (pointer) { //需要進(jìn)行指針轉(zhuǎn)換 A* p = (A*)pointer; cout << "打印的值:" << p->num; } } } int main() { yy0::A a; a.test(); getchar(); return 0; }
結(jié)果正確打印,說明回調(diào)函數(shù)正常調(diào)用:
也可以定義一個全局的類對象指針:
namespace yy0 { //前置聲明 class A; //普通全局函數(shù) void call_back(); //全局的類對象指針 A* pointer = NULL; class A { public: A() { //給全局類對象指針賦值 pointer = this; //初始化指針 p_call_back = NULL; //單數(shù)最大的數(shù) num = 9; } ~A() {} public: //打印這個數(shù)來驗證是否正常調(diào)用回調(diào)函數(shù) int num; private: //指向回調(diào)函數(shù)的地址的指針 void(*p_call_back)(); public: //用于注冊回調(diào)函數(shù) void register_call_back(void(*p)()) { if (p) p_call_back = p; } //執(zhí)行回調(diào)函數(shù) void run_call_back() { if (p_call_back) { //把對象指針傳遞出去 p_call_back(); } } //測試函數(shù) void test() { //注冊 register_call_back(call_back); //執(zhí)行 run_call_back(); } }; void call_back() { if (pointer) { //需要進(jìn)行指針轉(zhuǎn)換 A* p = (A*)pointer; cout << "打印的值:" << p->num; } } }
這也可以正確執(zhí)行,但是這種定義全局的對象指針有風(fēng)險。如果只創(chuàng)建一個A的對象,就可以正常使用,不會出現(xiàn)什么太大問題。但是,一旦創(chuàng)建的對象個數(shù)≥2,那么就造成數(shù)據(jù)讀取錯誤的問題。
可以想象一下,創(chuàng)建對象a1時,全局對象指針pointer是指向a1的位置,那么讀取的pointer->num,是a1對象的num。
然后再創(chuàng)建a2,那么全局對象指針pointer就變成了指向a2的位置(因為pointer是個全局變量,從始至終只有一個這個變量),那么執(zhí)行a2.text(),pointer->num讀取的是a2的num。
如果執(zhí)行a1.text(),那么此時,pointer->num讀取的也是a2的num,而不是a1的num。更嚴(yán)重的是,一旦刪除了a1或者a2,就會造成另外一個對象訪問內(nèi)存失敗的問題。
2.靜態(tài)函數(shù)作為注冊函數(shù)
這個就自行上網(wǎng)查看吧,我用的少就不寫了。
3.成員函數(shù)作為注冊函數(shù)
假設(shè)場景:A類成員函數(shù)作為B類回調(diào)函數(shù)
《深度探索C++對象模型》這本書講到,類成員函數(shù)都有一個隱藏參數(shù)用于傳遞this指針,這個this傳遞給函數(shù)由編譯器來完成,不需要用戶來做。
直接上代碼:
namespace yy3 { class B { public: B() { pointer = NULL; } ~B() {} public: //存放A類的this指針 void* pointer; //指向回調(diào)函數(shù) void(__stdcall *pCallBack)(void*); public: /* @函數(shù)作用:注冊回調(diào) @輸入?yún)?shù): void(*p)(void*) -- 輸入A類的回調(diào)函數(shù)的地址 void* p_this -- 輸入A類的this指針 */ //② void register_fun(void(__stdcall *p)(void*), void* p_this) { pCallBack = p; pointer = p_this; } //執(zhí)行回調(diào) //③ 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; //定義聯(lián)合,不知道原理,網(wǎng)上查到的技巧 union for_callback { void(__stdcall *fun_int_c)(void*); void (A::*fun_in_class)(void*); }fp; public: //要拿來注冊的回調(diào)函數(shù) void call_back(void* p) { A* pointer = (A*)p; //能打印出正確的a值就對了 cout << "a:" << pointer->a << endl; } //測試函數(shù) //① 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聲明:這個看情況,我在公司電腦寫的時候不需要加這個關(guān)鍵字,自己的電腦就要加這個。就是一個傳參約定,可以上網(wǎng)查。
2.
//定義聯(lián)合,不知道原理,網(wǎng)上查到的技巧 union for_callback { void(__stdcall *fun_int_c)(void*); void (A::*fun_in_class)(void*); }fp;
使用union,這個說是為了逃避編譯器檢查,原理我也不太懂,如果有知道原理的大神,麻煩告訴一下下,感謝感謝。我直接就拿來用了。
3.前面說了成員函數(shù)有個隱含傳遞指針的參數(shù),所以函數(shù)指針:
//指向回調(diào)函數(shù) void(__stdcall *pCallBack)(void*);
需要定義參數(shù)為void*的函數(shù)指針,用于傳遞A類的this指針
4.因為函數(shù)指針是B類的成員,而函數(shù)指針接受的參數(shù)是A類的this指針,我們不能直接這樣使用:
void run_call_back() { if (pCallBack) pCallBack(this); }
這個pCallBack(this)中的this是指向B類對象的地址而非A類對象的地址,因此,在B類定義一個成員:void* pointr,用于保存A類對象的指針,然后這樣使用
//執(zhí)行回調(diào) void run_call_back() { if (pCallBack) pCallBack(pointer); }
這樣就運行回調(diào)函數(shù),同時傳遞A類對象指針。
5.(無參這一點單獨在這里說)當(dāng)然,雖然成員函數(shù)有自帶隱藏參數(shù),我們也可以把它轉(zhuǎn)換成無參的函數(shù),修改這些地方:
//【1】 //指向回調(diào)函數(shù) 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】 //執(zhí)行回調(diào) 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】 //要拿來注冊的回調(diào)函數(shù)修改為 void call_back() { cout << "a:" << this->a << endl; }
這種情況編譯能通過,但是void call_back()使用this指針,是無法正確讀取內(nèi)存的值,如下
言歸正傳。
成員函數(shù)轉(zhuǎn)為帶一個void*參數(shù)的函數(shù)運行情況如下:
結(jié)果也是一個不正確的值,因此進(jìn)行調(diào)試查看,把斷點放在這個函數(shù)上,發(fā)現(xiàn)了一個奇怪的問題:
//要拿來注冊的回調(diào)函數(shù) void call_back(void* p) { A* pointer = (A*)p; //能打印出正確的a值就對了 cout << "a:" << pointer->a << endl; }
pointer是A類對象的指針,pointer通過函數(shù)指針pCallBack(pointr)傳遞給了call_back(void* p),從理論上講,p的值要與pointer保持一致才對。但是p的值與pCaalBack相同,也就是p是函數(shù)指針,特別奇怪。我也不知道什么原因,所以如果有人知道,麻煩跟我講一下,在這里先謝謝了。
我無法解決這個問題,所以嘗試了將函數(shù)指針轉(zhuǎn)為帶有兩個void*參數(shù)的函數(shù),竟然可以傳遞正確的this指針,算是瞎貓碰上死耗子,代碼跟上面類似,如下:
namespace yy3 { class B { public: B() { pointer = NULL; } ~B() {} public: //存放A類的this指針 void* pointer; //指向回調(diào)函數(shù) void(__stdcall *pCallBack)(void*, void*); public: /* @函數(shù)作用:注冊回調(diào) @輸入?yún)?shù): void(*p)(void*,void*) -- 輸入A類的回調(diào)函數(shù)的地址 void* p_this -- 輸入A類的this指針 */ void register_fun(void(__stdcall *p)(void*, void*), void* p_this) { pCallBack = p; pointer = p_this; } //執(zhí)行回調(diào) void run_call_back() { if (pCallBack) //需要兩個指針作為參數(shù),干脆就傳遞兩個pointer吧 pCallBack(pointer,pointer); } }; class A { public: A() { a = 5; } A(int num) { a = num; }; ~A() {} public: //在A類中定義一個B類的變量 B b; //拿來測試的變量 double a; //定義聯(lián)合,不知道原理,網(wǎng)上查到的技巧 union for_callback { void(__stdcall *fun_int_c)(void*, void*); void (A::*fun_in_class)(void*, void*); }fp; public: //要拿來注冊的回調(diào)函數(shù) void call_back(void* p, void* pp) { A* pointer = (A*)p; //能打印出正確的a值就對了 cout << "a:" << pointer->a << endl; } //測試函數(shù) void test() { fp.fun_in_class = &A::call_back; b.register_fun(fp.fun_int_c, this); b.run_call_back(); } }; }
結(jié)果是正確的:
從圖上可知,pCallBack(函數(shù)指針)的值,與p和pp都不同,無論是p還是pp,這兩個值都是A類對象的地址,也就是說,已經(jīng)成功把A的this指針傳遞進(jìn)來了。因此結(jié)果也是正確的。
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Ubuntu 20.04 下安裝配置 VScode 的 C/C++ 開發(fā)環(huán)境(圖文教程)
這篇文章主要介紹了Ubuntu 20.04 下安裝配置 VScode 的 C/C++ 開發(fā)環(huán)境,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-05-05VC++中HTControl的CHTButton按鈕控件類用法實例解析
這篇文章主要介紹了VC++中HTControl的CHTButton按鈕控件類用法,對于大家進(jìn)行VC++項目開發(fā)有一定的幫助作用,需要的朋友可以參考下2014-08-08vscode遠(yuǎn)程連接服務(wù)器(免密登錄+遠(yuǎn)程開發(fā))
vscode的遠(yuǎn)程連接功能十分方便,本文就來介紹一下vscode遠(yuǎn)程連接服務(wù)器,主要包括免密登錄和遠(yuǎn)程開發(fā),感興趣的可以了解一下2024-07-07