etolisp 進捗 (22) 〜 ガーベージコレクタの実装
C++/CLI を少しいじって,メモリ管理から開放される気楽さを知ってしまった。興味がわいたので,A garbage collection framework for C++の実装を参考にお手軽 gc_ptr を実装した。正確にいうと,リンク先の gc_ptr の実装を理解しただけ,ということになる。個人的に,static オブジェクトを作る設計は避けたかったので,その辺り,いくつか気になる部分を修正した意外は,概ねオリジナルのまま利用することにした。
ガーベージコレクタの最適化の話は,それだけで分厚い本になっているくらいなので,効率の良いものを片手間にチョイと実装,というのは,ハッキリ言って現実的ではないと思う。しかし,取りあえず動く程度のものであれば,リンク先のコードのように,数10行で実現できてしまう。千里の道も一歩から,である。
導入はいたって簡単で
- new の代わりに new(gc)
- type* の代わりに gc_ptr
を使うだけである。C++としての違和感がとても少ないのが嬉しい。さらに,ガーベージコレクタで管理したいクラスの定義を以下のマクロ
#ifdef _WIN32 #define managed_ptr(type) type^ #else #define gcnew new(gc) #define managed_ptr(type) gc_ptr<type> #endif //_WIN32
を使って定義しておけば,C++/CLI にそのまま移行できる…かも知れない。デストラクタも同じ原理で
#ifdef _WIN32 #define virtual_destructor(type) !type() #else #define virtual_destructor(type) virtual ~type() #endif //_WIN32
などとすれば。たぶん。
etolispでは今まで,自分が複雑なS式を書けないのを良いことに,メモリ管理はauto_ptrおよび参照カウンタ方式のshared_ptrで誤摩化していたけど,これでようやく,参照カウンタ方式の穴である,循環参照しているオブジェクトがリークする問題が解決する。ついでに実装もスッキリするんじゃないかな。
ただ,記事でも触れられている通り,オブジェクトをポインタ値で管理しているので,多重継承を利用している場合は注意が必要。C++では,同一オブジェクトを指していても,基底クラスが複数ある場合は,基底クラスの型によって,ポインタ値が変わることがあるためだ*1。おそらく gc_ptr から異なる型の gc_ptr への代入は,継承リストの一番最初にくるクラスを辿るような親子間でしかサポートされないだろう。しかも,そのサポートもコンパイラ依存となる(Visual C++はたぶんOK)。あ,でも,よく考えたらこれ,代入時にチェック可能だね。