C++/CLI

C++/CLIの最新ニュースをまとめて検索!

C++/CLIは、.NET Framework共通言語基盤 (CLI)上で実行するプログラムを作るための、C++を拡張したプログラミング言語である。前身であるC++マネージ拡張に比べて単純でわかりやすい構文になり、可読性も向上した。

C++/CLIはEcma Internationalで標準化されている[1]。C++/CLIに対応したコンパイラはVisual C++ 2005以降である。

目次

[編集] 構文の変化

マネージ拡張C++がC++に独自拡張を加えたスーパーセットであったのに対し、C++/CLIはそれ自身が1つの言語である(ただしC++とは上位互換である)。それにより曖昧な識別子がなくなったり、.NET固有の仕様に適合するような機能の追加が行われるなどの大きな変更も加えられている。

もっとも大きな構文の違いとしてはnew演算子が挙げられる。C++/CLIでは.NETの参照型インスタンスを作るための演算子をgcnewに分離した。また、.NETのジェネリックに対応する構文も追加された。

[編集] ハンドル

マネージ拡張C++には、2種類のポインタが存在した。従来からのC++ポインタである__nogcポインタと.NETの参照型オブジェクトを指す__gcポインタである。一方C++/CLIでは、ポインタはC++のポインタしかなく、.NETの参照型のオブジェクトを指すものは「ハンドル」と呼称することになった。ハンドル型はクラス名*に代わってクラス名^という構文を使う。これにより、.NETでガベージコレクションされるオブジェクトとそうでないものとが明確になり、マネージドとアンマネージドが混合しているコードが分かりやすくなった。

// マネージ拡張C++
#using <mscorlib.dll>
using namespace System::Collections;
using System::String;
__gc class referencetype
{
protected:
    String* stringVar;
    int intArr __gc[];
    ArrayList* doubleList;
public:
    referencetype(String* str, int* pointer, int number) // どれがマネージ型だろうか?
    {
        doubleList = new ArrayList();
        intArr = new int __gc[8];
        System::Console::WriteLine(String::Concat(str->Trim(), number.ToString()));
    }
};
// C++/CLI
#using <mscorlib.dll>
using namespace System::Collections::Generic;
using System::String;
ref class referencetype
{
protected:
    String^ stringVar;
    array<int> intArr;
    List<double>^ doubleList;
public:
    referencetype(String^ str, int* pointer, int number) // 曖昧でない
    {
        doubleList = gcnew List<double>();
        intArr = gcnew array<int>(8);
        System::Console::WriteLine(str->Trim() + number); //Stringの連結に+演算子が使用可能となった
    }
};

[編集] 追跡参照

C++/CLIの追跡参照トラッキング参照)は値ではなく参照で渡されるハンドルである。これらはC#の"ref"やVisual Basic .NETの"ByRef"に相当する。C++/CLIはハンドルへの追跡参照を示すのに"^%"という構文を使用する。これは標準C++で"*&"(ポインタへの参照)を用いた構文に似ている。

下記のコードは追跡参照の使用例である。仮に、下のコードでString^ sと変えてしまうと、参照ではなく値を渡すことになるため、sは配列にセットされた文字列ハンドルをコピーするだけとなる。そのため、arrの各要素は初期化されないままになってしまう。

int main()
{
    array<String^>^ arr = gcnew array<String^>(10);
    int i = 0;
 
    for each(String^% s in arr)
        s = gcnew String(i++.ToString());
}

加えて上記のコードは、例えばC#ではこれに直接対応する方法でforeachループを利用できないように、.NET言語がちょっと違う動作をするという例になる。つまりC#ではforeach(ref string s in arr)はforeachループが参照で値を渡せないので間違いであり、この場合はその他の回避策が使われる。

[編集] ファイナライザと自動変数

そのほかの変化として、C++/CLIではガベージコレクション時に実行されるファイナライザの構文が!クラス名()となったことが挙げられる。そして~クラス名()は従来のC++と同じ意味のデストラクタとなった。さらに、下の例にあるような新しい構文では(従来のC++と同じく)デストラクタは自動的に呼ばれる。CIL上では、C++/CLIのデストラクタはIDisposableインターフェイスのDisposeメソッドとして実装される(C++/CLIコンパイラがそのようにコンパイルする)。このためC++/CLIでも引き続きRAIIが可能である。

