回調函數中調用類中的非靜態成員變量或非靜態成員函數程序員
【問題1】如何在類中封裝回調函數?編程
【答】:
a.回調函數只能是全局的或是靜態的。
b.全局函數會破壞類的封裝性,故不予採用。
c.靜態函數只能訪問類的靜態成員,不能訪問類中非靜態成員。
【問題2】如何讓靜態函數訪問類的非靜態成員?
【解決方案】:函數
聲明一靜態函數a(),將類實例對象指針作爲參數傳入。如:
class A()
{
static void a(A *); //靜態函數
void b(); //非靜態函數
}
void A::a(A * pThis)
{
pThis->b(); //靜態函數中調用非靜態函數
}
【問題3】回調函數中如何訪問非靜態成員?
因爲回調函數每每有固定定義,並不接受 A * pThis 參數
如:CALLBACK MyTimerProc(HWND hwnd,UINT uMsg,UINT idEvent,DWORD dwTime);
【解決方案1】:本方案當遇到有多個類實例對象時會有問題。緣由是pThis指針只能指向一個對象。
class A()
{
static void a(); //靜態回調函數
void b(); //非靜態函數
static A * pThis; //靜態對象指針
}
A * A::pThis=NULL;
A::A() //構造函數中將this指針賦給pThis,使得回調函數能經過pThis指針訪問本對象
{
pThis=this;
}
void A::a()
{
if (pThis==NULL) return;
pThis->b(); //回調函數中調用非靜態函數
}
【解決方案2】:本方案解決多個類實例對象時方案1的問題。用映射表存全部對象地址,每一個對象保存本身的ID號。
typedef CMap<UINT,UINT,A*,A*> CAMap;
class A()
{
static void a(); //靜態回調函數
void b(); //非靜態函數
int m_ID; //本對象在列表中的ID號
static int m_SID; //靜態當前對象ID (須要時,將m_ID賦值給m_SID以起到調用本對象函數的功能)
static CAMap m_Map; //靜態對象映射表
}
CAMap A::m_Map;
int A::m_SID=0;
A::A() //構造函數中將this指針賦給pThis,使得回調函數能經過pThis指針訪問本對象
{
if(m_Map.IsEmpty())
{
m_ID=1;
}
else
{
m_ID=m_Map.GetCount()+1;
}
m_Map.SetAt( m_ID, this );
}
void A::a()
{
if (m_Map.IsEmpty()) return;
A * pThis=NULL;
if(m_Map.Lookup(m_SID,pThis))
{
pThis->b(); //回調函數中調用非靜態函數
};
}this
=================================spa
回調函數是基於C編程的Windows SDK的技術,不是針對C++的,程序員能夠將一個C函數直接做爲回調函數,可是若是試圖直接使用C++的成員函數做爲回調函數將發生錯誤,甚至編譯就不能經過。 .net
普通的C++成員函數都隱含了一個傳遞函數做爲參數,亦即「this」指針,C++經過傳遞一個指向自身的指針給其成員函數從而實現程序函數能夠訪問C++的數據成員。這也能夠理解爲何C++類的多個實例能夠共享成員函數可是確有不一樣的數據成員。因爲this指針的做用,使得將一個CALLBACK型的成員函數做爲回調函數安裝時就會由於隱含的this指針使得函數參數個數不匹配,從而致使回調函數安裝失敗。線程
這樣從理論上講,C++類的成員函數是不能看成回調函數的。但咱們在用C++編程時總但願在類內實現其功能,即要保持封裝性,若是把回調函數寫做普通函數有諸多不便。通過網上搜索和本身研究,發現了幾種巧妙的方法,可使得類成員函數看成回調函數使用。指針
這裏採用Linux C++中線程建立函數pthread_create舉例,其原型以下:rest
類MyClass須要在本身內部開闢一個子線程來執行成員函數func()中的代碼,子線程經過調用startThread()成員函數來啓動。這裏將回調函數callback寫在了類外面,傳遞的參數是一個指向MyClass對象的指針(在pthrad_create()中由第4個參數this指定),回調函數通過強制轉換把void*變爲MyClass*,而後再調用arg->func()執行子線程的代碼。code
這樣作的原理是把當前對象的指針看成參數先交給一個外部函數,再由外部函數調用類成員函數,之外部函數做爲回調函數,但執行的是成員函數的功能,這樣至關於在中間做了一層轉換。缺點是回調函數在類外,影響了封裝性,這裏把callback()限定爲static,防止在其它文件中調用此函數。
方法二:回調函數爲類內靜態成員函數,在其內部調用成員函數
在方法一上稍做更改,把回調函數搬到類MyClass裏,這樣就保持了封裝性。代碼以下:
這個方法的好處時封裝性獲得了很好的保護,MyClass對外只公開一個接口startThread(),子線程代碼和回調函數都被設爲私有,外界不可見。另外沒有佔用callback的參數,能夠從外界傳遞參數進來。但每一個對象啓動子線程前必定要注意先調用setCurMy()讓CurMy正確的指向自身,不然將爲其它對象開啓線程,這樣很引起很嚴重的後果。
方法三:對成員函數進行強制轉換,看成回調函數
代碼以下:
方法三的封裝性比方法二更好,由於不涉及多個對象共用一個靜態成員的問題,每一個對象能夠獨立地啓動本身的線程而不影響其它對象。