2009年11月アーカイブ

概要: むずかしいです。でもうつくしいです。

OpenCVでカメラからのキャプチャを行うような事をC++/CLIでやろうとしています。で、まあリソース管理の為のコードを書くのは避けて通れない訳で、Disposeパターンに従って作ろうとしたら、public: virtual void Dispose(bool);が通らない......予約されていると? と思って見てみたら、こんなページがMSDN Libraryにありました。

URIが日本語用(ja-jp)なのに翻訳されていないというのはとりあえず置いておいて、C#に慣れた頭で読んでみると驚きの連続でした。ついでに眠いせいか難しい。一通り読んだだけでは収拾がつかないので、デストラクタ(Wikipedia)なども見つつ重要そうな所を僕なりにちょっとまとめてみます。

C++/CLIにおけるデストラクタとファイナライザ

デストラクタ

リソース(特にアンマネージドリソース)を積極的に解放する為にあるメソッド。リソースが要らなくなったらすぐ呼ばれるべきもので、スコープを抜ける時に自動的に呼ばれたり、プログラマが明示的に呼んだりする。

C++/CLIではデストラクタ構文(~classname())で表される。.NET FrameworkではIDisposable.Dispose()に相当する。C#はこいつを実装する事でデストラクタの機能を提供する。

ファイナライザ

リソースの解放し忘れによる被害を最小限に抑える為にあるメソッド。ガベージコレクタが使う最後の砦。インスタンスが占有する領域を解放したくなった時、リークが無いかどうか調べて、もしあれば解放する(デストラクトする)為のもの。ガベージコレクタが解放したくならなければ(=メモリがいっぱいあれば)呼ばれない事もあり得る。

C++/CLIではファイナライザ構文(!classname())で表される。.NET FrameworkではObject.Finalize()に相当する。C#におけるファイナライザは、C#ではデストラクタと呼ばれているらしく(初めて知った気がするが気のせいか)、デストラクタ構文(~classname())で表される。

C#のファイナライザがデストラクタと呼ばれているのが混乱を招きそうですが、まあC#の~classname() <=> Finalize()と最初から覚えてしまえば良さそう。

マネージドリソースとアンマネージドリソース

色んな所で出てきますが、おさらい。

マネージドリソース
ガベージコレクタによって管理されるリソース。ガベージコレクタの管轄であるマネージドヒープに作られたオブジェクトの事。C++/CLIのref classオブジェクトとかC#のclassオブジェクトなんかもこれ。幸運にもガベージコレクタが管理できるという色が強い、と思う。
アンマネージドリソース
ガベージコレクタの管轄外にあるリソース。例えばCのmallocや標準C++のnewなどによって、ガベージコレクタが監視できないヒープに確保されたオブジェクトなどが含まれる。ファイルハンドルも代表例の一つ。ガベージコレクタは残念ながらこいつらを自動的に解放する事はできない。

ガベージコレクションにおけるファイナライゼーションの順番

これは、保証されません。マネージドリソースAが別のマネージドリソースBへの参照を持っていた場合などはAからファイナライズすればいいように思えますが、更にBがAへの参照を持つ事もあり得るので、そうなるとどちらからやればいいかは自明ではありません。勿論規則を明確にすればどのような場合でも一意に決められるでしょうが、想像してみると判るように、それを実現する為のコストは割と高い(外部環境の影響を受けないように追加情報(例えばインスタンス化時刻とか)を載せなければならない)ですし、第一人間がそんなもの把握しきれません。

となると、です。オブジェクトAのファイナライザが呼ばれた時には、オブジェクトBのファイナライザは既に呼ばれているかも知れません。ファイナライゼーションが済んでしまえばガベージコレクタはそのオブジェクトが占有していたメモリ領域を解放しますから、今までBへの正しい参照だったものは危険な(使ってはいけない)参照になります。そしてBのファイナライゼーションが既に行われたかどうかを知る術はAにはありません。ファイナライザでは、マネージドリソースへの参照を使ってはいけないのです。

もしBがアンマネージドリソースだった場合は逆で、むしろ責任を持って解放してやる必要があります。ガベージコレクタがアンマネージドリソースをファイナライズする事はありませんし、BがAによってのみ所有されていれば、AはBのファイナライゼーションが済んだかどうかは正確に把握できるはずです。

Disposeパターン

こうなってくると、デストラクタでは場合分けが必要になる事が判ります。明示的に(ファイナライゼーションよりも早い段階で)デストラクトされる場合と、ファイナライゼーションによってデストラクトされる場合です。この為に、C#などではboolをパラメータとするDisposeメソッドを用意する事が推奨されています。Disposeパターンと呼ばれるようです?

