multiple_lock_guard で型推論する

元ネタ:
http://cpplover.blogspot.com/2011/06/multiplelockguard.html


いちいち multiple_lock_guard< std::mutex, std::mutex, std::mutex > とか書くのは面倒なので,
関数にして auto で束縛できるようにしてみました.
なお,テスト等は全く行っていないので,使う場合は自己責任でお願いします.

#include <tuple>
#include <memory>
#include <mutex>

template< class Mutex >
struct unlock
{
  void operator()( Mutex* m ) const {
    m->unlock();
  }
};

// クラスではなく関数にする(型推論しやすいよう)
template< class... Args,
  class Result = std::tuple<std::unique_ptr<Args, unlock<Args>>...>
>
Result make_lock_guard( Args&... args )
{
  std::lock( args... );
  return Result( std::addressof(args)... );
}


専用のクラスを作るのではなく, unique_ptrtuple を組み合わせています.
これなら copy 禁止も move 対応もしなくていいので,かなり楽です.

std::mutex m1, m2, m3 ;

int main()
{
  auto lock = make_lock_guard( m1, m2, m3 ) ;
} 

このように使います.

追記

std::unique_lockadopt_lock を使うという手もありました.
unique_ptr を使ったときに比べて空間効率は悪化しますが,標準ですし,こちらを使うのも良いと思います.

#include <tuple>
#include <memory>
#include <mutex>

// 補助関数. 受け取った mutex を adopt_lock する
template< class Mutex >
std::unique_lock<Mutex> adopt_unique_lock( Mutex& m )
{
  return std::unique_lock<Mutex>( m, std::adopt_lock );
}

// クラスではなく関数にする(型推論しやすいよう)
template< class... Args,
  class Result = std::tuple<std::unique_lock<Args>...>
>
Result make_lock_guard( Args&... args )
{
  std::lock( args... );
  return Result( adopt_unique_lock(args)... );
}


std::mutex m1, m2, m3;

int main()
{
  auto lock = make_lock_guard( m1, m2, m3 );
}

// make_lock_guard って名前はイマイチだなぁ….

さらに追記

http://d.hatena.ne.jp/Cryolite/20110721#p1

defer_lock は知らなかった. これは便利ですね.

template<class Mutex>
inline std::unique_lock<Mutex> make_deferred_lock( Mutex& m ) {
  return std::unique_lock<Mutex>( m, std::defer_lock );
}

といったヘルパ関数を用意して,

std::mutex m1, m2, m3;

void f() {
  auto lk1 = make_deferred_lock(m1);
  auto lk2 = make_deferred_lock(m2);
  auto lk3 = make_deferred_lock(m3);
  std::lock( lk1, lk2, lk3 );

  // 処理

}

という感じで処理すれば, std::unique_lock の変数にアクセスしやすいので,
変に std::tuple に詰め込むより,ずっとコーディングが楽になりそうです.


とはいえ, std::unique_lock は 若干オーバースペックな気もしないでもないので,
std::unique_ptr を使った方法も,悪くはないんじゃないかなー,とか.

もいっちょ追記

std::unique_lock を製作するヘルパ関数を幾つか書いたけど,よく考えたら,

template< class Mutex, class... Args >
inline std::unique_lock<Mutex> make_unique_lock( Mutex& m, Args&&... args ) {
  return std::unique_lock<Mutex>( m, std::forward<Args>(args)... );
}

こういうヘルパ関数が一つ有れば,それで十分なことに気づきました.

std::mutex m1, m2;

void f()
{
  // 単純にロックする
  auto lk = make_unique_lock( m1 );

  // 処理...

}

void g()
{
  // try lock
  auto lk = make_unique_lock( m1, std::try_lock );

  if( lk ) { // あるいは if( auto lk = make_unique_lock(...) ) としてもよい
    // 処理...
  }
}

void h()
{
  // adopt lock
  std::lock( m1, m2 );
  auto lk1 = make_unique_lock( m1, std::adopt_lock );
  auto lk2 = make_unique_lock( m2, std::adopt_lock );

  // 処理...

}

void i()
{
  // defer lock
  auto lk1 = make_unique_lock( m1, std::defer_lock );
  auto lk2 = make_unique_lock( m2, std::defer_lock );
  std::lock( lk1, lk2 );

  // 処理...

}

なので,これからは,これを使おうかと.

// っていうか,これは標準ライブラリで用意するべき関数な気がが