通常.NETで管理されないリソース(ネットワーク・データベース接続、ファイルシステムなど)の解放にデストラクタを使うことが推奨されているが、それ以外の場合はファイナライザで十分であろう。また、デストラクタが使われるときは常に、デストラクタが既に呼ばれたかどうかを確認し、呼ばれていなければデストラクタを呼ぶという内容のファイナライザも作るべきである。(メモリ・リソースの漏れを防ぐためである)

// C++/CLI
ref class MyClass // : IDisposable (これはコンパイラが追加する)
{
public:
    MyClass();  // コンストラクタ
    ~MyClass(); // [デストラクタ (コンパイラによってIDisposable::Dispose()にされる)
 
    static void Test()
    {
        MyClass auto; // ハンドルでなく初期化子も無い:コンパイラがコンストラクタを呼ぶ。
        // 変数autoは、このメソッド内であればuserと同じように使える。
        MyClass^ user = gcnew MyClass();
        try { /* userを使う */ } finally { delete user; }
        // コンパイラはメソッド全体を包むfinallyを作り、その中で変数autoのデストラクタを呼ぶ。
    }
protected:
    !MyClass(); // ファイナライザ(以前はvirtual void Finalize()という構文だった)
};
// C#
class MyClass : IDisposable
{
    public MyClass() {} // コンストラクタ
    ~MyClass() {} // デストラクタ (C++/CLIのファイナライザはprotected override void Finalize()に相当)
    public void Dispose() {} // Dispose メソッド (C++/CLIではデストラクタ)
 
    public static void Test()
    {
        using (MyClass auto = new MyClass()) { /* autoを使う */ }
        // コンパイラはusingブロックを抜けるときにauto.Dispose()を呼ぶ。
        // つまり下のコードに等しい。
        MyClass user = new MyClass();
        try { /* userを使う */ } finally { user.Dispose(); }
    }
}

[編集] 演算子の多重定義

アンマネージドのC++に関しては演算子の多重定義はおおむね正確に働く。すべての*は^となり、すべての&は%となるが、それ以外の構文はそのままでも多重定義を実装できる。また、それに加えてクラス自身に対してだけでなくそれらのクラスへのハンドルに対しても演算子多重定義が可能となった(従来のC++ではポインタ型同士に対して多重定義できなかった)。また、CLIに適合するため演算子の多重定義をクラスの静的メンバとして実装することも可能になった。.NET Frameworkの参照クラスでもハンドルを引数に取る演算子の多重定義は静的メンバとして実装されている。

これは、中の文字列が同一なら、2つの異なるStringの参照を==演算子で比較しても、(Stringの==演算子の多重定義によって)結果がtrueとなることを意味する。もちろん、マネージドコードを書くときだけに限らず、常にそうあるべきであるように、演算子の多重定義は多態的でない。従って、Object ^へのキャストは多重定義のセマンティクスから逃れることになる。

//参照演算子の多重定義の効果
String ^s1 = "abc";
String ^s2 = "ab" + "c";
Object ^o1 = s1;
Object ^o2 = s2;
s1 == s2; // true
o1 == o2; // false

標準的なセマンティクスではネイティブ型や値型、仮に型Tに対しては、従来のC++のようにTやT const&を引数に取る演算子を定義し、参照クラス型Rに対してはハンドルR^を引数に取る演算子を定義することになる。ただ、C++だけのプロジェクトでは、ハンドル型を引数に取る演算子多重定義を使わないようにする、つまり参照クラスに対しても従来のC++の演算子の多重定義方式のように参照 (R const%)を引数に取るという手段も考えられる。そのような例は、コピーコンストラクタ(演算子ではないが)や代入演算子の実装で使われることが考えられる。

[編集] 関連項目

[編集] 外部リンク

最終更新 2009年9月15日 (火) 07:26 (日時は個人設定で未設定ならばUTC)。
【C++/CLI】変更履歴

ご利用上の注意