public class MyClass {
  // disposingがtrueであればマネージドリソースのデストラクトも行う
  // アンマネージドリソースのデストラクトはdisposingの値に依らず行う
  // 派生クラスでやる内容変わるだろうからvirtualに
  protected virtual void Dispose( bool disposing ) {
    try {
      if ( disposing ) {
        // (マネージドリソースのデストラクト)
      }
      // (アンマネージドリソースのデストラクト)
    }
    finally {
      // もし親がいれば
      //base.Dispose( disposing );
    }
  }
  // デストラクタ
  // マネージド、アンマネージド両方とも解放
  public void Dispose() {
    // 明示的なデストラクション
    this.Dispose( true );
    System.GC.SuppressFinalize( this );
  }
  // ファイナライザ(デストラクタ構文)
  // アンマネージドリソースのみ解放
  ~MyClass() {
    // ガベージコレクタによるデストラクション
    this.Dispose( false );
  }
}

C++/CLIにおけるDisposeパターン等価コード

先程のコードを、C++/CLIで書くと次のようになります。

public ref class MyClass {
  // デストラクタ
  // マネージド、アンマネージド両方とも解放
  ~MyClass() {
    // (マネージドリソースのデストラクト)
    this->!MyClass();
  }
  // ファイナライザ
  // アンマネージドリソースのみ解放
  !MyClass() {
    // (アンマネージドリソースのデストラクト)
  }
};

美しい。これで等価だそうです。SuppressFinalize(Object)の所まで含めて。

このように書くと、デストラクタ構文の方は強いデストラクションをやっていて、ファイナライザ構文の方は弱いデストラクションをやっているようにも見えてきます。

まとめ

ま、上に書いた事がすべてではありますが、一応。

  • C++/CLIでは、DisposeやFinalizeなどのメソッドは文字通り書く事は無い。デストラクタ構文、ファイナライザ構文を使う。
  • ファイナライザでは他のマネージドリソースをデストラクトするべきではない
  • 明示的にデストラクタが呼ばれればマネージドリソースも含めてデストラクトするべきで(強いデストラクション)、
  • ガベージコレクタのファイナライゼーションで初めてデストラクタが呼ばれるようなら、マネージドリソースはデストラクトすべきではない(弱いデストラクション)。
  • C++/CLIでは、強いデストラクションをデストラクタ構文で、弱いデストラクションをファイナライザ構文で書く
  • つまり、デストラクタではマネージドリソースを解放した上でファイナライザを呼び出し
  • ファイナライザでアンマネージドリソースを解放する

うん。読みながら理解しようとしていた時には混乱しかけていたけれど、まとめてみれば何て事無い、とても素直で綺麗な設計だと思います。さすがはリソース管理にうるさい(?)C++が前身の言語と言うべき? 気をつける事と言えば、C#ではファイナライザ(デストラクタ構文)がDispose(bool)を呼び出すのが普通なので、一見逆の事をやっているように見える事ですね。実際やっている事は同じだと。ふむ。

概要: OpenMPを利用すると、さほど労力をかけずプログラムを並列化できるようです。自分ではまだ試していませんがかなり面白そう。

自分で頑張ってスレッド作って、複数スレッド間で頑張って同期したりしなくても、コード中にヒントを書いておけば勝手に並列化してくれる(と思う)OpenMPなんてのがあるそうです。概要掴むにはWikipediaにあるOpenMPの記事が役に立ちます。

当然ながらアプリケーションの複数スレッド間で資源が競合しないようにする為には元のソースコードに対する深い知識が必要ですが、OpenMPはコンパイラが使うミドルウェアといった感じのようです。コンパイラによるサポートが必要ですが、gccのC++コンパイラやVisual C++のclといった(独断と偏見によれば)メジャーなコンパイラではサポートされているようです。コンパイラは当然元のソースコードをよく知っていますから、うまく動くって訳ですね。ちなみにVisual C++の場合はVisual C++ の OpenMPを読むと良さそう。

こいつを知ったのは、最近OpenCVを入れたのですが、そこでOpenMPが使われていたからです。なるほど画像処理でも局所性が高いものが多いでしょう。スレッドを分ける要請は無いものの、CPU余ってるなら使いたいと。で、OpenMPという訳か。

ミドルウェアで思い出しましたが、Globusってのもあったなあ。こちらは複数台のコンピュータを使って一つのタスクをする(つまり、グリッドコンピューティングをする)為のミドルウェアだそうですが、前から入れよう入れよう思っているものの全然調べてない......。まあうちのとこのサーバ群も遊んでるマシンは少なくなってきてはいますが。

概要: Movable Type 5の出荷日が11月26日に決まったそうです。同時期に移転したい所です。

おお、ようやく!

案の定下旬(11月26日)ですが、まあ11月中には違いない。

さあ、こっちの方も暇を見つけて頑張らなきゃなあ。本業(?)の方と並行して、相補的に進めばいいんですけど。

このアーカイブについて

このページには、2009年11月に書かれたブログ記事が新しい順に公開されています。

前のアーカイブは2009年10月です。

次のアーカイブは2010年2月です。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。