I really enjoyed Charles Calkins article, "Integrating Threads with Template Classes" in the May 2000 issue of CUJ. I had been struggling with designing thread objects that had the ability to clean up after themselves, as this would remove the requirement for a "thread manager" object. Charles' article brought everything into focus.
Charles mentioned at the end of his article that he thought there might be better solution. The code below implements a small shift in perspective, where instead of "wrapping" the class in a template that launches the thread, it may be better to implement a thread base class that supports launching a thread object and cleaning up after the thread terminates.
I wrote the thread base class with a launch function that can be used for thread objects created either on the stack or via dynamic allocation. Readers should remember that when using stack allocation they need to wait until the thread(s) completes before leaving the current scope. Things get rather ugly when the data vanishes from underneath a running thread! If you launch a thread object allocated out of the heap, then you need to either set bAutoDelete to true or delete the thread object manually after the thread terminates and you have extracted any relevant data.
Also note that this is a sample implementation for one platform, Windows. The platform-specific nature of the implementation generally doesn't affect the interface, though, except for addition of the handle accessor function. Also, VC++ will emit a diagnostic with an "undeclared identifier" on _beginthread if you have not specified multithreaded code generation. VC++ readers using a project need to set code generation in the C++ Project tab to either Multithread or Debug Multithread. Readers using a make file should set either the /MTd or /MT compiler option instead of /MLd or /ML.
#include <windows.h> #include <process.h> #include <iostream> #include <vector> #include <assert.h> using namespace std; // Thread Base Class class CThread { private: HANDLE hThread; size_t stackSize; bool bAutoDelete; virtual void run() = 0; static void _cdecl threadFnct(void *pVoid) { CThread *pData = static_cast<CThread*>(pVoid); if(pData != NULL) { pData->run(); if(pData->bAutoDelete) delete(pData); } } protected: void setStackSize( size_t s ) { stackSize = s; } public: virtual ~CThread() { } CThread() { hThread = NULL; bAutoDelete = false; stackSize = 0; } HANDLE launch(bool bAutoDelete) { assert(hThread == NULL); this->bAutoDelete = bAutoDelete; return hThread = (HANDLE)_beginthread(threadFnct, stackSize, this); } HANDLE handle() { return hThread; } }; // Test by subclassing the thread base class class CHelloWorld : public CThread { protected: virtual void run() { for(int n = 1; n < 10; n++) { cout << (10 - n) << " . "; Sleep(1000); } cout << endl << "Hello, World!" << endl; } }; int main() { vector<HANDLE> threads; // Test heap allocation CHelloWorld *pHello1 = new CHelloWorld; threads.push_back( pHello1->launch(true) ); // Test stack allocation CHelloWorld Hello2; threads.push_back( Hello2.launch(false) ); // Wait until both threads terminate WaitForMultipleObjects(threads.size(), threads.begin(), TRUE, INFINITE); }