真に安全なsafe_delete

ポインタをdeleteした後は、対象のポインタの値をnullにした方が安全で良いプラクティスだとされています。

毎回deleteする度にクリア処理を書くのは手間です。

マクロ化してみましょう。

一昔前に良く見かけたマクロです。

マクロはプリプロセスで展開されます。単に展開され、コンパイルエラーもマクロの中とは別の場所を示します。そして何より名前空間を指定することができません。取り回しが良いとは言いづらいですね。

Effective C++でも、かなり最初の方に#defineを使うことへの注意が書いてあります。

言い換えれば「プリプロセッサよりコンパイラを使おう」ということです。#defineは、一般には、言語の一部としては扱われません。この事実から問題が起こることもあるのです。

__Effective C++ 第3版

従いましょう。

引数はTのポインタの参照です。

Tのポインタの参照として受けることでnullptrの代入が呼び出し元に反映されます。間違ってT*だけにしてしまうとこの関数の外に戻った時にポインタの値が元のアドレスのままになってしまうので気をつけて下さい。

 

ここで一旦、safe_deleteのことは忘れて、以下の処理の流れを考えてみます。

前方参照されただけのHogeという型のポインタのインスタンスを生成して、deleteに渡すコードです。

この場合、コンパイラはHogeという型の詳細を知りません。

別のファイルでHogeの中身が定義してあるとしても、このソースのコンパイル時にその情報を持っていません。しかしHogeは前方参照されており、このコード中はポインタでしか扱っていない為、型サイズやメンバ変数の内容を知らなくてもコンパイルできてしまいます。

コンパイラによっては警告を出しますが、シンタックスエラーという訳でもないのでコンパイルは通ります。

Wandbox上でもコンパイルが通ることが確認できます。

なぜ不完全型がコンパイルエラーじゃないかというと、実のところ、不完全型をdeleteに渡すことは規格で許可されているからなのですが、但し書きが付きます。

独自のdelete演算子を定義していたり、トライバルなデストラクタを持たないクラスの場合、規格の保証外となり、未定義動作になります。

未定義動作は、単に不具合の元となりますので絶対にやってはいけません。

絶対にやってはいけないことは、絶対にできないように書く必要があります。

先ほどのsafe_deleteに不完全型をコンパイルエラーにする仕組みを導入しましょう。ランタイムではなくコンパイル時にエラーにするのが肝要です。

不完全型が渡された時に配列の要素が負数になり、そんなものはコンパイラが許さないのでエラーになります。

みんな大好きなboost::checked_deleteも同様のコンセプトの元、用意されています。

これで無事、完全なsafe_deleteを実装することができました。

簡単ですね!

実装する際は配列バージョンも用意するのをお忘れなく!

その先へ

安全なsafe_deleteの実装は完了しました。

ただこれはsafe_deleteの実装が安全なのであって、safe_deleteを使う環境が安全になったわけではありません。同僚プログラマがsafe_deleteを呼び忘れるリスク等を考慮するとスマートポインタなどの使用も検討しましょう。

 

まとめ

  1.  マクロによるSAFE_DELETEは不十分
  2. 不完全型のエラー対応をしよう
  3. 実装する際は配列版も忘れずに
Pocket

「真に安全なsafe_delete」への2件のフィードバック

    1. フィードバックを受けて加筆致しました。ありがとうございます。

コメントは停止中です。