C++ では、new をしたら必ず delete しなければいけません。
これについては前回説明しました。
この「~をしたら必ず~しなければならない」というのは、new と delete のペアだけではなく、malloc() と free()、fopen() と fclose()、HeapAlloc() と HeapFree()、CreateFileMapping() と CloseHandle()、CryptAcquireContext() と CryptReleaseContext() 等々、いろいろあります。
また、こういった、ペアで使用しなければならない関数やクラスを自分で作ることもあるでしょう。
これらも new, delete のペアと同じで、途中で例外が発生して後処理が呼ばれないという状況にならないようにする必要があります。
前回はそのための方法として、try, catch で囲む方法と、スマートポインタを使用する方法を説明しました。
当然スマートポインタを使う方法をお勧めします。
そのためにはスマートポインタがオブジェクトに対して任意の後処理を行える必要があって、時々それが出来ないスマートポインタもあったりするのですが、今はとりあえず除外します。
例えば boost::share_ptr で fopen() と fclose() を扱うのであれば、
struct Deleter
{
void operator()(FILE* p)
{
if (p != NULL)
{
::fclose(p);
}
}
};
boost::shared_ptr<FILE> file((::fopen("hoge.txt", "rb"), Deleter());
こんな感じでしょうか。
Deleter という関数オブジェクト(operator() を定義したクラス)を使用しているのがポイントで、これを変更することで任意の後処理を行うことが出来るようになります。
HeapAlloc(), HeapFree() であれば、
struct Deleter
{
void operator()(void* p)
{
if (p != NULL)
{
::HeapFree(::GetProcessHeap(), 0, p);
}
}
};
boost::shared_ptr<void> heap(::HeapAlloc(::GetProcessHeap(), 0, 256), Deleter());
こんな感じです。
boost::shared_ptr 等の汎用的な後処理が可能なスマートポインタを使うと、自作のクラスだろうが何だろうが全てスマートポインタで済ますことが可能になります。
こうすれば解放忘れや例外発生によるリークを防ぐことが出来るようになり、また、解放漏れのことを考えず、簡単にソースを読めるようになるため、堅牢性・保守性・可読性の高いコードになるのではないかと思っています。
まあ前回も言った通り、やっぱりスマートポインタを使いこなせるようになるまでが面倒なんですが、それでもスマートポインタを勉強する価値はあると思います。
おまけ:
スマートポインタの実装の多くは、ポインタの参照数を管理するための領域を new によって確保しているけれども、もしこれが失敗したらセットしようとしたポインタがリークしてしまうやん?とか思って boost::shared_ptr の実装を見てみました。
try
{
pi_ = new sp_counted_impl_pd<P, D>( p );
}
catch(...)
{
d(p);
throw;
}
sp_counted_impl_pd は p の参照数を管理するためのクラスで、d は Deleter() のことです。
new の部分を try, catch で囲んで、失敗した場合はちゃんとポインタを削除してから例外を再送しているようです。
さすが Boost Library、自分が懸念を覚えるなんて100年早いと言わんばかりの実装ですw