COMのアパートメント (3) スレッドの属性とコンポーネント

さてと、アパートメントという用語がどのあたりから来るのか匂わせておいたところで、2つの軸をどのように対処するのかを見ていく。

一つ目は、ファサードの有無の選択だ。
main関数で例を示したように、このままではクラスの呼び分けをする必要がある。
そこで、newする実装を隠ぺいすることを考える。たとえば、以下のような関数を用意する。

IAdd *CreateInstance(const bool threadSafe)
{
 IAdd *pInstance = new AddCalculator();

 // スレッドセーフなコンポーネントが必要なら
 if (threadSafe == true)
 {
  return new AddCalculatorFacade(pInstance);
 }
 else
 {
  return pInstance;
 }
}

呼び出す側は、スレッドセーフな実装が必要であればtrueを、そうでなければfalseを与えれば良い。しかし、これではクラスの型を指定するのが、boolの値にすり替わっただけだ。
そこで、いよいよコンポーネント自身にスレッド親和性の情報を付加する時がやってきた。

まず、コンポーネントの実装に、コンポーネント自身のスレッドセーフ属性を加える。これはコンポーネントのインスタンスが生成される前に参照する必要があるので、static関数とする。

class AddCalculator : public IAdd
 {
 public:
 // 途中省略
 static bool IsThreadSafe() throw()
 {
  // このコンポーネントはスレッドセーフではない
  return false;
 }
 };

class AddCalculator2 : public IAdd
 {
 public:
 // 途中省略
 static bool IsThreadSafe() throw()
 {
  // このコンポーネントはスレッドセーフだ
  return true;
 }
 };

IAdd *CreateInstance()
{
 // コンポーネントのスレッドセーフ属性を得る
 const bool componentIsThreadSafe = AddCalculator::IsThreadSafe();
 // const bool componentIsThreadSafe = AddCalculator2::IsThreadSafe();
 IAdd *pInstance = new AddCalculator();

 // コンポーネントがスレッドセーフでなければ
 if (componentIsThreadSafe == false)
 {
  // ファサードを挟んでスレッドセーフにする
  return new AddCalculatorFacade(pInstance);
 }
 else
 {
  return pInstance;
 }
}

まだ納得いかないだろう。特にスレッドセーフ属性を取得するくだりは、コンポーネントの型が分かっているのだから、意味がないように見えるかもしれない。今はテンプレートを使ってお茶を濁しておく。テンプレート経由だが、コンポーネントが自身でスレッドセーフ属性を提供している事がわかると思う。

template <typename T>  IAdd *CreateInstance()
{
 // コンポーネントのスレッドセーフ属性を得る
 const bool componentIsThreadSafe = T::IsThreadSafe();
 IAdd *pInstance = new T();

 // コンポーネントがスレッドセーフでなければ
 if (componentIsThreadSafe == false)
 {
  // ファサードを挟んでスレッドセーフにする
  return new AddCalculatorFacade(pInstance);
 }
 else
 {
  return pInstance;
 }
}

int main()
{
 // まだ実質的な変化はない...
 IAdd *pInstance = CreateInstance<AddCalculator>();
 // IAdd *pInstance = CreateInstance<AddCalculator2>();

 // 使う...
 delete pInstance;
 return 0;
}

上のコードに意味が見いだせないのは当然で、今までやって来た事の形を変えただけだからだ。「ついに」、もう半分の肝心な部分を付け足す。それは、現在のスレッドの指標を加味することだ。

// スレッドローカルストレージ(スレッド毎に保存される変数)
static __declspec(thread) bool g_MultiThreadBound = false;

// 現在のスレッドにスレッド属性を設定する
void SetThreadBound(const bool isMultiThreaded) throw()
{
 g_MultiThreadBound = isMultiThreaded;
}

template <typename T>  IAdd *CreateInstance()
{
 // コンポーネントのスレッドセーフ属性を得る
 const bool componentIsThreadSafe = T::IsThreadSafe();
 IAdd *pInstance = new T();

 // 現在のスレッドがマルチスレッドで使用する前提でかつ、
 // コンポーネントがスレッドセーフでなければ
 if ((g_MultiThreadBound == true) && (componentIsThreadSafe == false))
 {
  // ファサードを挟んでスレッドセーフにする
  return new AddCalculatorFacade(pInstance);
 }
 // コンポーネントがスレッドセーフなら、現在のスレッドがどのような属性でも問題ない。
 // コンポーネントがスレッドセーフでなくても、現在のスレッドがシングルスレッドでのみ使用する属性なら問題ない。
 else
 {
  return pInstance;
 }
}

さて、これで、コンポーネントを使用する側にとっては、スレッドの状態を表明さえすれば、そのコンポーネントがスレッドセーフだろうが、そうでなかろうが、安全に使用できるようになった。使用者はコンポーネントがスレッドに対して安全であるかどうかを考えなくてもよくなったという事だ。
シングルスレッドで使用するなら、

int main()
{
 // シングルスレッドでのみ使用する場合
 SetThreadBound(false);

 // どちらの実装を使ったとしても、最適なインスタンスが提供される。
 IAdd *pInstance = CreateInstance<AddCalculator>();
 // IAdd *pInstance = CreateInstance<AddCalculator2>();

 // 使う...
 delete pInstance;
 return 0;
}

マルチスレッドで使用するなら、

// スレッドのエントリポイント
int ThreadEntryPoint()
{
 // マルチスレッドで使用する場合
 SetThreadBound(true);

 // どちらの実装を使ったとしても、最適なインスタンスが提供される。
 IAdd *pInstance = CreateInstance<AddCalculator>();
 // IAdd *pInstance = CreateInstance<AddCalculator2>();

 // 使う...
 // (場合によっては別のスレッドにポインタを渡して使うかも)
 delete pInstance;
 return 0;
}

なんとなく、普段COMでやっていることに近づいてきたのが分かるだろうか。
つづく。


最新のCOM記事:

COMについて、ローカルディスカッションで大幅に拡充してまとめ上げた記事があるので、そちらもどうぞ:「ChalkTalk CLR – COMのすべて」