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

Resource Acquisition Is Initialization (2)

.NET

C++/CLI の特徴のひとつは,従来の C++ でしばしば用いられていた RAII イディオムを用いて .NET Framework が依拠する Dispose パターンを実装できることです.
http://c2.com/cgi/wiki?ResourceAcquisitionIsInitialization
Herb Sutter 氏は自らの blog で,(C++的な) Destructor と GC は対立するものではなく,協力することでメリットが得られると述べられています.
http://pluralsight.com/blogs/hsutter/archive/2004/11/23/3666.aspx
例として次のようなC++/CLIのコードを見てみます.

public ref class MyFileReader
{
    private:
        FileStream _file;
    public:
        MyFileReader( String^ path, FileMode mode, FileAccess access ) : _file(path, mode, access)
        {
        }
};

public ref class MyTest
{
public:
    static void Main()
    {
        MyFileReader reader( "hogehoge.txt", FileMode::Open, FileAccess::Read);
        // ....
    }
};

ここでは検証可能コードについて見てみたいので,/clr:safe オプションを使用してコンパイルします.Visual C++ 2005 beta 2 を用いました.生成されたアセンブリから Reflector for .NET を用いて C# での等価コードを得たものが以下です.生成された IL は,従来 C# で推奨されてきた形での Dispose パターンと同じであることが分かります.

public class MyFileReader : IDisposable
{
    // Methods
    public MyFileReader(string path, FileMode mode, FileAccess access)
    {
        FileStream modopt(IsConst) local1 
            = (FileStream modopt(IsConst)) new FileStream(path, mode, access);
        try
        {
            this._file = local1;
        }
        fault
        {
            this._file.Dispose();
        }
    }

    public void ~MyFileReader()
    {
        this._file.Dispose();
    }

    public void Dispose()
    {
        this.Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose([MarshalAs(UnmanagedType.U1)] bool flag1)
    {
        if (flag1)
        {
            this._file.Dispose();
        }
        else
        {
            base.Finalize();
        }
    }

    // Fields
    private readonly FileStream modreq(IsByValue) _file;
}

public class MyTest
{
    public static void Main()
    {
        MyFileReader reader1;
        MyFileReader modopt(IsConst) local1 = 
        (MyFileReader modopt(IsConst)) new MyFileReader("hogehoge.txt", FileMode.Open, FileAccess.Read);
        try
        {
            reader1 = local1;
        }
        fault
        {
            reader1.Dispose();
        }
        reader1.Dispose();
    }
}

もちろん C++/CLI には従来 C++ で用いられてきた new やポインタをマネージ対応させたものも存在します.C++/CLI では変数の宣言方法の違いで出力される IL がどのように変化するかについて詳しく知りたければ,以下の記事を眺めてみるとよいでしょう.
http://www.bluebytesoftware.com/blog/PermaLink.aspx?guid=88e62cdf-5919-4ac7-bc33-20c06ae539ae
また,Reflector for .NET の結果からは,カスタム修飾子*1を駆使して他のマネージ言語との型互換性を確保していることが伺えます.C++/CLI コンパイラが自動変数や(埋め込まれた)メンバ変数のように扱う参照型変数/フィールドは,実際の IL レベルではいくつかの修飾子が付加されていることを除いて通常の参照型の変数/フィールドと代わりありません.

*1:IsByValueIsConstなどが用いられている模様