C++/CLIでDisposableなクラス継承を確認
C++/CLIだとDisposeパターンがちょっと特殊なのは、解説サイトがいくつもあって助かるんですが、Disposableなクラスを継承してさらにDisposeを実装する時の呼出実験が見当たらないので確認します(アレもしかして検索下手なだけ???)。コンストラクト/デストラクトまわりは安易に仮想継承できないワンダーランド。どうなってるんでしょうか。
とりあえずC++/CLIのベタなDisposeパターンで継承します。
環境:VC2010/Net4.0
#include <tchar.h> public ref class BaseClass { ~BaseClass() { System::Console::WriteLine( "BaseClass::destruct" ); this->!BaseClass(); } !BaseClass() { System::Console::WriteLine( "BaseClass::finalize" ); } }; public ref class DerivedClass : BaseClass { ~DerivedClass() { System::Console::WriteLine( "DerivedClass::destruct" ); this->!DerivedClass(); } !DerivedClass() { System::Console::WriteLine( "DerivedClass::finalize" ); } }; int _tmain(int argc, _TCHAR* argv[]) { DerivedClass^ instance = gcnew DerivedClass(); delete instance; return System::Console::Read(); }
実行結果
DerivedClass::destruct DerivedClass::finalize BaseClass::destruct BaseClass::finalize
やりおる。delete処理で親クラスのDisposeはきちんと後回しで勝手に呼び出されました。子のファイナライズが親のデストラクトに先行するのは考えてなかったんですが、言われてみれば確かに子のリソースは全て解放しないと親は安心して親側のリソースを解放できない原則通り。
ちなみに出来上がったExeをILSpyを使ってC#形式にリバエンしてみるとこんな風でした。
public class BaseClass : IDisposable { private void ~BaseClass() { Console.WriteLine("BaseClass::destruct"); this.!BaseClass(); } private void !BaseClass() { Console.WriteLine("BaseClass::finalize"); } [HandleProcessCorruptedStateExceptions] protected virtual void Dispose([MarshalAs(UnmanagedType.U1)] bool flag) { if (flag) { this.~BaseClass(); } else { try { this.!BaseClass(); } finally { base.Finalize(); } } } public sealed override void Dispose() { this.Dispose(true); GC.SuppressFinalize(this); } protected override void Finalize() { this.Dispose(false); } } public class DerivedClass : BaseClass { private void ~DerivedClass() { Console.WriteLine("DerivedClass::destruct"); this.!DerivedClass(); } private void !DerivedClass() { Console.WriteLine("DerivedClass::finalize"); } [HandleProcessCorruptedStateExceptions] protected override void Dispose([MarshalAs(UnmanagedType.U1)] bool flag) { if (flag) { try { this.~DerivedClass(); } finally { base.Dispose(true); } } else { try { this.!DerivedClass(); } finally { base.Dispose(false); } } } }
という具合。IDisposableなベースクラスを継承してる/してないで、ちょっと違う形に置換するみたいですね。
蛇足ですが、delete処理のところをのぞいてみるとこんな風に置換されています。
// C# DerivedClass instance = new DerivedClass(); IDisposable disposable = instance; if (disposable != null) { disposable.Dispose(); }
IDisposableキャストを通しています。ところで、
// CPP/CLI
BaseClass^ instance = gcnew DerivedClass();
としても大丈夫(Dispose(void)がsealedで、内部でDipose(bool)をvirtual呼び出し)。入れ物側の型情報には左右されずに、あくまでもinstanceに投入されたDerivedClassインスタンスのDispose(bool)が呼び出されます。それと、いきなりIDisposableに突っ込んで大丈夫かいなと思って、明らかにDisposableではない可能性のあるSystem::Objectインスタンスのdeleteをトライ。
// CPP/CLI System::Object^ obj = gcnew System::Object(); delete obj;
これはきちんとas演算子(dynamic_cast相当)が挟まれます。わりとケースバイで展開されるようですね。
// C# object obj = new object(); IDisposable disposable = obj as IDisposable; if (disposable != null) { disposable.Dispose(); }