汎用Deleter
C++ではメモリを自分で管理しなければならないので面倒だ.でもnewを使わずにスタック上に作ったオブジェクトは自動的に解放されるので,一時的にスコープのなかだけで使うオブジェクトなんかだとあまり気にする必要は無い.
ところが,一時的に作るオブジェクトであってもnewで作らざるを得ない場合がある.Factoryパターンを使って,多相性を持つオブジェクトを作る場合なんかがそれ.下のFactoryクラスのgetメソッドは引数に応じてベースクラスとサブクラスを返すが,このようなメソッドを実体返しで記述することはできない.
class Factory { public: TargetBase * get(bool sub){ if (sub) return new TargetSub(); return new TargetBase(); } };
従って,このようなオブジェクトは,必ずヒープ上に取られてしまうことになる.
手動のdeleteは危険
ヒープ上に取られたからにはプログラマがdeleteしてやらなければならない訳だけれど,これは結構面倒.とくに例外で抜けられる場合などは,対処が非常に難しい.
Factory f; TargetBase * t = f.get(true); some_function_call(); delete t;
と書いておいても,deleteの前に呼び出される関数の中から例外で脱出されるとdeleteのパスを通らなくなってしまう.特にC++では関数はthrowする例外を必ずしも宣言しなくても良いので,自分で書いた関数でもない限り,ある関数呼び出しから例外で脱出するかどうかはわからない.
汎用Deleter
これに対処する方法としてDeleterというものを考えてみた.と言ってもきっと定番の方法なんじゃないかと思うのだけど.
スタック上のオブジェクトは自動的にdeleteされる訳だから,スタック上に別のオブジェクトをつくって,それにポインタを渡しておいて,スタック上のオブジェクトが解放されるときに,目的のオブジェクトも解放してもらえばいい.実装は超簡単.
template < class T > class Deleter { T * t; public: Deleter(T * t):t(t){} ~Deleter(){delete t;} };
使い方はこんな感じ.deleteを呼び出していないことに注意.これで,_dのスコープを抜けたときに自動的にtも解放される.
Factory f; TargetBase * t = f.get(true); Deleter<TargetBase> _d(t); some_function_call();
例外で抜ける場合でも_dは解放されるので巻き添えでtも解放される.めでたしめでたし.