読者です 読者をやめる 読者になる 読者になる

チリペヂィア

リンクフリー。サンプルコードなどは関連記事内でライセンスについて明示されない限り商用利用なども自由に行って構いませんが、自己責任でお願いします。またこれら日記内容の著作権自体は放棄していません。引用部分については引用元の権利に従ってください。

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();
}
メタセコプラグイン開発状況

プログラマブルな頂点色焼き込みに着手したんですが、検証項目だらけで大変遅れており枡。うわああああ週メコどころか月メコも怪しい!