C++でsynchronized methodを書くのは難しい (3)

方式cの改良です。まず、「初期化順」の問題を避けるためには、コンストラクタを呼び出すことなく初期化が済んでいるオブジェクトでなければなりません。そのためには、mutexオブジェクトを

// 方法c' (仮)

void Foo::need_to_sync(void) {
  static StaticMutex mutex = { PTHREAD_MUTEX_RECURSIVE_INITIALIZER_NP, ........ };

のように初期化できなければなりません。一般的なC++のクラスはこういう形の初期化は許されません。このような初期化を実現するためには、上記StaticMutexクラスはPOD型でなければなりません。POD型というのは、規格によると

  • コンストラクタがあってはダメ
  • デストラクタがあってはダメ
  • コピーコンストラクタ、代入演算子コンパイラが生成しないとダメ
  • private, protected なメンバがあってはダメ
  • 仮想関数があってはダメ

を満たす型のことです*1


随分厳しい制約ではありますが、「non-virtualなメンバ関数を定義するのは問題がない」などの性質を利用して、方式cを改良した方式c'を考案してみます。


...次で如何でしょう?

// 方式c'

#define POD_MUTEX_MAGIC 0xdeadbeef
#define STATIC_MUTEX_INITIALIZER           { PTHREAD_INITIALIZER,              POD_MUTEX_MAGIC }
#define STATIC_RECURSIVE_MUTEX_INITIALIZER { PTHREAD_RECURSIVE_INITIALIZER_NP, POD_MUTEX_MAGIC }

class PODMutex {
public:
  void Lock() {
    assert(magic_ == POD_MUTEX_MAGIC);
    pthread_mutex_lock(&mutex_);
  }
  void Unlock() {
    assert(magic_ == POD_MUTEX_MAGIC);
    pthread_mutex_unlock(&mutex_);
  }
  typedef ScopedLock<PODMutex> ScopedLock;

public:
  // POD型にするにはpublicにしないと駄目
  pthread_mutex_t mutex_;
  const unsigned int magic_;
};

// ScopedLockクラステンプレートは方法e,f用に作成したものを流用

void Foo::need_to_sync(void) {
  static PODMutex mutex = STATIC_RECURSIVE_MUTEX_INITIALIZER;
  {
    PODMutex::ScopedLock lock(mutex);

    // 処理

  }
  return;
}

pthread_mutex_t型を隠蔽する、ScopedLock<>を流用する、という先の目標を両方満たしています。少しはC++らしくなったのではないでしょうか。なお、PODMutex型は上記の例のような局所的静的変数以外にも、グローバル変数、クラス変数としても安心してお使いいただけます。


なお、メンバ変数 magic_ は、constなメンバ変数を置くことで、コンパイラによって自動生成されたコンストラクタによるオブジェクト生成をエラーにする意図で入れてありますが、リリースビルドからは取り除いたほうが良いでしょう。


上記のコードをg++ -Sでコンパイルし、アセンブリリストを吐かせてみると、局所的な静的変数 mutex は次のようになっています。

$ g++ -S sample.cpp
$ c++filt < sample.s | lv
(略)
        .size   Foo::need_to_sync()::mutex, 28
Foo::need_to_sync()::mutex:
        .long   0
        .long   0
        .long   0
        .long   1
        .long   0
        .long   0
        .long   -559038737

0,0,0,1,0,0 というのが PTHREAD_RECURSIVE_INITIALIZER_NP の部分、-559038737 が POD_MUTEX_MAGIC の部分です。動的初期化を行わずとも(コンストラクタを呼ばずとも)、オブジェクトファイル上に書かれた通りに静的初期化を行うだけでmutexオブジェクトが正常に初期化されることがわかります。OK。


ちなみにboostライブラリを使う場合は、方法f以外に選択の余地は(いまのところ)ないですね。MLなどを見ると、オーダー順の問題が出る可能性は(当然!!)理解しているが、方法c'相当のPOD-Mutexを移植性*2と速度を確保しつつ作成する方法がいまのところない、ということらしいです。


以上

*1:詳しくは ISO/IEC 14882:2003 あるいは JIS X 3014:2003 の「§3.9/10 C互換型」「§8.5.1/14 静的記憶期間をもつC互換型の集成体の波括弧で囲んだ初期化子並びによる静的な初期化」「§9/4 C互換構造体」を参照のこと

*2:Windowsはどうする?という命題を解決できないのかもしれません - 推測