C++ にはなぜリフレクションがないのでしょうか? 質問する

C++ にはなぜリフレクションがないのでしょうか? 質問する

これは少し奇妙な質問です。私の目的は、言語設計の決定を理解し、C++ でのリフレクションの可能性を特定することです。

  1. C++ 言語委員会が言語にリフレクションを実装しなかったのはなぜですか? 仮想マシン上で実行されない言語 (Java など) ではリフレクションは難しすぎるのでしょうか?

  2. C++ にリフレクションを実装する場合、どのような課題があるでしょうか?

リフレクションの用途はよく知られていると思います。エディターが簡単に記述でき、プログラム コードが小さくなり、ユニット テスト用のモックを生成できるなどです。ただし、リフレクションの用途についてもコメントしていただけるとありがたいです。

ベストアンサー1

C++ のリフレクションにはいくつかの問題があります。

  • 追加するには多くの作業が必要です。また、C++ 委員会はかなり保守的であり、確実に成果が得られると確信しない限り、根本的な新機能に時間を費やしません。(.NET アセンブリに似たモジュール システムを追加する提案がなされており、それがあればよいという一般的な合意があると思いますが、現時点では最優先事項ではなく、C++0x よりずっと後まで延期されています。この機能の目的はシステムを排除することです#includeが、少なくとも一部のメタデータも有効になります)。

  • 使わないものにはお金はかかりません。これは、C++ の根底にある基本的な設計哲学の 1 つです。メタデータがまったく必要にならないのに、なぜコードにメタデータを持たせる必要があるのでしょうか。さらに、メタデータを追加すると、コンパイラの最適化が妨げられる可能性があります。メタデータがまったく必要にならないのに、なぜコードにそのコストを払う必要があるのでしょうか。

  • ここから、もう 1 つの重要な点が浮かび上がります。C++ では、コンパイルされたコードについてほとんど保証がありません。コンパイラは、結果の機能が期待どおりである限り、ほぼ何でも好きなように行うことができます。たとえば、クラスが実際に存在している必要はありませんコンパイラはクラスを最適化して、クラスが行うすべてのことをインライン化できます。単純なテンプレート コードでも、かなりの数のテンプレート インスタンス化が作成される傾向があるため、コンパイラは頻繁にそれを行います。C++ 標準ライブラリは、この積極的な最適化に依存しています。関数は、オブジェクトのインスタンス化と破棄のオーバーヘッドを最適化して除去できる場合にのみ、パフォーマンスが高くなります。operator[]ベクターでの関数は、演算子全体をインライン化してコンパイルされたコードから完全に削除できるため、パフォーマンスの点で生の配列インデックスとしか比較できません。C# と Java では、コンパイラの出力について多くの保証が行われます。C# でクラスを定義すると、そのクラスは結果のアセンブリに存在します。たとえ使用しない場合でも、そのクラスのメンバー関数へのすべての呼び出しをインライン化できる場合でもです。リフレクションがクラスを見つけられるように、クラスはそこに存在する必要があります。この問題は、C# をバイトコードにコンパイルすることで一部緩和されます。つまり、JIT コンパイラは、最初の C# コンパイラがクラス定義やインライン関数を削除できない場合でも、必要に応じて削除できます。C++ では、コンパイラは 1 つしかなく、効率的なコードを出力する必要があります。C++ 実行可能ファイルのメタデータを検査できる場合、定義されているすべてのクラスが表示されることが期待されます。つまり、コンパイラは、必要でなくても、定義されているすべてのクラスを保持する必要があります。

  • そして、テンプレートがあります。C++ のテンプレートは、他の言語のジェネリクスとはまったく異なります。テンプレートをインスタンス化するたびに、新しい型が作成されます。は、std::vector<int>とは完全に別のクラスですstd::vector<float>。そのため、プログラム全体で多くの異なる型が追加されます。リフレクションは、何を参照する必要がありますか? テンプレート ですか? しかし、これは実行時には意味を持たないソースコード構造なので、どうすればよいでしょうか? 個別のクラスとstd::vectorを参照する必要があります。また、 と を参照する必要があります。 などについても同様です。テンプレート メタプログラミングに踏み込むと、すぐに何百ものテンプレートをインスタンス化することになり、それらはすべてコンパイラによってインライン化され、再び削除されます。コンパイル時のメタプログラムの一部として使用する場合を除き、それらは意味を持ちません。これらの何百ものクラスはすべてリフレクションから参照できる必要がありますか? そうする必要があります。そうでなければ、定義したクラスが実際に存在することを保証しなければ、リフレクションは役に立たなくなります。また、副次的な問題として、テンプレート クラスはインスタンス化されるまで存在しないという点があります。 を使用するプログラムを想像してください。リフレクション システムは を参照できる必要がありますか?一方で、当然そう予想されます。これは重要なクラスであり、メタデータに存在するで定義されています。一方、プログラムがこの反復子クラス テンプレートを実際に使用しない場合は、その型がインスタンス化されることはなく、コンパイラはそもそもクラスを生成しません。また、ソース コードにアクセスする必要があるため、実行時に作成するには遅すぎます。std::vector<int>std::vector<float>std::vector<int>::iteratorstd::vector<float>::iteratorconst_iteratorstd::vector<int>std::vector<int>::iteratorstd::vector<int>

  • そして最後に、リフレクションは C++ では C# ほど重要ではありません。その理由は、やはりテンプレート メタプログラミングです。これですべて解決できるわけではありませんが、リフレクションに頼ることになる多くの場合、コンパイル時に同じことを実行するメタプログラムを書くことができます。boost::type_traitsは簡単な例です。 型について知りたいですかT? 型を確認してくださいtype_traits。C# では、リフレクションを使用して型を探し回る必要があります。リフレクションは、まだいくつかの用途では役立ちます (メタプログラミングで簡単に置き換えることができない主な用途は、自動生成されたシリアル化コードです) が、C++ ではかなりのコストがかかるため、他の言語ほど頻繁に必要になるわけではありません。

編集:コメントへの返信:

cdleary: はい、デバッグ シンボルは実行可能ファイルで使用される型に関するメタデータを格納するという点で、同様の機能を果たします。ただし、私が説明した問題も抱えています。リリース ビルドのデバッグを試したことがあれば、私の言っていることがおわかりでしょう。ソース コードでクラスを作成した場所には大きな論理ギャップがあり、最終コードではインライン化されています。リフレクションを何か有用な目的で使用する場合、より信頼性が高く一貫性のあるものにする必要があります。現状では、コンパイルするたびに型が消えたり消えたりすることになります。ごく小さな詳細を変更すると、コンパイラはそれに応じて、どの型をインライン化し、どの型をインライン化しないかを変更します。最も関連性の高い型がメタデータに表される保証さえない場合、そこから有用なものをどのように抽出すればよいのでしょうか。探していた型は前回のビルドでは存在していたかもしれませんが、今はもうなくなっています。そして明日、誰かが小さな無害な関数に小さな無害な変更をチェックインし、その変更によって型が完全にインライン化されない程度の大きさになり、再び型が復活します。これはデバッグ シンボルには依然として便利ですが、それ以上のものではありません。このような条件でクラスのシリアル化コードを生成しようとするのは嫌です。

Evan Teran: もちろん、これらの問題は解決できます。しかし、それは私の論点 1 に戻ります。それには多くの作業が必要であり、C++ 委員会には、より重要だと感じていることが数多くあります。C++ で限定的なリフレクション (限定的なものになります) を得ることのメリットは、他の機能を犠牲にしてそれに重点を置くことを正当化するほど大きいのでしょうか。QT などのライブラリやプリプロセッサで既に (ほとんど) 実行できる機能をコア言語に追加することに、本当に大きなメリットがあるのでしょうか。そうかもしれません。しかし、そのようなライブラリが存在しない場合よりも、必要性ははるかに緊急性が低くなります。ただし、あなたの具体的な提案については、テンプレートでそれを禁止すると完全に役に立たなくなると思います。たとえば、標準ライブラリでリフレクションを使用できなくなります。 を表示できないようなリフレクションとはどのようなものでしょうか。std::vectorテンプレートはC++ の大きな部分を占めています。テンプレートで機能しない機能は基本的に役に立たないものです。

しかし、おっしゃる通り、何らかの形のリフレクションを実装することは可能です。しかし、それは言語の大きな変更になります。現状では、型はコンパイル時にのみ使用される構成要素です。型はコンパイラーの利益のために存在し、他の目的はありません。コードがコンパイルされると、クラスは存在しませ。無理やり拡張すれば、関数はまだ存在すると主張することもできますが、実際には、一連のジャンプ アセンブラ命令と、多数のスタック プッシュ/ポップがあるだけです。このようなメタデータを追加する場合、頼りになるものはあまりありません。

しかし、私が言ったように、コンパイル モデルの変更、自己完結型モジュールの追加、選択したタイプのメタデータの保存、#includes をいじることなく他のモジュールがそれらを参照できるようにする提案があります。これは良いスタートです。正直に言うと、標準委員会が変更が大きすぎるという理由でこの提案を却下しなかったことに驚いています。おそらく 5 ~ 10 年後でしょうか? :)

おすすめ記事