2010/03/22(月) あばらんち
C++/CLIについてのよくある誤解
以前にも書いたような気がしますが、もう一度書きます。C++/CLI(仕様の邦訳)は、.netからC++やその他ネイティブコードのライブラリを楽に使えるようにするためと、そのための作業を楽に行うための言語です。「.netで使えるC++」ではありません。そういうことを言う人は悉くこの言語の厳つい顎で頭を砕かれ、手足をもがれて、腸を食らい千切られ死にます。それが摂理なのです。あらがえません。C++/CLIは大きく分けてC++と、C++風構文を持つ何か.netっぽい言語(ここでは仮に「Cヰ」と呼びましょう)からなる、キメラです。二つの言語のコードは一つのファイルに混ぜて書くことができます。C++の部分はそのままC++です。何も違いはありません。GCもありません。ありませんが.netのGCにネイティブオブジェクト(要するにC++で作ったオブジェクト)を通知して管理させる方法はあります。interfaceだとか多重継承できないとかそういうのもありません。interfaceはあったような気もしますがVCのC++に対する独自拡張なので知りません忘れてください。今拡張という言葉が出てきましたが、C++/CLIはC++の拡張ではなく、別の言語です。これは真面目にです、別の言語としての規格がちゃんと存在します。それから以前はC++のマネージ拡張というのがありましたがそれも忘れてください。こっちはC++への拡張ですが、構文が違うだけです。誤解しないでほしいですが、私はC++/CLIがクソである、と言いたいわけではありません。しかしマネージ拡張はクソです。
話を戻しましょう。どんな感じの言語かは、ここを見ればだいたい掴めると思います。掴んでください。C++は普通にC++ですが、Cヰはどうでしょうか。まずtemplateが使えません。代わりにgenericsが使えますが、これは構文がC++風なだけで.netのそれそのものです。今、「だったら何も問題なくね?」と思いましたか?
template<typename T>
typename F::result_of_foo func(T & obj) {
return obj.foo();
}
とても人為的な例ですが、まずこれ相当のものは書けません。今から理由を説明しますので慌てないでください。とりあえず、これと同じことを意図したCヰの擬似コードを示します(あくまでも擬似です)。
generic<typename T>
??? func(T ^ obj) { // ^ 記号はC++の*(ポインタ修飾)みたいなものです。
return obj->foo();
}
断わっておきますが、Cヰではメンバに属さないメソッドをアセンブリの外に公開できません。このへんはCLIの仕様によるところです。なので実際にはCヰのクラス(これはあとで説明します)の静的メソッドにするべきですが、ここでは対比として分かりやすくするためにこう書いておきます。ちなみに名前のガイドラインも守ってませんが、そのへんはもういいでしょう。あなたはもう大人なのですから、今言いたいことの本質がそこではないことぐらい分かるはずです。
まずobj.foo()というメソッド呼び出しがコンパイルエラーとなります。それと、???を書く術がありません。C++の真似してるクセに使えなさすぎです。ですがこれには訳があります。
genericなメソッドやクラスは、コンパイル時だけで世界が閉じるわけにはいきません。そのようなメソッドを含むプログラムから生成されたアセンブリを利用する側からでも、彼らが指定した型でメソッドを利用できるような仕組みになっています。当然アセンブリを利用する側が一体どのような型でこのメソッドを利用するのか分かりません。分からないということはコードを生成できません。困りました。でも.netだとできています。実はgenericなメソッドやクラスは、Tが何であろうと同じコードで動いています。つまりTによってコードを生成することはありません。これは半分嘘です。Tが値型の場合はコードを生成しなければいけないこともありますが、そのへんの細かいことは今はいいです。話を進めます。型パラメータTに対して、Tが持つべき特徴を指定します。
interface class IFoo {
Foo ^ foo();
};
generic<typename T>
where T : IFoo
Foo ^ func(T ^ obj) {
return obj->foo();
}
where節が登場しました。これはTがIFooを実装したクラスでなければならない、という制約です。ということは、この関数におけるTは必ずfooというメソッドを持っているはずです、これで、たとえ別のアセンブリに存在する型であっても、IFooを実装しさえすればこの関数を呼び出せます。何せ型に合わせた関数のコードを吐くのではなく、関数のコードを決め打ちにして、引数となる型の構造のほうを関数に合わせたのですから当然です。これでこのメソッド呼び出しは、一つのコードで実現できます。おめでとうございます。しかしこの例だけでは Foo ^ func(IFoo ^ obj); と比べてどう便利なのかという話です。何をおっしゃいますやら、私にも分かりません。ですが generic<typename T> where T : INantoka List<T> bar(Bar<T>); という関数とかそういうのを考えればありがたみが分かると思います。ところが残念なことに、私は今とてもおなかがすいているので次にいきます。今煙に巻きました。
.netにはデリゲートという機能があります。Boost.Signals2相当です。ところがこれはCヰからだと使いにくいです。ちょっとここから例を拝借します。3番目のサンプルコードをC++/CLIで書いてみました。
/// <summary>
/// メッセージを表示するだけのデリゲート
/// </summary>
delegate void ShowMessage();
ref class Person
{
System::String name;
public:
Person(System::String name){this.name = name;}
void ShowName(){Console.Write("名前: {0}\n", this.name);}
};
int main()
{
Person ^ p = gcnew Person("鬼丸美輝");
// インスタンスメソッドを代入。
ShowMessage show = gcnew ShowMessage(p, &Person::ShowName);
show();
}
できました。最後から2番目の文です、これが使い勝手の悪さの全てです。デリゲートを作るのに毎回C++で言うメンバ関数へのポインタを書くようなものです。激しく使い勝手が悪いです。これがC#(特に2.0以降)だとShowMessage show = p.ShowName; で余裕です。この差は大きいです。.netでGUIプログラミングをやろうとするとイベント登録/削除のためにデリゲートが頻出します。この差が10年分ほど積み重なれば間違いなくエベレスト登頂できます。それと、あまり関係ないですが、同じ理由でBoost.Bindの使い勝手も今一つだと思っているので、C++では主にPStade.Eggで作った関数オブジェクトを主体に扱いたいです。
忘れていました。そもそも組み込み型の一部を除いてC++の型とCヰの型には互換性がないので、以下のようなことは(単純には)できません。
struct Foo {
int n;
…
};
value struct Bar {};
ref struct Baz {
int n; // これはおーけー
char * a; // ポインタもおーけー
//Foo & foo; // C++の参照をCヰの参照型(値型でも)のメンバとして使うことはできない!
Foo * pfoo; // ポインタでやりましょう!
//Foo foo; // でも値では持てない!
…
};
struct Qux {
//Bar bar; // Cヰの値型オブジェクトも同じ!
//Baz ^ baz; // CヰのハンドルをC++の型のメンバに持つことはできない!
…
};
// 関数のローカルオブジェクトとしてなら両言語のオブジェクトを自由に作れますし、返せます
Foo f() {
Foo foo;
Baz baz1;
Baz ^ baz2 = gcnew Baz();
baz1.n = 42;
foo.n = baz1.n;
baz2->n = foo.n;
return foo;
}
Baz ^ g() {
return gcnew Baz();
}
組み込み算術型、ポインタ型はCヰでもそのまま書けますが、現実はそんなに甘くありません。それ以外のC++の型はCヰの型のフィールドにはできませんし、C++の型のメンバとしてCヰの値型/参照型を持つことはできないです。辛い現実ですが、受け入れなくてはなりません。ちなみに(ref/value)と(struct/class)で4パターンの組み合わせがありますが、そのへんはさっきの入門サイトを見てください。
あとまだ色々あるのですが、もう列挙するのも面倒なので勝手に岩永さんのところとさっきのところとか見比べてC#でのある機能に対応するC++/CLIのそれがいかに面倒か調べてください。あなたがEnumeratorです。
そう、面倒、面倒です。ですが私は最初に書きました。「C++/CLIは、.netからC++やその他ネイティブコードのライブラリを楽に使えるようにするためと、そのための作業を楽に行うための言語です」と。あなた自分でいったこと忘れたのですか?って感じで頭おかしいですね。いいえちっともおかしくありません。確かに全てをCヰでやろうとすると面倒ですしC++の使い勝手を期待するとがっかりですしC#使いましょうになるのですが、それはその方針が間違っているからです。VC付属のライブラリや.netのクラスライブラリを使えばネイティブオブジェクトをCLIのGCに管理させたり、ネイティブ側でもマネージオブジェクトへの参照を保持することができますし、それによってネイティブコード、マネージコードそれぞれでできることに制限が生じることもありません(コンパイルオプションがほげほげというのはありますが今は忘れましょう)。それになにより、最初のほうにさらりと書きましたが、C++のコードとCヰのコードは、混在させることができます。直前の関数の例がそうです。ここが重要です。一般に、ある言語から別の言語で作った機能を呼び出す場合、ここまでに述べてきた程度の面倒さでは済まない上に、型安全性の「か」の字どころか「安」の字の第一画すら含まない(.net系の言語間は別です、この記事の文脈からは、それらは全て同じ言語にしか見えません)ので、慎重に自分でそれを保証しなければなりません。ところがC++からはCヰをラッパとしてその背後にある.netクラスライブラリを、CヰからはC++あるいはその他のネイティブなライブラリを簡単に呼び出せます。まあ最近の.net系の言語の事情は知らないので、もしかしたら後者のメリットは無いかもしれないです。が、C++側から.netを使うにはこれ以上にない最高の選択です。だからC++/CLI(というかCヰ)を使う場合は、マネージとネイティブの仲介のためだけに使って、その他のほとんどのコードはC++や.net系言語で書いてください。これはお願いです。そしてこの言語のことを名前で呼んであげてください。君とか貴方じゃなくて、その言語の名前で呼んであげてください。そうすればこの言語あなたを背に乗せてマッハ3(後述のそれぞれの場所において)ぐらいで地を駆け、空を舞い、海を潜り、星光を殲滅するでしょう。背にのったあなたは最後に星の光と共に消滅します。でも許してあげてください、それは彼女なりの友情の証です。あなたはC++/CLIと友達になれたのです。
- 449 http://www.google.co.jp/url?sa=t&rct=j&q=c++/cli&source=web&cd=3&ved=0CEMQFjAC&url=http://d.hatena.ne.jp/DigitalGhost/20100322/1269291644&ei=UkKETpfsJYr6mAXuqZ0o&usg=AFQjCNFTFE5YiRlkuDB4YEFPb2i_0iRsPg
- 297 http://www.google.co.jp/search?q=C++/CLI&ie=utf-8&oe=utf-8&aq=t&rls=org.mozilla:ja:official&hl=ja&client=firefox-a
- 135 http://www.google.co.jp/search?sourceid=navclient&hl=ja&ie=UTF-8&rlz=1T4GGLL_jaJP316JP316&q="C++/CLI"
- 104 http://www.google.co.jp/url?sa=t&rct=j&q=c++ cli&source=web&cd=3&ved=0CEYQFjAC&url=http://d.hatena.ne.jp/DigitalGhost/20100322/1269291644&ctbs=lr:lang_1ja&ei=gDqETqb7DavUiAKwoeC2DA&usg=AFQjCNFTFE5YiRlkuDB4YEFPb2i_0iRsPg&sig2=iKv83Hz4qw
- 99 http://www.google.co.jp/url?sa=t&rct=j&q=c+++cli&source=web&cd=3&ved=0CEUQFjAC&url=http://d.hatena.ne.jp/DigitalGhost/20100322/1269291644&ei=tErITuYPxsmZBYeZhBw&usg=AFQjCNFTFE5YiRlkuDB4YEFPb2i_0iRsPg
- 84 http://www.google.co.jp/search?sourceid=chrome&ie=UTF-8&q=C++から.netを使う
- 82 http://www.google.co.jp/search?q=C+++CLI&ie=utf-8&oe=utf-8&aq=t&rls=org.mozilla:ja:official&hl=ja&client=firefox-a
- 72 http://www.google.co.jp/search?aq=f&sourceid=chrome&ie=UTF-8&q=C++/CLI
- 65 http://www.google.co.jp/search?hl=ja&q=c++/CLI&lr=lang_ja
- 64 http://www.google.co.jp/search?hl=ja&source=hp&q=c+++cli&aq=f&aqi=g10&aql=&oq=&gs_rfai=
