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ネイティブの同期プリミティブ(例:POSIXのpthread_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を参照。