線程的封裝很簡單,並沒有多少東西,但是如果我告訴你,我封裝的這個線程類裏有個成員函數Start,作用是開啓線程運行,可以這樣調用:
class a
{
public:
DWORD ThreadFunca(LONG lParam);
void StartThread();
CMyThread m_thread;
}
DWORD a::ThreadFunca(LONG lParam)
{
//線程執行代碼 ...
}
class b
{
public:
DWORD ThreadFuncb(LONG lParam);
void StartThread();
CMyThread m_thread;
}
DWORD b::ThreadFuncb(LONG lParam)
{
//線程執行代碼 ...
}
//調用代碼:
void a::StartThread()
{
m_thread.Start(this,(THREADFUNC)ThreadFunca);
}
void b::StartThread()
{
m_thread.Start(this,(THREADFUNC)ThreadFuncb);
}
你是不是會覺得很神奇呢?a和b是完全不相干的兩個類,但其成員函數卻能作爲參數傳進去。這個Start並不是宏,那其參數是怎麼定義的呢?
聰明的你一定已經想到模板了,不錯,這個Start是一個模板函數,但是調用時卻見不到模板調用時常見的xxx<yyy>格式,並且調用的類,這個線程類產生對象時也不需要把具體數據類型傳入,這裏用到了一點技巧。
首先,想象一下這個Start的實現機制,按線程實現的規則,一定是先把線程函數定位到一個全局或靜態的線程處理函數,然後由這個函數轉到具體的成員處理函數。這樣,這個函數就必須知道類a或b的成員函數指針,加上原來線程需要傳入的參數,因此傳入這個全局線程處理函數的參數就必須是一個結構了。定義一下這個結構:
template <typename T>
struct ThreadInfo
{
typedef DWORD (T::*THREADFUNC1)(LONG);
T* m_obj;
LONG m_param;
THREADFUNC1 m_lpthreadFunc;
};
這個結構應該是作爲CMyThread的成員數據,由CMyThread創建線程的時候作爲參數傳給線程處理函數,對用戶是透明的。這時候問題來了,創建CMyThread對象的時候必須初始化這個結構,但這時候還沒調用Start,並不知道具體調用類的類型,而這個結構是必須關聯到具體數據類型的,CMyThread初始化的時候這個結構該是什麼類型呢?這裏就要用到一個數據類型強制轉換的技巧了,我們先聲明一個空的類,拿這個類初始化這個結構:
class NULLCLASS
{
};
爲了方便,typedef一下:typedef ThreadInfo<NULLCLASS> ThInfo;
這個結構的類型就是:
class CMyThread
{
...
private:
ThInfo* m_info;
};
這裏額外提一下,在Borland C++ 5.02下,這個NULLCLASS只需要聲明,不需要實現,即可以不加{}:class NULLCLASS;就行了,但是VC6就不行,加不加{},其sizeof(NULLCLASS)返回的尺寸是不一樣的,加了{}返回的尺寸纔是對的,不知是哪個公司錯了。
發現沒有,這個m_info結構裏有了一個DWORD NULLCLASS::THREADFUNC1)(LONG);函數指針,而這個NULLCLASS根本沒有這個函數,沒關係,這個NULLCLASS只是用來做數據類型轉換用,順帶也轉換了成員函數指針類型。爲了方便,再typedef一下這個成員函數指針:typedef ThreadInfo<NULLCLASS>::THREADFUNC1 THREADFUNC;
這樣這個Start就能實現了:
class CMyThread
{
public:
CMyThread();
~CMyThread();
template<typename tname>
BOOL Start(tname* obj,THREADFUNC func,LONG param = 0)
{
if(m_handle || obj == NULL || func == NULL)
return FALSE; //已有線程運行
ThreadInfo<tname>* lpv = new ThreadInfo<tname>;
lpv->m_obj = obj;
lpv->m_param = param;
lpv->m_lpthreadFunc = (ThreadInfo<tname>::THREADFUNC1)func;
m_info = (ThInfo*)lpv;
DWORD gdwThreadId = 0;
m_handle = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)ThreadProc,m_info,0,&gdwThreadId);
return (BOOL)m_handle;
}
...
private:
static DWORD WINAPI ThreadProc(ThInfo* obj);
private:
HANDLE m_handle;
ThInfo* m_info;
};
ThreadProc就很簡單了:
DWORD WINAPI CMyThread::ThreadProc(ThInfo* obj)
{
if(obj)
return (obj->m_obj->*(obj->m_lpthreadFunc))(obj->m_param);
return 0;
}
這樣就可以象上面的代碼那樣調用:m_thread.Start(this,(THREADFUNC)ThreadFunca);了,利用typename,把this指針傳遞進去,會自動判別數據類型,再利用NULLCLASS作爲數據類型轉換過個橋,這樣就實現任意類的成員函數指針傳遞了。而傳統的解決方法就需要在調用時多實現一個全局線程函數,傳遞this指針進去,再在全局線程函數中得到這個指針調用成員函數,並沒實現真正的封裝。