C++11?成員函數(shù)作為回調(diào)函數(shù)的使用方式
C++11成員函數(shù)作為回調(diào)函數(shù)使用
std::bind()被廣泛地應(yīng)用在新式的回調(diào)函數(shù)中。
C++11以前類的普通成員函數(shù)不能作為回調(diào)函數(shù)去注冊,因為將普通成員函數(shù)注冊給對方,但對方使用這個函數(shù)指針時,就會發(fā)生參數(shù)列表匹配的問題,因為少了隱含的this。
靜態(tài)成員函數(shù)不包含this指針,所以一般將靜態(tài)成員函數(shù)注冊給對方。
C++11推出std::bind()和std::function搭配,前者生成新的調(diào)用對象,參數(shù)個數(shù)可以小于綁定函數(shù)的參數(shù)個數(shù),少的參數(shù),按位占用。后者保存函數(shù)調(diào)用類型的函數(shù)對象,使用該對象進行設(shè)置參數(shù)即可。
示例1
先看一個例子來熱熱身,熟悉一下std::bind和std::function
#include <functional> //所需std::bind和std::function頭文件
#include <iostream>
#include <map>
using namespace std;
// 使用std::bind時記得和::bind區(qū)別開,就怕作用于污染,誤用::bind
//除法運算
class Division {
public:
?? ?int operator()(int i, int j) { return i / j; }
};
//乘法運算
int Multiplication(int i, int j) { return i * j; }
//減法運算
int Substraction(int i, int j) { return i - j; }
//回調(diào)注冊函數(shù)
int CallbackReg(function<int(int, int)> &func, int i, int j) { return func(i, j); }
//回調(diào)注冊函數(shù)1,
int CallbackReq1(function<int(int)> &&func, int i) { return func(i); }
int main() {
?? ?// 此function接受函數(shù)調(diào)用類型為int(int, int)的調(diào)用對象
?? ?function<int(int, int)> func1 = [](int i, int j) { return i + j; }; //lambda
?? ?function<int(int, int)> func2 = &Substraction;?? ??? ??? ??? ??? ??? ?//函數(shù)指針
?? ?function<int(int, int)> func3 = Multiplication;?? ??? ??? ??? ??? ??? ?//函數(shù)名
?? ?function<int(int, int)> func4 = Division();?? ??? ??? ??? ??? ??? ??? ?//重載調(diào)用運算符的對象
?? ?//可將function類型存在容器中,來一次映射
?? ?map<int, function<int(int, int)>> mpFuncs;
?? ?mpFuncs[1] = func1;
?? ?mpFuncs[2] = func2;
?? ?mpFuncs[3] = func3;
?? ?mpFuncs[4] = func4;
?? ?//這里做著玩,映射一個數(shù)字和字符串
?? ?map<int, string> mpOprs{{1, " + "}, {2, " - "}, {3, " * "}, {4, " / "}};
?? ?// 便利map調(diào)用容器內(nèi)函數(shù)對象們
?? ?for (auto& it : mpFuncs) {
?? ??? ?cout << "calculator :" << 20 << mpOprs[it.first] << 5?
?? ??? ??? ?<< " = " << CallbackReg(it.second, 20, 5) << endl;
?? ?}
?? ?//使用std::bind,產(chǎn)生一個新的調(diào)用對象bindFunc(int i), 200作為int Multiplication(int i, 200)
?? ?//std::placeholders 有個N個占位符(vs此版為20個):_N,表示占用綁定函數(shù)的第n個位子
?? ?int pre = 300;
?? ?auto bindFunc = std::bind(Multiplication, placeholders::_1, pre);
?? ?cout << bindFunc(3) << endl;
?? ?//bind最重要的一點在于參數(shù)綁定,如下例注冊回調(diào),參數(shù)就從2個變成了1個
?? ?cout << CallbackReq1(std::bind(Multiplication, placeholders::_1, 200), 2) << endl;
?? ?return 0;
}calculator :20 + 5 = 25
calculator :20 - 5 = 15
calculator :20 * 5 = 100
calculator :20 / 5 = 4
900
400
好,在了解了std::bind和std::function之后來看一個平時常遇到的C++式的回調(diào)函數(shù)注冊
示例2
#include <functional>
#include <iostream>
#include <string>
#include <memory>
using namespace std;
using namespace std::placeholders; //占位符_N所在的命名空間
using CallBackFuncType = function<void(string const&)>;?
class Client {
public:
?? ?string name;
?? ?CallBackFuncType serverFunc;
?? ?Client() :name("Vergo"), serverFunc(nullptr) {}
?? ?~Client() {}
?? ?void SetCallBack(const CallBackFuncType &func) { serverFunc = func; }
?? ?void DoCallBack() { serverFunc(name); }
};
class Server {
public:
?? ?Client *m_clt;
?? ?Server() : m_clt(nullptr) { m_clt = new Client; }
?? ?~Server() { if (m_clt) delete m_clt; m_clt = nullptr; }
?? ?//回調(diào)函數(shù)本數(shù)
?? ?void MyCallBackFunc(string const& str) { cout << "The name of client is " << str << endl; }
?? ?//注冊回調(diào),將this指針綁定到回調(diào)函數(shù)中
?? ?void RegCallBackFunc() { if (!m_clt) return; ?m_clt->SetCallBack(CallBackFuncType(std::bind(&Server::MyCallBackFunc, this, _1))); }
?? ?//回調(diào)
?? ?void GiveMeCallBack() { if (!m_clt) return; m_clt->DoCallBack(); }
};
int main() {
?? ?Server testClass;
?? ?testClass.RegCallBackFunc();
?? ?testClass.GiveMeCallBack();?? ?
?? ?return 0;
}The name of client is Vergo
類成員函數(shù)作為回調(diào)函數(shù)的方法及注意點
編程中遇到一個錯誤,提示為error C2597: illegal reference to non-static member
即因為一個類的靜態(tài)成員函數(shù)調(diào)用了類的非靜態(tài)成員變量,而報錯。
下面具體介紹一些相關(guān)知識點,以防下次再出錯。
類成員函數(shù)當(dāng)回調(diào)函數(shù)的方法
方法一:回調(diào)函數(shù)為普通的全局函數(shù),但在函數(shù)體內(nèi)執(zhí)行類的成員函數(shù)
在創(chuàng)建線程調(diào)用回調(diào)函數(shù)時,傳入類對象的指針(比如this指針)作為參數(shù),并在回調(diào)函數(shù)中把void*強制轉(zhuǎn)換為類的指針(MyClass*),就能使用該指針調(diào)用類的成員函數(shù)。
這樣做的原理是把當(dāng)前對象的指針當(dāng)作參數(shù)先交給一個外部函數(shù),再由外部函數(shù)調(diào)用類成員函數(shù)。以外部函數(shù)作為回調(diào)函數(shù),但執(zhí)行的是成員函數(shù)的功能,這樣相當(dāng)于在中間作了一層轉(zhuǎn)換。
缺點:回調(diào)函數(shù)在類外,影響了封裝性。
方法二:回調(diào)函數(shù)為類內(nèi)靜態(tài)成員函數(shù),在其內(nèi)部調(diào)用類的非靜態(tài)成員函數(shù)
此時需要一個指向類本身的、類的靜態(tài)成員變量指針(static MyClass* CurMy),用來存儲當(dāng)前回調(diào)函數(shù)調(diào)用的對象,相當(dāng)于法1中給回調(diào)函數(shù)傳入的指針參數(shù)。在回調(diào)函數(shù)中通過CurMy指針調(diào)用類的成員函數(shù)。
優(yōu)點:
- 1、解決了法1的封裝性問題
- 2、沒有占用callback的參數(shù),可以從外界傳遞參數(shù)進來
缺點:每個對象啟動子線程前一定要注意先讓CurMy正確的指向自身,否則將為其它對象開啟線程。
方法三:對成員函數(shù)進行強制轉(zhuǎn)換,使其作為回調(diào)函數(shù)
這個方法是原理是,MyClass::func最終會轉(zhuǎn)化成 void func(MyClass *this);即在原第一個參數(shù)前插入指向?qū)ο蟊旧淼膖his指針??梢岳眠@個特性寫一個非靜態(tài)類成員方法來直接作為線程回調(diào)函數(shù)。
typedef void* (*FUNC)(void*); FUNC callback = (FUNC)&MyClass::func;
對編譯器而言,void (MyClass::*FUNC1)()和void* (*FUNC)(void*)這兩種函數(shù)指針雖然看上去很不一樣,但他們的最終形式是相同的,因此就可以把成員函數(shù)指針強制轉(zhuǎn)換成普通函數(shù)的指針來當(dāng)作回調(diào)函數(shù)。在建立線程時要把當(dāng)前對象的指針this當(dāng)作參數(shù)傳給回調(diào)函數(shù)(成員函數(shù)func),這樣才能知道線程是針對哪個對象建立的。
注意:此方法中FUNC函數(shù)的參數(shù)一定要是void*,這樣才能在編譯后把this指針轉(zhuǎn)變?yōu)镸yClass *this。
優(yōu)點:法3的封裝性比法2更好,因為不涉及多個對象共用一個靜態(tài)成員的問題,每個對象可以獨立地啟動自己的線程而不影響其它對象。
為什么回調(diào)函數(shù)必須為靜態(tài)函數(shù)?
普通的C++成員函數(shù)都隱含了一個“this”指針參數(shù),當(dāng)在類的非靜態(tài)成員函數(shù)中訪問類的非靜態(tài)成員時,C++編譯器通過傳遞一個指向?qū)ο蟊旧淼闹羔樈o其成員函數(shù),從而能夠訪問類的數(shù)據(jù)成員。也就是說,即使你沒有寫上this指針,編譯器在編譯的時候自動加上this的,它作為非靜態(tài)成員函數(shù)的隱含形參,對各成員的訪問均通過this進行。
正是由于this指針的作用,使得將一個CALLBACK型的成員函數(shù)作為回調(diào)函數(shù)時就會因為隱含的this指針使得函數(shù)參數(shù)個數(shù)不匹配,從而導(dǎo)致回調(diào)函數(shù)匹配失敗。所以為了實現(xiàn)回調(diào),類中的成員函數(shù)必須舍棄掉隱藏的this指針參數(shù)。因此,類中的回調(diào)函數(shù)必須為靜態(tài)函數(shù),加上static關(guān)鍵字。
類的靜態(tài)成員函數(shù)如何訪問非靜態(tài)成員?
靜態(tài)成員不屬于某個具體的對象,而是被所有對象所共享。即靜態(tài)成員屬于整個類,不屬于具體某個對象;非靜態(tài)成員屬于具體某個對象。因而靜態(tài)成員函數(shù)只能訪問類的靜態(tài)成員,不能訪問類中非靜態(tài)成員。
那么,如何讓靜態(tài)函數(shù)訪問類的非靜態(tài)成員?
方法是:對于靜態(tài)成員函數(shù),我們顯示的為其傳遞一個對象的首地址(該類的指針)。一般在這個靜態(tài)成員函數(shù)的形參列表中加入一個 void* 類型的參數(shù),來保存對象的首地址。并在該函數(shù)內(nèi)部對該參數(shù)進行類型轉(zhuǎn)換,通過類型轉(zhuǎn)換后的參數(shù)來調(diào)用非靜態(tài)成員。
或者用一個類的全局指針數(shù)組,保存每一個創(chuàng)建出來的類的this指針,用全局指針去調(diào)用。
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
C++中關(guān)于std::queue?中遇到釋放內(nèi)存錯誤的問題
這篇文章主要介紹了std::queue中遇到釋放內(nèi)存錯誤的問題,本文通過實例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-07-07
C語言復(fù)數(shù)的加減及輸出結(jié)構(gòu)體
大家好,本篇文章主要講的是C語言復(fù)數(shù)的加減及輸出結(jié)構(gòu)體,感興趣的同學(xué)趕快來看一看吧,對你有幫助的話記得收藏一下2022-02-02

