下記のような関数があるとします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | - | ! - | | | - - ! | ! | | ! | |
処理内容は下記の通りです。
std::to_wstring 関数で文字列に変換する。CreateMutexW 関数を呼び出し、ミューテクスを作成。GetLastError 関数の戻り値が ERROR_ALREADY_EXISTS ならば既に作成済みのミューテクスなので解放して nullptr を返す。特に問題はなさそうに見えますが、ある特定の状況だとこの関数は意図通りに動きません。
その状況とは、グローバルの delete 演算子がオーバロードされ、その中で GetLastError 関数の内部値が上書きされた場合です。
上記の例では直接 SetLastError 関数を呼んでいますが、実際には何らかの Win32 API が呼ばれているものと思ってください。
先のコードの、 CreateMutexW 関数を呼んでいる文は次の通りです。
8 | |
std::to_wstring(id) で作成した std::wstring は特に変数等に束縛されていないため、この文の処理が完了した時点でデストラクタが呼び出されます。
std::wstring のデストラクタは、内部に保持しているバッファを delete 演算子によって解放します。
よって、 GetLastError 関数を呼び出すよりも前に operator delete が呼び出され、内部値が上書きされてしまいます。
GetLastError 関数を利用するコードと delete 演算子のオーバロードをどちらも自身で書いたならば気付けるかもしれませんが、下記のような場合は非常に気付きにくいです。
GetLastError 関数を利用するコードが入っている場合。delete 演算子がオーバロードされている場合。グローバルの new, delete 演算子のオーバロードはリンクしているライブラリにまで波及するのがとても厄介です。
両者の組み合わせが一番最悪で、どちらかのソースコードを修正可能でなければどうしようもなくなってしまいます。
私は、自身で作ったライブラリと Unreal Engine 4 (ゲームエンジン)の組み合わせでこの問題にハマり、原因判明までに丸1日を要しました…。
個人的には「グローバルの new, delete 演算子をオーバロードするのはやめろ」と言いたいのですが、外製のフレームワークにやられていたらどうしようもないですし、 GetLastError 関数の仕組みが危険なのもまた事実なので、注意してコーディングするしかないですかね。
なお、今回のコード例であれば、 std::to_wstring(id) の結果を一旦変数に受け取ることで問題を回避できます。
より安全にするなら、最初に OpenMutexW 関数によって既存のミューテクスが存在するか確認するようにすれば、 GetLastError 関数を使わずとも同等の処理が可能です。