線程的封裝技巧:把任意類的成員函數指針作爲參數

線程的封裝很簡單,並沒有多少東西,但是如果我告訴你,我封裝的這個線程類裏有個成員函數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指針進去,再在全局線程函數中得到這個指針調用成員函數,並沒實現真正的封裝。

發佈了26 篇原創文章 · 獲贊 5 · 訪問量 30萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章