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で二重開放が起きることです。
次回はこれをコピー可能にし、二重開放されないように修正してみましょう。