ロック機構の実装に手間取りました。
これまでロック機構というと、クライアント コードは下記のように実装してきていたのですが…、
CriticalSection aCs;
{
Lock<CriticalSection> aLock(aCs);
// このブロック中はロックされる
}
このうち、
Lock<CriticalSection> aLock(aCs);
のように毎度ロック用の型を明示するのが面倒くさくて、パラレル・フォースでは
auto aLock = lock(aCs);
のように記述できるよう lock メソッドの実装を進めていました。せっかく左辺値の型推論が有効なのですから、型名は極力省略したいものです。
さて、この lock メソッド実装の課題は 2 つ。
メソッドの型は下記を想定します。
template <class LockObject> Lock<LockObject> lock(LockObject& iLockObject);
C++0x より前の枠組みで素直に実装すると、下記のようになってしまいます。
template <class LockObject> Lock<LockObject> lock(LockObject& iLockObject) { return Lock<LockObject>(iLockObject); }
これでは Lock<> インスタンスのコピーが 1 度以上発生してしまいます。RVO を期待したところで、それはコンパイラの最適化ヒントにつき、言語仕様上、上記のコードでコピーを抑制することはできません。
コピー抑制について、C++0x を知る前の自分では、戻り値を std::auto_ptr
いろいろ調べた結果、よりよい解決方法は C++0x の右辺値参照にありました。
右辺値参照の詳細は他のサイトや書籍に譲りますが、概要としては std::auto_ptr のような破壊的セマンティクスを C++ の文法レベルで導入したものです。lock メソッドにおいては、Lock<> にムーブ コンストラクタとムーブ演算子を実装した上で、下記のように実装しました。
template <class LockObject> Lock<LockObject> lock(LockObject& iLockObject) { Lock<LockObject> aLock(iLockObject); return std::move(aLock); }
これで、Lock<> インスタンスのコピーは抑制され、コンストラクタ以上のオーバーヘッドは、ムーブ コンストラクタ & ムーブ演算子に記載のポインタコピー程度となり、非常に満足いく結果が得られました。
CriticalSection aCs; { auto aLock = lock(aCs); // このブロック中はロックされる }
その後、スレッド関連オブジェクトのロックだけでなく、イメージの画素ロックにも Lock<> クラスを使用するようになり、部分特殊化したテンプレートクラスに画素データへのアクセサを用意した都合上、lock メソッドはもう少し変化していますが、おおむね上記のような感じで収まっています。クライアントコードには変化はなく、当初の目的だった、クライアントコードを極力省略することそれ自体はきちんと達成されています。
んー。C++0x 便利。素敵過ぎます。