LoginSignup
11
9

More than 5 years have passed since last update.

SAFE_RELEASE MUST DIE

Last updated at Posted at 2015-03-20

SAFE_RELEASE/SAFE_DELETEを知らない人々は幸いである。

SAFE_RELEASE MUST DIE

こんな感じのマクロ:

wtf.cpp
// "安全な"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_RELEASESAFE_DELETE、またはそれに類するマクロは、絶対に使わないこと。

見た目だけC++ templateに変えても(SafeRelease)、本質的には同じである。何も改善していない。絶対に使わないこと。

  • 嬉々としてこのマクロを紹介するWebサイトを見かけたら、インターネット回線を切断してからWebブラウザを閉じ、該当サイトURLをブロックリストに追加しましょう。
  • このマクロを使っているソースコードを見かけたら、エディタをそっと閉じてPCに向かって清めの塩をまき、ファイルをランダムデータで3回上書きしてから削除しましょう。
  • このマクロを使っているプログラマを見かけたら、<censored>。

あと、MSDNはいい加減になんとかしてくれC++ templateに変えたからといって、決して許されるものでは無い。この誤訳に至っては、翻訳者は<censored>

スマートポインタ is awesome.

腐臭漂うC時代の負債マクロではなく、C++コンパイラやC++ライブラリが提供する「スマートポインタ(Smart Pointer)」を利用すること。絶対に。

COMオブジェクト参照カウントのリリース(IUnknown::Release())を行いたければ、以下のスマートポインタが利用できます。

動的確保された配列の解放(delete[])を行いたければ、以下のスマートポインタが利用できます。

というより、動的配列を確保している大半のケースでは、単にC++標準コンテナ std::vectorクラス で間に合います。

  • 有効要素数(size())とバッファ容量(capacity())という属性情報を、セットで管理しています。動的配列ではプログラマの責任で別途管理が要りますが、面倒くさいしバグの温床にしかなりません。
  • std::vector<T>の全要素は、連続したメモリ領域に配置されると保証されています。例えばT=charとすれば汎用の「メモリバッファ」として使えます。
  • operator[]を介した要素アクセスコストまで気にするなら、走査直前にT* ptr = &vec[0];とでもしてptr[n]アクセスすればゼロオーバーヘッドになります。

感情論はこれくらいにして、理性的な解説も

SAFE_RELEASEマクロは、何がどう駄目なのかを列挙していきます。よくありそうなCOMクライアントコードを考えます。

legacy_macro.cpp
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提供のスマートポインタを使うと、次のように書けます:

modern_sp.cpp
#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される
}
  1. マクロ版では結局プログラマの責任で、関数returnの直前で解放処理を記述しなければなりません。コード構造が変更されたり、新しいインターフェイスが追加された時に、対応する解放処理の追記を忘れるとリソースリークします。スマートポインタ版では自動的に解放してくれます。
  2. マクロ版は例外安全ではありませんanother_func()や後続処理からC++例外が送出されて関数my_funcを抜ける場合、インターフェイスはReleaseされずにリソースリークします。スマートポインタ版では例外による関数脱出であっても安全です。
  3. マクロ版はスマートポインタ版に比べて、本質的な処理コードに対するBookkeepingコードの比率が高くなり、ソースコードの"ノイズ"が増えます。コードの可読性が低くなることで、保守性・拡張性にも悪影響を与えます。
  4. C++を使っているのですから、変数宣言を関数先頭で行うのではなく。代入するタイミングで宣言すべきです。変数スコープを必要最小限に限定することは、ソースコードの可読性/保守性/拡張性を改善し向上させます。(マクロ版でも実施可能ですが、レガシーコードでは大抵C言語スタイル、つまり関数先頭で一括宣言するケースが多いようです。)
11
9
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
9