試想一下, 有沒有這種需求:
對於每一個新的對象, 我們希望它能夠在一定時間後自動銷燬, 前提是我們沒有在這段時間內給它發出重置信號.
這種需求其實是有的, 比如在電影裏, 主角知道了一個反派不希望被揭露的祕密, 同時需要保住自己的性命, 那麼就可以構造這樣一個對象, 如果24小時內主角不給這個對象發送重置的信號, 它就會將這個祕密公之於衆. 再比如, 在網絡應用場景裏, 我們希望每一個客戶端能夠定時給我們發送心跳包, 如果長時間不發送的話, 我們就剔除這個客戶.
在之前的文章裏, 我嘗試使用了WIN32的Timer, 但是發現這種做法非常繁瑣且容易出錯, 你需要給每個對象綁定一個Timer, 同時需要在Timer到期時處理對象, 並且重置Timer的API和設置Timer的API是同一個, 稍有不慎就會搞砸.
現在, 我想出了一種相對簡單的實現方式, 雖然精度不是非常理想, 但對於一般應用而言, 足矣.
我們構造一個類, 它有一些私有的數據, 這些可以自定義, 但有一些API是必須的:
class Client { private: // ...Data or something int32_t m_life; int32_t m_max_life; DWORD delete_thread_id; HANDLE count_thread_handle; public: Client(int32_t, DWORD); void reset(void); static WIN32API DWORD countDownEntry(void *); DWORD countDown(void); // ...De-cons... }
1. 構造函數:
Client:Client(int32_t life, DWORD thread_id) { m_max_life = m_life = life; delete_thread_id = thread_id; count_thread_handle = CreateThread(..., ..., Client::countDownEntry, this); }
第二個參數是用來銷燬對象的線程ID, 這樣設計是考慮到對象有可能保存在一個堆, 如果我們簡單地調用析構函數, 那麼對象本身所佔據的空間就無法被釋放了, 所以我們通知這麼一個線程來完成所有的析構操作.
注意到我們使用的是countDownEntry()而不是countDown(), 因爲CreateThread不接受一個非靜態的成員函數作爲函數入口(無法確認地址).
2. reset()方法, 這方法需要先掛起倒計時的線程, 主要是防止同時訪問同一個內存的情況出現:
void Client::reset(void) {
SuspendThread(count_thread_handle); m_life = m_max_life;
ResumeThread(count_thread_handle); }
3. countDownEntry()方法爲何是static的? 很簡單, 我們需要在構造函數裏使用它來初始化倒計時線程, 而它的實現非常簡單, 我們在構造函數裏把this指針傳遞給這個靜態方法, 並在靜態方法裏重新獲取這個this代表的對象, 調用這個對象的倒計時函數即可:
static WIN32API DWORD Client::countDownEntry(void *pM) { Client *c = (Client *) pM; return c->countDown(); }
4. 而countDown()方法更加簡單, 使用Sleep函數來計時即可, 每計一秒就將life減1:
DWORD Client::countDown() { while (m_life > 0) { Sleep(1000); m_life--; } PostThreadMessageA(delete_thread_id); return 0; }
以上就是這樣一個對象的設計思路, 原理比較簡單, 也只是寫了個大概, 同時需要windows.h的支持.