これはやや奇妙な質問です。私の目的は、言語設計の決定を理解し、C++でのリフレクションの可能性を識別することです。
C++言語委員会が言語のリフレクションの実装に向かわなかったのはなぜですか?仮想マシン(Javaなど)で実行されない言語では、リフレクションはあまりにも難しいですか?
C++のリフレクションを実装する場合、課題は何ですか?
リフレクションの使用法はよく知られていると思います:エディターはより簡単に記述でき、プログラムコードは小さくなり、モックはユニットテスト用に生成されるなどです。しかし、リフレクションの使用についてもコメントできれば素晴らしいと思います。
C++のリフレクションにはいくつかの問題があります。
追加するのは大変な作業であり、C++委員会はかなり保守的であり、成果を上げることが確実でない限り、急進的な新機能に時間を費やさないでください。 。 C++ 0x。この機能の動機は、#include
システムを削除することですが、少なくとも一部のメタデータも有効にします)。
使用しないものに対しては支払いません。これは、C++の基礎となる基本的な設計哲学の1つです。メタデータが必要ない場合に、コードでメタデータを持ち歩く必要があるのはなぜですか?さらに、メタデータを追加すると、コンパイラーによる最適化が妨げられる場合があります。そのメタデータが必要ない場合、コードでその費用を支払う必要があるのはなぜですか?
それは別の大きなポイントに私たちを導きます:C++はveryコンパイルされたコードについてほとんど保証しません。コンパイラーは、結果の機能が期待されるものである限り、好きなことをほとんど何でも行うことができます。たとえば、クラスは実際にはbe thereである必要はありません。コンパイラーはそれらを最適化して、それらが行うすべてをインライン化できます。単純なテンプレートコードでさえ、非常に多くのテンプレートのインスタンス化を作成する傾向があるため、頻繁にそれを行います。 C++標準ライブラリreliesこの積極的な最適化。ファンクタは、オブジェクトのインスタンス化と破棄のオーバーヘッドを最適化して排除できる場合にのみパフォーマンスを発揮します。ベクトルのoperator[]
は、演算子全体をインライン化でき、コンパイルされたコードから完全に削除できるため、パフォーマンスの点では生の配列インデックス付けにしか匹敵しません。 C#およびJavaは、コンパイラの出力について多くの保証を行います。 C#でクラスを定義すると、そのクラス存在します結果のアセンブリで。使用しなくても。そのメンバー関数へのすべての呼び出しがインライン化できたとしても。リフレクションがそれを見つけることができるように、クラスはそこになければなりません。この一部は、バイトコードにコンパイルするC#によって軽減されます。つまり、最初のC#コンパイラーができない場合でも、JITコンパイラーcanは、必要に応じてクラス定義とインライン関数を削除します。 C++では、コンパイラは1つしかなく、効率的なコードを出力する必要があります。 C++実行可能ファイルのメタデータの検査を許可された場合、定義されたすべてのクラスが表示されることが期待されます。つまり、コンパイラは、必要ではない場合でも、定義されたクラスをすべて保持する必要があります。
そして、テンプレートがあります。 C++のテンプレートは、他の言語のジェネリックのようなものではありません。すべてのテンプレートのインスタンス化はnewタイプを作成します。 std::vector<int>
は、std::vector<float>
とはまったく別のクラスです。これは、プログラム全体で多くの異なるタイプになります。私たちの反射は何を見るべきですか? テンプレートstd::vector
?しかし、それは、実行時に意味を持たないソースコードの構造なので、どうすればよいのでしょうか?別のクラスstd::vector<int>
とstd::vector<float>
を見る必要があります。 std::vector<int>::iterator
とstd::vector<float>::iterator
、const_iterator
なども同様です。そして、テンプレートのメタプログラミングに足を踏み入れると、すぐに何百ものテンプレートをインスタンス化することになり、それらはすべてインライン化され、コンパイラによって再び削除されます。コンパイル時のメタプログラムの一部として以外は意味がありません。これらの数百のクラスすべてをリフレクションに表示する必要がありますか?そうしなければ、私が定義したクラスが実際にbe thereになることさえ保証しなければ、リフレクションは役に立たないからです。そして副次的な問題は、インスタンス化されるまでテンプレートクラスが存在しないことです。 std::vector<int>
を使用するプログラムを想像してください。リフレクションシステムはstd::vector<int>::iterator
を見ることができますか?一方で、あなたは確かにそう期待するでしょう。これは重要なクラスであり、メタデータにdoesが存在するstd::vector<int>
の観点から定義されています。一方、プログラムが実際にsesこのイテレータクラステンプレートを使用しない場合、その型はインスタンス化されないため、コンパイラはそもそもクラスを生成しません。また、ソースコードにアクセスする必要があるため、実行時に作成するには遅すぎます。
boost::type_traits
は簡単な例です。タイプT
について知りたいですか? type_traits
を確認してください。 C#では、リフレクションを使用してそのタイプの後に釣り回る必要があります。リフレクションはいくつかのことにはまだ役立ちます(私が見ることができる主な用途は、メタプログラミングを簡単に置き換えることはできませんが、自動生成されたシリアル化コードのためです)が、C++にはかなりのコストがかかりますが、それはそれほど頻繁ではありません他の言語です。編集:コメントへの応答:
cdleary:はい、デバッグシンボルは、実行可能ファイルで使用されるタイプに関するメタデータを保存するという点で、同様のことを行います。しかし、彼らは私が説明した問題にも苦しんでいます。リリースビルドのデバッグを試したことがあるなら、私が何を意味するか知っているでしょう。ソースコードにクラスを作成した場所には論理的な大きなギャップがあり、最終的なコードではインライン化されています。有用なものにリフレクションを使用する場合、信頼性と一貫性を高める必要があります。そのままでは、コンパイルするたびに型が消えたり消えたりします。細部を少し変更すると、コンパイラは応答としてインライン化するタイプとインライン化しないタイプを変更することを決定します。最も関連性の高いタイプがメタデータで表されることが保証されていない場合、そこから有用なものをどのように抽出しますか?あなたが探していたタイプは前回のビルドにあったかもしれませんが、今ではなくなっています。そして明日、誰かが小さな無邪気な変更を小さな無邪気な関数にチェックインします。これにより、型が完全にインライン化されないほど大きくなり、再び戻ってきます。これはシンボルのデバッグにはまだ役立ちますが、それ以上ではありません。これらの条件の下でクラスのシリアル化コードを生成しようとするのは嫌です。
エヴァン・テラン:もちろんこれらの問題はcould解決されています。しかし、それは私のポイント#1に戻ります。それには多くの作業が必要であり、C++委員会にはもっと重要だと感じるものがたくさんあります。他の機能を犠牲にして、それに焦点を当てるのを正当化するのに十分なほど、C++で制限された(そして制限される)反射を得る利点はありますか? QTのようなライブラリとプリプロセッサを介して既に(ほとんど)実行できるコア言語の機能を追加することで、本当に大きな利点がありますか?おそらく、しかし、そのようなライブラリが存在しなかった場合よりも、その必要性は非常に緊急です。ただし、具体的な提案については、テンプレートでそれを拒否すると完全に役に立たなくなると思います。たとえば、標準ライブラリでリフレクションを使用することはできません。 std::vector
が表示されないのはどのような反射ですか?テンプレートは、C++の巨大の一部です。テンプレートで機能しない機能は、基本的には役に立ちません。
しかし、あなたは正しい、何らかの形のリフレクションを実装することができます。しかし、それは言語の大きな変化になるでしょう。現在のように、型は排他的にコンパイル時の構造です。それらはコンパイラの利益のために存在し、他には何もありません。コードがコンパイルされると、areクラスはありません。自分自身を伸ばすと、関数がまだ存在していると主張することができますが、実際には、多数のジャンプアセンブラー命令と多くのスタックプッシュ/ポップがあります。このようなメタデータを追加するとき、先に進むことはあまりありません。
しかし、私が言ったように、コンパイルモデルの変更、自己完結型モジュールの追加、選択タイプのメタデータの保存、#include
sをいじらずに他のモジュールがそれらを参照できるようにする提案があります。それは良い出発点であり、正直なところ、標準委員会があまりにも大きな変更であるという提案を捨てなかったのには驚いています。おそらく5〜10年で? :)
リフレクションでは、クエリ可能な場所に型に関するメタデータを保存する必要があります。 C++はネイティブマシンコードにコンパイルされ、最適化により大幅な変更が行われるため、アプリケーションの高レベルのビューはコンパイルの過程でほとんど失われます。そのため、実行時にクエリを実行することはできません。 Javaおよび.Net仮想マシンのバイナリコードで非常に高レベルの表現を使用して、このレベルのリフレクションを可能にします。ただし、一部のC++実装では、実行時型情報(RTTI)と呼ばれるものがあり、これはリフレクションの簡略版と見なすことができます。
すべての言語が、他のすべての言語のすべての機能を取り入れようとするべきではありません。
C++は本質的に非常に洗練されたマクロアセンブラーです。 (従来の意味では)C#、Java、Objective-C、Smalltalkなどの高レベル言語ではありません。
仕事ごとに異なるツールを用意するのは良いことです。ハンマーしか持っていない場合、すべてのものは釘などのように見えます。スクリプト言語を使用すると、一部のジョブに役立ちます。また、リフレクティブOO言語(Java、Obj-C、C#)は、別のクラスのジョブに便利です。 -効率的なベアボーンマシンに近い言語は、さらに別のクラスのジョブ(C++、C、アセンブラー)に役立ちます。
C++は、アセンブラーテクノロジを信じられないレベルの複雑さ管理に拡張し、プログラミングを人間にとってより大きく、より複雑なタスクにできるように抽象化するという驚くべき仕事をしています。ただし、厳密に高レベルの観点から問題に取り組んでいる人(LISP、Smalltalk、Java、C#)に最適な言語とは限りません。問題の解決策を最適に実装するためにこれらの機能を備えた言語が必要な場合は、そのような言語を作成してくれたすべての人に感謝します!
しかし、C++は、何らかの理由で、コードと基礎となるマシンの動作との間に強い相関関係が必要な人向けです。その効率、デバイスドライバーのプログラミング、または下位レベルのOSサービスとの相互作用など、C++はこれらのタスクにより適しています。
C#、Java、Objective-Cはすべて、実行をサポートするために、はるかに大きくリッチなランタイムシステムを必要とします。そのランタイムは、問題のシステムに配信する必要があります-ソフトウェアの操作をサポートするためにプリインストールされています。そして、その層は、そのプラットフォームで動作するように他の言語によってカスタマイズされた、さまざまなターゲットシステム用に維持する必要があります。そして、その中間層-ホストOSとコードの間の適応層-ランタイムは、ほとんどの場合、効率が#1であるCまたはC++のような言語で記述されており、ソフトウェアとハードウェア間の正確な相互作用をよく理解できます理解し、最大限の利益を得るために操作します。
Smalltalk、Objective-Cが大好きで、リフレクション、メタデータ、ガベージコレクションなどを備えた豊富なランタイムシステムがあります。これらの機能を活用するためにすばらしいコードを書くことができます。しかし、それは単にスタックの上位層であり、下位層に置かなければならない層であり、それ自体が最終的にOSとハードウェアの上に置かれなければなりません。そして、その層を構築するのに最適な言語であるC++/C/Assemblerが常に必要です。
補遺:C++ 11/14は、高レベルの抽象化とシステムをサポートするためにC++の機能を拡張し続けています。スレッド化、同期、正確なメモリモデル、より正確な抽象マシン定義により、C++開発者は、これらの高レベルのみの言語の一部が排他的ドメインを持つために使用した高レベルの抽象化の多くを達成しながら、金属性能と優れた予測可能性(つまり、最小限のランタイムサブシステム)。おそらく、リフレクション機能は、C++の将来のリビジョンで必要に応じて選択的に有効になります-または、おそらくライブラリがそのようなランタイムサービスを提供します(おそらく1つがありますか、それともブーストの始まりですか?)。
C++を取り巻く設計上の決定を本当に理解したい場合は、Ellis and Stroustrupによる The Annotated C++ Reference Manual のコピーを見つけてください。それは最新の標準では最新ではありませんが、元の標準を通過し、物事がどのように機能し、頻繁に、どのようにそのようになったかを説明しています。
リフレクションとは、リフレクションを可能にするためにコンパイラがオブジェクトコードに残しておくソースコードの量と、そのリフレクション情報を解釈するために使用できる分析機械の量です。コンパイラがすべてのソースコードを保持していない限り、ソースコードに関する利用可能な事実を分析する能力に反映が制限されます。
C++コンパイラは(RTTIを無視して)何も保持しないため、リフレクションin言語を取得しません。 (JavaおよびC#コンパイラはクラス、メソッド名、戻り値の型のみを保持するため、少しのリフレクションデータを取得できますが、式やプログラム構造を検査することはできません。つまり、「リフレクション対応」言語でも取得できる情報は非常に少ないため、実際にはあまり分析できません。
ただし、outside言語をステップ実行して、完全なリフレクション機能を取得できます。 reflection in C に関する別のスタックオーバーフローの議論への答えはこれを議論します。
言語によってデフォルトで設定されるべきではない重いコスト(メモリと速度)を持っているため、ネイティブc ++機能ではありません-言語は「デフォルトで最大パフォーマンス」指向です。
不要なものにお金を払うべきではなく、あなたが言うように、他のアプリケーションよりもエディターでより多く必要であるため、すべてのコードに「強制」されるのではなく、必要な場所にのみ実装する必要があります(エディターまたは他の同様のアプリケーションで作業するすべてのデータを反映する必要はありません)。
C++にリフレクションがない理由は、クラスタイプのメンバー、メンバーに関する情報、関数に関する情報など、シンボル情報をオブジェクトファイルに追加する必要があるためです。これにより、宣言によって出荷された情報がそれらのオブジェクトファイルから読み取られるため、インクルードファイルが役に立たなくなります(モジュール)。 C++では、それぞれのヘッダーを含めることで、プログラム内で型定義が複数回発生する可能性があります(すべての定義が同じである場合)。ここでの合併症。多数のクラステンプレートのインスタンス化を最適化できるC++コンパイラによって行われる積極的な最適化は、もう1つの長所です。可能ですが、C++はCと互換性があるため、これは厄介な組み合わせになります。
C++でリフレクションを使用する場合、テンプレートメタプログラミングのようなコンパイル時の構成を使用して適切に対処できないケースが数多くあります。
N334 は、C++でリフレクションを導入する方法としてリッチポインターを提案します。とりわけ、使用しない限り機能にお金を払わないという問題に対処します。
C++が持つ可能性がある場合:
const
修飾子のクラスメンバーデータconst
修飾子のクラスメンバーデータこれは、今日のWebおよびデータベースアプリケーション(すべてのオーム、メッセージングメカニズム、xml/jsonパーサー、データシリアル化など)で広く普及しているタイプレスデータ処理の核心で非常に使いやすいライブラリを作成するのに十分です。
たとえば、Q_PROPERTY
マクロ(Qt Frameworkの一部)でサポートされている基本情報 http://qt.nokia.com/doc/4.5/properties.html クラスメソッドをカバーするために展開されていますおよびe)-C++およびソフトウェアコミュニティ全体にとって非常に有益です。
確かに、私が言及しているリフレクションは、セマンティックな意味やより複雑な問題(コメントソースコードの行番号、データフロー分析など)をカバーしませんが、言語標準の一部である必要もありません。
Alistair Cockburnによれば、 反射環境ではサブタイプは保証されません 。
リフレクションは潜在型付けシステムにより関連しています。 C++では、どのタイプを持っているか、そしてそれで何ができるかを知っています。
プリプロセッサディレクティブのように、リフレクションはオプションです。何かのようなもの
#pragma enable reflection
そうすれば、このプラグマライブラリがリフレクションなしで(説明したオーバーヘッドなしで)作成されない限り、両方の長所を活用できます。その後、速度と使いやすさのどちらを望むかは、個々の開発者次第です。
それは基本的に「オプションの追加」だからです。多くの人がJavaやC#などの言語よりもC++を選択しているため、コンパイラ出力をより細かく制御できます。より小さく、かつ/またはより高速なプログラム。
反射を追加することを選択した場合、 利用可能なさまざまなソリューション があります。
C++のリフレクションは、C++をデータベースアクセス、Webセッション処理/ httpおよびGUI開発用の言語として使用する場合、非常に重要だと思います。リフレクションがないため、ORM(HibernateやLINQなど)、クラスをインスタンス化するXMLおよびJSONパーサー、データシリアル化、およびその他の多くの型(最初はクラスのインスタンスを作成するために型なしデータを使用する必要があります)を防ぎます。
ビルドプロセス中にソフトウェア開発者が利用できるコンパイル時の切り替えを使用して、この「使用した分に料金を支払う」懸念を解消できます。
ファームウェア開発者は、シリアルポートからデータを読み取るためにリフレクションを必要としません。その後、スイッチを使用しないでください。しかし、C++の使用を継続したいデータベース開発者として、データメンバーとデータベースコンストラクト間でデータをマッピングする恐ろしく、管理が難しいコードを常に使用しています。
Boostのシリアル化も他のメカニズムも、実際にリフレクションを解決していません-コンパイラーによって行われる必要があります-そして、一度行われると、C++は学校で再び調べられ、データ処理を処理するソフトウェアで使用されます
私にとって、この問題#1(およびネイティブスレッドプリミティブは問題#2)です。