yohhoyの日記

技術的メモをしていきたい日記

なぜmutexオブジェクトはムーブできないか?

C++標準ライブラリが提供するstd::mutexほか同期プリミティブ型*1は、意図的にコピー/ムーブ操作を禁止している。本記事の内容はStack Overflowで見つけた質問と回答に基づく。

#include <mutex>

class X {
  int data_ = 0;
  std::mutex mtx_;
  using Lock = std::lock_guard<std::mutex>;
public:
  int  get()      { Lock lk(mtx_); return data_; }
  void set(int v) { Lock lk(mtx_); data_ = v; }
};
// コンパイラにより暗黙定義されるコピー/ムーブコンストラクタは、
// メンバmtx_がコピー/ムーブ不可のた暗黙にdelete宣言される。

X func() { return {}; }

X x = func();  // NG: X型はムーブ不可

概念上std::mutexはファイルポインタのように “外部リソースを所有する” クラスではなく、クラス自身が “リソースそのもの” である。また一般にOSネイティブの同期プリミティブ(例:POSIXpthread_mutex_t)はアドレス独立でなく*2、ムーブ操作の禁止によってそのメモリアドレスを固定化することで、OSネイティブ同期プリミティブを直接データメンバとして保持する内部実装が許容される。

std::mutexクラスは静的初期化(static initialization)のためconstexprコンストラクタを提供するが、ムーブ操作を許容しかつOSネイティブ同期プリミティブがアドレス独立でない場合、ヒープメモリ確保を避けられずC++標準ライブラリ要件を満たせなくなる。

おまけ:C++1z(C++17)ではP0135R1が採択され、X x = func();にてcopy elisionが保証されムーブコンストラクタを要求しなくなる。つまり上記コードはC++1z以降でwell-definedに変更される。

関連URL

*1:std::timed_mutex, std::recursive_mutex, std::recursive_timed_mutex, std::shared_mutex[C++1z以降], std::shared_timed_mutex, std::condition_variable, std::condition_variable_any

*2:例えばLinuxのpthread実装NPTLで用いられるfutexシステムコールは、アドレス値そのものに対して待機操作を行う。NPTL詳細は(PDF)The Native POSIX Thread Library for Linuxを参照。