SAFE_RELEASE
/SAFE_DELETE
を知らない人々は幸いである。
SAFE_RELEASE MUST DIE
こんな感じのマクロ:
// "安全な"COMポインタの解放処理マクロ
#define SAFE_RELEASE(p) if(p){ p->Release(); p=NULL; }
// "安全な"配列の解放処理マクロ
#define SAFE_DELETE(p) if(p){ delete[] p; p=NULL; }
あくまでC++言語のお話。C言語?知らない子ですね。
C言語でCOMを操作するとか、きがくるっとる。
慈悲は無い
SAFE_RELEASE
やSAFE_DELETE
、またはそれに類するマクロは、絶対に使わないこと。
見た目だけC++ templateに変えても(SafeRelease
)、本質的には同じである。何も改善していない。絶対に使わないこと。
- 嬉々としてこのマクロを紹介するWebサイトを見かけたら、インターネット回線を切断してからWebブラウザを閉じ、該当サイトURLをブロックリストに追加しましょう。
- このマクロを使っているソースコードを見かけたら、エディタをそっと閉じてPCに向かって清めの塩をまき、ファイルをランダムデータで3回上書きしてから削除しましょう。
- このマクロを使っているプログラマを見かけたら、<censored>。
あと、MSDNはいい加減になんとかしてくれ。C++ templateに変えたからといって、決して許されるものでは無い。この誤訳に至っては、翻訳者は<censored>
スマートポインタ is awesome.
腐臭漂うC時代の負債マクロではなく、C++コンパイラやC++ライブラリが提供する「スマートポインタ(Smart Pointer)」を利用すること。絶対に。
COMオブジェクト参照カウントのリリース(IUnknown::Release()
)を行いたければ、以下のスマートポインタが利用できます。
- Active Template Library(ATL)の
CComPtr
クラス,CComQIPtr
クラス - Visual C++コンパイラ組込の
_com_ptr_t
クラス - Boost.Smart Pointersライブラリの
intrusive_ptr
クラス
動的確保された配列の解放(delete[]
)を行いたければ、以下のスマートポインタが利用できます。
- C++11標準ライブラリの
unique_ptr
クラス - Boost.Smart Pointersライブラリの
scoped_array
クラス,shared_array
クラス
というより、動的配列を確保している大半のケースでは、単にC++標準コンテナ std::vector
クラス で間に合います。
- 有効要素数(
size()
)とバッファ容量(capacity()
)という属性情報を、セットで管理しています。動的配列ではプログラマの責任で別途管理が要りますが、面倒くさいしバグの温床にしかなりません。 -
std::vector<T>
の全要素は、連続したメモリ領域に配置されると保証されています。例えばT=char
とすれば汎用の「メモリバッファ」として使えます。 -
operator[]
を介した要素アクセスコストまで気にするなら、走査直前にT* ptr = &vec[0];
とでもしてptr[n]
アクセスすればゼロオーバーヘッドになります。
感情論はこれくらいにして、理性的な解説も
SAFE_RELEASE
マクロは、何がどう駄目なのかを列挙していきます。よくありそうなCOMクライアントコードを考えます。
bool my_func()
{
ISomething *pObj = NULL;
IFoo *pFoo = NULL;
HRESULT hr;
// Somethingクラスを作成(ISomethingインターフェイス取得)
hr = CreateComObject(CLSID_Something, &pObj);
// (戻り値hrチェック省略)
// IFooインターフェイスを取得
hr = pObj->QueryInterface(IID_Foo, &pFoo);
if (FAILED(hr)) {
// 関数を抜ける前にはReleaseを忘れずに!
SAFE_RELEASE(pObj);
return false;
}
// 別関数another_funcを呼び出し
if (!another_func(pFoo)) {
// 関数を抜ける前にはReleaseを忘れずに!
SAFE_RELEASE(pFoo);
SAFE_RELEASE(pObj);
return false;
}
// まだまだ処理が続くよ...
// 関数を抜ける前にはReleaseを忘れずに!
SAFE_RELEASE(pFoo);
SAFE_RELEASE(pObj);
return true;
}
ATL提供のスマートポインタを使うと、次のように書けます:
#include <atlbase.h>
bool my_func()
{
// Somethingクラスを作成(ISomethingインターフェイス取得)
CComPtr<ISomething> pObj;
HRESULT hr = CreateComObject(CLSID_Something, &pObj);
// (戻り値hrチェック省略)
// IFooインターフェイスを取得
CComQIPtr<IFoo, &IID_Foo> pFoo = pObj;
// QueryInterfaceが呼び出される
if (!pFoo)
return false; // 自動的にReleaseされる
// 別関数another_funcを呼び出し
if (!another_func(pFoo)) {
return false; // 自動的にReleaseされる
}
// まだまだ処理が続くよ...
return true; // 自動的にReleaseされる
}
- マクロ版では結局プログラマの責任で、関数
return
の直前で解放処理を記述しなければなりません。コード構造が変更されたり、新しいインターフェイスが追加された時に、対応する解放処理の追記を忘れるとリソースリークします。スマートポインタ版では自動的に解放してくれます。 - マクロ版は例外安全ではありません。
another_func()
や後続処理からC++例外が送出されて関数my_func
を抜ける場合、インターフェイスはReleaseされずにリソースリークします。スマートポインタ版では例外による関数脱出であっても安全です。 - マクロ版はスマートポインタ版に比べて、本質的な処理コードに対するBookkeepingコードの比率が高くなり、ソースコードの**"ノイズ"が増えます**。コードの可読性が低くなることで、保守性・拡張性にも悪影響を与えます。
- C++を使っているのですから、変数宣言を関数先頭で行うのではなく。代入するタイミングで宣言すべきです。変数スコープを必要最小限に限定することは、ソースコードの可読性/保守性/拡張性を改善し向上させます。(マクロ版でも実施可能ですが、レガシーコードでは大抵C言語スタイル、つまり関数先頭で一括宣言するケースが多いようです。)