ポインタをdeleteした後は、対象のポインタの値をnullにした方が安全で良いプラクティスだとされています。
1 2 |
delete p; // deleteした後 p = nullptr; // null値にクリア |
毎回deleteする度にクリア処理を書くのは手間です。
マクロ化してみましょう。
1 |
#define SAFE_DELETE(p) {delete p; p = nullptr;} |
一昔前に良く見かけたマクロです。
マクロはプリプロセスで展開されます。単に展開され、コンパイルエラーもマクロの中とは別の場所を示します。そして何より名前空間を指定することができません。取り回しが良いとは言いづらいですね。
Effective C++でも、かなり最初の方に#defineを使うことへの注意が書いてあります。
言い換えれば「プリプロセッサよりコンパイラを使おう」ということです。#defineは、一般には、言語の一部としては扱われません。この事実から問題が起こることもあるのです。
__Effective C++ 第3版
従いましょう。
1 2 3 4 5 6 7 |
// SAFE_DELETEを関数化したもの // コンパイラによる型チェック、名前空間の指定も可能 template <class T> inline void safe_delete(T*& p) { delete p; p = nullptr; } |
引数はTのポインタの参照です。
Tのポインタの参照として受けることでnullptrの代入が呼び出し元に反映されます。間違ってT*だけにしてしまうとこの関数の外に戻った時にポインタの値が元のアドレスのままになってしまうので気をつけて下さい。
ここで一旦、safe_deleteのことは忘れて、以下の処理の流れを考えてみます。
1 2 3 4 5 6 7 |
struct Hoge; int main() { Hoge* hoge; delete hoge; return 0; } |
前方参照されただけのHogeという型のポインタのインスタンスを生成して、deleteに渡すコードです。
この場合、コンパイラはHogeという型の詳細を知りません。
別のファイルでHogeの中身が定義してあるとしても、このソースのコンパイル時にその情報を持っていません。しかしHogeは前方参照されており、このコード中はポインタでしか扱っていない為、型サイズやメンバ変数の内容を知らなくてもコンパイルできてしまいます。
コンパイラによっては警告を出しますが、シンタックスエラーという訳でもないのでコンパイルは通ります。
なぜ不完全型がコンパイルエラーじゃないかというと、実のところ、不完全型をdeleteに渡すことは規格で許可されているからなのですが、但し書きが付きます。
独自のdelete演算子を定義していたり、トライバルなデストラクタを持たないクラスの場合、規格の保証外となり、未定義動作になります。
未定義動作は、単に不具合の元となりますので絶対にやってはいけません。
絶対にやってはいけないことは、絶対にできないように書く必要があります。
先ほどのsafe_deleteに不完全型をコンパイルエラーにする仕組みを導入しましょう。ランタイムではなくコンパイル時にエラーにするのが肝要です。
1 2 3 4 5 6 7 8 9 |
template <class T> inline void complete_type_safe_delete(T*& p) { // 不完全な型のポインタをdeleteしようとした時にコンパイルエラーにする typedef char type_must_be_complete[ sizeof(T)? 1: -1 ]; (void)sizeof(type_must_be_complete); delete p; p = nullptr; } |
不完全型が渡された時に配列の要素が負数になり、そんなものはコンパイラが許さないのでエラーになります。
みんな大好きなboost::checked_deleteも同様のコンセプトの元、用意されています。
これで無事、完全なsafe_deleteを実装することができました。
簡単ですね!
実装する際は配列バージョンも用意するのをお忘れなく!
その先へ
安全なsafe_deleteの実装は完了しました。
ただこれはsafe_deleteの実装が安全なのであって、safe_deleteを使う環境が安全になったわけではありません。同僚プログラマがsafe_deleteを呼び忘れるリスク等を考慮するとスマートポインタなどの使用も検討しましょう。
1 |
std::shared_ptr<Object> obj = std::make_shared<Object>(); |
まとめ
- マクロによるSAFE_DELETEは不十分
- 不完全型のエラー対応をしよう
- 実装する際は配列版も忘れずに
http://qiita.com/yohhoy/items/35bc7176061d4313ff25
> SAFE_RELEASEやSAFE_DELETE、またはそれに類するマクロは、絶対に使わないこと。
>
> 見た目だけC++ templateに変えても(SafeRelease)、本質的には同じである。何も改善していない。絶対に使わないこと。
フィードバックを受けて加筆致しました。ありがとうございます。