threadクラスの実装を晒す2
日にちがあいてしまいましたがエラー処置と引数を追加していきます。
コンストラクタで行っていた処理をstart_thread関数に移しています。
それに伴いthread_base*をメンバー変数p_にしました。
// thread.hpp #include <memory> // std::auto_ptr #include <process.h> // ::_beginthreadex #include <stdexcept> // std::runtime_error #include "thread_base.hpp" #include "threadholder0.hpp" // スレッド作成に失敗したことを通知するexceptionクラス class thread_resource_error : public std::runtime_error{}; class thread { HANDLE hThread_; // スレッドのハンドル unsigned int threadId_; // スレッドのID thread_base* p_; // スレッド実行クラス private: // _beginthreadexに渡す関数 static unsigned int __stdcall workerThread(void* arg) { std::auto_ptr<thread_base> p(reinterpret_cast<thread_base*>(arg)); // ポインタをauto_ptrに渡して自動的に開放 p->run(); // threadクラスに渡された関数を実行 return 0; } // スレッド開始関数 void thread_start() { hThread_ = reinterpret_cast<HANDLE>(::_beginthreadex(0, 0, &thread::workerThread, p_, CREATE_SUSPENDED, &threadId_)); if (!hThread_) { throw thread_resource_error(); } ::ResumeThread(hThread_); } public: // 引数0個の関数を実行するためのコンストラクタ template <typename Func> explicit thread(Func func) : hThread_(0) , threadId_(0) , p_(new threadholder0<Func>(func)) // 引数0個の関数ポインタを保持するクラスを生成 { thread_start(); } ~thread() { ::CloseHandle(hThread_); } // スレッドの実行を待つ関数 int join() { return static_cast<int>(::WaitForSingleObject(hThread_, INFINITE)); } };
thread_start関数ではまずスレッドをCREATE_SUSPENDEDを指定して待機させた状態で作成しています。
スレッドが作成されなかった場合はthread_resource_errorをスローしてスレッドの作成に失敗したことを通知します。
thread_resource_errorはboost::threadに定義されているものと同じ名前にしてみました。
作成に成功していた場合はResumeThread関数を用いてスレッドの開始を再開します。
次に引数が1つある関数をthreadで実行するための処理を追加します。
まずは引数1つを取る関数を実行するためのthreadholder1を追加します。
// threadholder1.hpp #include "thread_base.hpp" template <typename Func, typename Arg1> class threadholder1 : public thread_base { Func function_; // 関数ポインタを保持 Arg1 arg1_; // 第一引数のコピーを保持 public: threadholder1(Func func, Arg1 arg1) : function_(func) : arg1_(arg1) {} void run() { function_(arg1_); // threadクラスに渡された関数を実行 } };
これで引数1個の関数を実行するための用意が出来ました。
次にthreadクラスのコンストラクタに引数1つを取る関数を実行するためのバージョンを追加します。
// 引数1個の関数を実行するためのコンストラクタ template <typename Func, typename Arg1> thread(Func func, Arg1 arg1) : hThread_(0) , threadId_(0) , p_(new threadholder1<Func>(func, arg1)) // 引数1個の関数ポインタを保持するクラスを生成 { thread_start(); }
これで引数を1取る関数を実行することが出来るようになりました。
以下threadクラスに追加したものです。
// thread.hpp #include <memory> // std::auto_ptr #include <process.h> // ::_beginthreadex #include <stdexcept> // std::runtime_error #include "thread_base.hpp" #include "threadholder0.hpp" // 引数0個の関数を実行するクラス #include "threadholder1.hpp" // 引数1個の関数を実行するクラス // スレッド作成に失敗したことを通知するexceptionクラス class thread_resource_error : public std::runtime_error{}; class thread { HANDLE hThread_; // スレッドのハンドル unsigned int threadId_; // スレッドのID thread_base* p_; // スレッド実行クラス private: // _beginthreadexに渡す関数 static unsigned int __stdcall workerThread(void* arg) { std::auto_ptr<thread_base> p(reinterpret_cast<thread_base*>(arg)); // ポインタをauto_ptrに渡して自動的に開放 p->run(); // threadクラスに渡された関数を実行 return 0; } // スレッド開始関数 void thread_start() { hThread_ = reinterpret_cast<HANDLE>(::_beginthreadex(0, 0, &thread::workerThread, p_, CREATE_SUSPENDED, &threadId_)); if (!hThread_) { throw thread_resource_error(); } ::ResumeThread(hThread_); } public: // 引数0個の関数を実行するためのコンストラクタ template <typename Func> explicit thread(Func func) : hThread_(0) , threadId_(0) , p_(new threadholder0<Func>(func)) // 引数0個の関数ポインタを保持するクラスを生成 { thread_start(); } // 引数1個の関数を実行するためのコンストラクタ template <typename Func, typename Arg1> thread(Func func, Arg1 arg1) : hThread_(0) , threadId_(0) , p_(new threadholder1<Func>(func, arg1)) // 引数1個の関数ポインタを保持するクラスを生成 { thread_start(); } ~thread() { ::CloseHandle(hThread_); } // スレッドの実行を待つ関数 int join() { return static_cast<int>(::WaitForSingleObject(hThread_, INFINITE)); } };
以上で引数つき関数を実行するための拡張の仕方は終わりです。
これと同じ拡張を繰り返すことでthreadクラスに渡せる関数の引数を増やすことが出来ます。
boostではthreadクラスのコンストラクタでboost::bindを使用して引数9個まで渡せるように拡張が施されていますね。
このthreadクラスにはまだ問題が残っています。
それはthreadがコピー可能ではないこと、コピーされた場合にデストラクタで行う::CloseHandleで二重開放が起きることです。
次回はこれをコピー可能にし、二重開放されないように修正してみましょう。