C++ クラスの名前、内容 (つまり、メンバーとその型) などをイントロスペクトできるようにしたいと考えています。ここでは、リフレクションを持つマネージ C++ ではなく、ネイティブ C++ について話しています。C++ が RTTI を使用して限られた情報を提供していることは理解しています。この情報を提供できる追加のライブラリ (またはその他の手法) はどれですか?
28 に答える
必要なことは、フィールドに関するリフレクション データをプリプロセッサに生成させることです。このデータは、ネストされたクラスとして格納できます。
まず、プリプロセッサでの記述をより簡単かつ明確にするために、型付き式を使用します。型付き式は、型を括弧で囲んだ単なる式です。したがって、書く代わりに と書きint x
ます(int) x
。型付き式を支援するいくつかの便利なマクロを次に示します。
#define REM(...) __VA_ARGS__
#define EAT(...)
// Retrieve the type
#define TYPEOF(x) DETAIL_TYPEOF(DETAIL_TYPEOF_PROBE x,)
#define DETAIL_TYPEOF(...) DETAIL_TYPEOF_HEAD(__VA_ARGS__)
#define DETAIL_TYPEOF_HEAD(x, ...) REM x
#define DETAIL_TYPEOF_PROBE(...) (__VA_ARGS__),
// Strip off the type
#define STRIP(x) EAT x
// Show the type without parenthesis
#define PAIR(x) REM x
REFLECTABLE
次に、各フィールド (およびフィールド自体) に関するデータを生成するマクロを定義します。このマクロは次のように呼び出されます。
REFLECTABLE
(
(const char *) name,
(int) age
)
したがって、Boost.PPを使用して各引数を繰り返し処理し、次のようなデータを生成します。
// A helper metafunction for adding const to a type
template<class M, class T>
struct make_const
{
typedef T type;
};
template<class M, class T>
struct make_const<const M, T>
{
typedef typename boost::add_const<T>::type type;
};
#define REFLECTABLE(...) \
static const int fields_n = BOOST_PP_VARIADIC_SIZE(__VA_ARGS__); \
friend struct reflector; \
template<int N, class Self> \
struct field_data {}; \
BOOST_PP_SEQ_FOR_EACH_I(REFLECT_EACH, data, BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__))
#define REFLECT_EACH(r, data, i, x) \
PAIR(x); \
template<class Self> \
struct field_data<i, Self> \
{ \
Self & self; \
field_data(Self & self) : self(self) {} \
\
typename make_const<Self, TYPEOF(x)>::type & get() \
{ \
return self.STRIP(x); \
}\
typename boost::add_const<TYPEOF(x)>::type & get() const \
{ \
return self.STRIP(x); \
}\
const char * name() const \
{\
return BOOST_PP_STRINGIZE(STRIP(x)); \
} \
}; \
fields_n
これが行うことは、クラス内の反映可能なフィールドの数である定数を生成することです。field_data
次に、フィールドごとに特化します。また、reflector
クラスをフレンドシップします。これは、フィールドが非公開の場合でもフィールドにアクセスできるようにするためです。
struct reflector
{
//Get field_data at index N
template<int N, class T>
static typename T::template field_data<N, T> get_field_data(T& x)
{
return typename T::template field_data<N, T>(x);
}
// Get the number of fields
template<class T>
struct fields
{
static const int n = T::fields_n;
};
};
フィールドを反復するために、訪問者パターンを使用します。0 からフィールド数までの MPL 範囲を作成し、そのインデックスでフィールド データにアクセスします。次に、フィールド データをユーザー提供の訪問者に渡します。
struct field_visitor
{
template<class C, class Visitor, class I>
void operator()(C& c, Visitor v, I)
{
v(reflector::get_field_data<I::value>(c));
}
};
template<class C, class Visitor>
void visit_each(C & c, Visitor v)
{
typedef boost::mpl::range_c<int,0,reflector::fields<C>::n> range;
boost::mpl::for_each<range>(boost::bind<void>(field_visitor(), boost::ref(c), v, _1));
}
真実の瞬間のために、すべてをまとめます。Person
リフレクト可能なクラスを定義する方法は次のとおりです。
struct Person
{
Person(const char *name, int age)
:
name(name),
age(age)
{
}
private:
REFLECTABLE
(
(const char *) name,
(int) age
)
};
print_fields
リフレクション データを使用してフィールドを反復処理する一般化された関数を次に示します。
struct print_visitor
{
template<class FieldData>
void operator()(FieldData f)
{
std::cout << f.name() << "=" << f.get() << std::endl;
}
};
template<class T>
void print_fields(T & x)
{
visit_each(x, print_visitor());
}
print_fields
ReflectablePerson
クラスで を使用する例:
int main()
{
Person p("Tom", 82);
print_fields(p);
return 0;
}
どの出力:
name=Tom
age=82
ほら、100 行未満のコードで C++ にリフレクションを実装しました。
2種類のreflection
泳ぎ方があります。
- 型のメンバーの反復処理、メソッドの列挙などによる検査。
これは C++ では不可能です。 - クラス型 (クラス、構造体、共用体) がメソッドまたはネストされた型を持っているかどうかをチェックすることによる検査は、別の特定の型から派生しています。
この種のことは、C++ を使用して可能template-tricks
です。多くのことに使用boost::type_traits
します (型が整数かどうかのチェックなど)。メンバー関数の存在を確認するには、関数の存在を確認するためにテンプレートを作成することは可能ですか? を使用します。. 特定のネストされた型が存在するかどうかを確認するには、プレーンなSFINAEを使用します。
1) を達成する方法を探している場合、たとえば、クラスに含まれるメソッドの数を調べたり、クラス ID の文字列表現を取得したりする場合は、これを行う標準 C++ の方法がないのではないかと思います。どちらかを使用する必要があります
- コードを変換して追加のメタ情報を追加する Qt メタ オブジェクト コンパイラのようなメタ コンパイラ。
- 必要なメタ情報を追加できるマクロで構成されるフレームワーク。フレームワークに、すべてのメソッド、クラス名、基本クラス、および必要なものすべてを伝える必要があります。
C++ は速度を念頭に置いて作成されています。C# や Java のような高レベルの検査が必要な場合は、何らかの努力なしには方法がないことをお伝えしなければなりません。
私はポニーが大好きですが、ポニーはタダではありません。:-p
http://en.wikibooks.org/wiki/C%2B%2B_Programming/RTTIが得られるものです。あなたが考えているようなリフレクション -- 実行時に利用可能な完全に記述的なメタデータ -- は、デフォルトでは C++ には存在しません。
リフレクションは、そのままでは C++ ではサポートされていません。これは悲しいことです。防御テストが苦痛になるからです。
リフレクションを行うには、いくつかのアプローチがあります。
- デバッグ情報を使用します (非移植性)。
- コードにマクロ/テンプレートまたはその他のソース アプローチを追加します (見栄えが悪くなります)。
- clang/gcc などのコンパイラを変更して、データベースを生成します。
- Qt moc アプローチを使用する
- ブーストリフレクト
- 正確でフラットな反射
最初のリンクは最も有望に見えます (mod を使用して clang にします)。2 番目のリンクではさまざまな手法について説明しています。3 番目のリンクは gcc を使用した別のアプローチです。
現在、C++ リフレクションのワーキング グループがあります。C++14 @ CERN のニュースを参照してください。
編集 13/08/17:
最初の投稿以来、リフレクションには多くの潜在的な進歩がありました。以下では、さまざまな手法とステータスに関する詳細と説明を提供します。
ただし、C++ でのリフレクションのサポートにコミュニティからさらに多くの関心が寄せられない限り、近い将来、C++ での標準化されたリフレクション アプローチは期待できそうにありません。
以下は、前回の C++ 標準会議からのフィードバックに基づく現在のステータスの詳細です。
2017 年 12 月 13 日を編集
リフレクションは C++ 20 またはそれ以上、おそらく TSR に向かっているようです。ただし動きは鈍い。
編集 15/09/2018
TS の草案は、投票のために各国の機関に送られました。
テキストはここにあります: https://github.com/cplusplus/reflection-ts
2019年11月7日編集
リフレクション TS は完全な機能であり、夏 (2019 年) にコメントと投票を求めています。
メタテンプレート プログラミング アプローチは、より単純なコンパイル時コード アプローチに置き換えられます (TS には反映されていません)。
2020年10月2日編集
Visual Studio でリフレクション TS をサポートするためのリクエストがここにあります。
著者 David Sankel による TS に関するトーク:
2020年3月17日編集
反省が進んでいます。「2020-02 プラハ ISO C++ 委員会旅行レポート」のレポートは、次の場所にあります。
C++23 で検討されている内容の詳細については、こちら (リフレクションに関する短いセクションを含む) を参照してください。
2020 年 6 月 4 日編集
ランタイム リフレクションのメカニズムを含む「Plywood」と呼ばれる新しいフレームワークが Jeff Preshing によってリリースされました。詳細については、次を参照してください。
ツールとアプローチは、これまでで最も洗練されていて使いやすいように見えます。
2020年7月12日編集
Clang の実験的なリフレクション フォーク: https://github.com/lock3/meta/wiki
マクロを追加する必要のない単純なリフレクションの情報を抽出するために clang ツール ライブラリを使用する興味深いリフレクション ライブラリ: https://github.com/chakaz/reflang
2021年2月24日編集
いくつかの追加の clang ツール アプローチ:
2021年8月25日編集
YouTube https://www.youtube.com/watch?v=60ECEc-URP8での ACCU のオンライン トークも、聞く価値があります。標準に対する現在の提案と、clang に基づく実装について語っています。
見る:
- https://github.com/lock3/meta、ブランチ paper/p2320
- コンパイラ エクスプローラ: https://cppx.godbolt.org/コンパイラ バージョンには p2320 トランクを使用します。
情報は存在しますが、必要な形式ではなく、クラスをエクスポートする場合のみです。これは Windows で動作しますが、他のプラットフォームについては知りません。たとえば、次のようにストレージ クラス指定子を使用します。
class __declspec(export) MyClass
{
public:
void Foo(float x);
}
これにより、コンパイラはクラス定義データを DLL/Exe にビルドします。しかし、リフレクションにすぐに使用できる形式ではありません。
私の会社では、このメタデータを解釈するライブラリを構築し、クラス自体に余分なマクロなどを挿入することなくクラスを反映できるようにしています。次のように関数を呼び出すことができます。
MyClass *instance_ptr=new MyClass;
GetClass("MyClass")->GetFunction("Foo")->Invoke(instance_ptr,1.331);
これにより、次のことが効果的に行われます。
instance_ptr->Foo(1.331);
Invoke(this_pointer,...) 関数には可変引数があります。明らかに、この方法で関数を呼び出すことにより、const-safety などを回避しているため、これらの側面はランタイム チェックとして実装されます。
構文は改善される可能性があると確信しており、今のところ Win32 と Win64 でのみ動作します。クラスへの自動 GUI インターフェイス、C++ でのプロパティの作成、XML との間のストリーミングなどに非常に便利であり、特定の基本クラスから派生させる必要がないことがわかりました。十分な需要があれば、リリースに向けて形を整えることができるかもしれません.
Qtの使用をお勧めします。
商用ライセンスだけでなく、オープンソース ライセンスもあります。
何をしようとしているのか、RTTI が要件を満たしているかどうかを確認する必要があります。いくつかの非常に具体的な目的のために、独自の疑似反射を実装しました。たとえば、シミュレーションが出力するものを柔軟に構成できるようにしたいと思ったことはあります。出力されるクラスに定型コードを追加する必要がありました。
namespace {
static bool b2 = Filter::Filterable<const MyObj>::Register("MyObject");
}
bool MyObj::BuildMap()
{
Filterable<const OutputDisease>::AddAccess("time", &MyObj::time);
Filterable<const OutputDisease>::AddAccess("person", &MyObj::id);
return true;
}
最初の呼び出しは、このオブジェクトをフィルター処理システムに追加します。フィルター処理システムはメソッドを呼び出して、BuildMap()
利用可能なメソッドを見つけます。
次に、構成ファイルで、次のようなことができます。
FILTER-OUTPUT-OBJECT MyObject
FILTER-OUTPUT-FILENAME file.txt
FILTER-CLAUSE-1 person == 1773
FILTER-CLAUSE-2 time > 2000
を含むいくつかのテンプレート マジックによりboost
、これは実行時 (構成ファイルが読み取られるとき) に一連のメソッド呼び出しに変換されるため、かなり効率的です。本当に必要でない限り、これを行うことはお勧めしませんが、そうすると、本当に素晴らしいことができます。
私はあなたが一度求めているようなことをしました。ある程度の反射とより高いレベルの機能へのアクセスを取得することは可能ですが、メンテナンスの頭痛の種は価値がないかもしれません. 私のシステムは、Objective-C のメッセージ パッシングと転送の概念に似た委譲によって、UI クラスをビジネス ロジックから完全に分離するために使用されました。それを行う方法は、シンボルをマッピングできるいくつかの基本クラスを作成することです (私は文字列プールを使用しましたが、全体的な柔軟性よりも速度とコンパイル時のエラー処理を好む場合は、列挙型で行うことができます) を関数ポインターに (実際にはそうではありません)純粋な関数ポインターですが、Boost が Boost.Function で持っているものと似ていますが、当時はアクセスできませんでした)。任意の値を表すことができる共通の基本クラスがある限り、メンバー変数に対して同じことを行うことができます。システム全体は、Key-Value コーディングとデリゲーションのあからさまなパクリであり、システムを使用するすべてのクラスがそのすべてのメソッドとメンバーを正当な呼び出しと一致させるのに必要な膨大な時間の価値があるいくつかの副作用がありました。 : 1) 任意のクラスは、ヘッダーを含めたり、偽の基本クラスを作成したりすることなく、他のクラスの任意のメソッドを呼び出すことができるため、コンパイラ用にインターフェイスを事前定義できます。2) メンバー変数のゲッターとセッターは、すべてのオブジェクトの基本クラスの 2 つのメソッドを介して常に値の変更またはアクセスが行われるため、スレッドセーフにするのが簡単でした。システム全体は、Key-Value コーディングとデリゲーションのあからさまなパクリであり、システムを使用するすべてのクラスがそのすべてのメソッドとメンバーを正当な呼び出しと一致させるのに必要な膨大な時間の価値があるいくつかの副作用がありました。 : 1) 任意のクラスは、ヘッダーを含めたり、偽の基本クラスを作成したりすることなく、他のクラスの任意のメソッドを呼び出すことができるため、コンパイラ用にインターフェイスを事前定義できます。2) メンバー変数のゲッターとセッターは、すべてのオブジェクトの基本クラスの 2 つのメソッドを介して常に値の変更またはアクセスが行われるため、スレッドセーフにするのが簡単でした。システム全体は、Key-Value コーディングとデリゲーションのあからさまなパクリであり、システムを使用するすべてのクラスがそのすべてのメソッドとメンバーを正当な呼び出しと一致させるのに必要な膨大な時間の価値があるいくつかの副作用がありました。 : 1) 任意のクラスは、ヘッダーを含めたり、偽の基本クラスを作成したりすることなく、他のクラスの任意のメソッドを呼び出すことができるため、コンパイラ用にインターフェイスを事前定義できます。2) メンバー変数のゲッターとセッターは、すべてのオブジェクトの基本クラスの 2 つのメソッドを介して常に値の変更またはアクセスが行われるため、スレッドセーフにするのが簡単でした。1) 任意のクラスは、ヘッダーを含めたり、偽の基本クラスを作成したりすることなく、他のクラスの任意のメソッドを呼び出すことができるため、コンパイラ用にインターフェイスを事前定義できます。2) メンバー変数のゲッターとセッターは、すべてのオブジェクトの基本クラスの 2 つのメソッドを介して常に値の変更またはアクセスが行われるため、スレッドセーフにするのが簡単でした。1) 任意のクラスは、ヘッダーを含めたり、偽の基本クラスを作成したりすることなく、他のクラスの任意のメソッドを呼び出すことができるため、コンパイラ用にインターフェイスを事前定義できます。2) メンバー変数のゲッターとセッターは、すべてのオブジェクトの基本クラスの 2 つのメソッドを介して常に値の変更またはアクセスが行われるため、スレッドセーフにするのが簡単でした。
それはまた、そうでなければ C++ では容易ではない、いくつかの本当に奇妙なことを行う可能性にもつながりました。たとえば、それ自体を含む任意の型の任意の項目を含む Array オブジェクトを作成し、すべての配列項目にメッセージを渡して戻り値を収集することで新しい配列を動的に作成できます (Lisp のマップに似ています)。もう 1 つは、キー値監視の実装です。これにより、常にデータをポーリングしたり、不必要に表示を再描画したりするのではなく、バックエンド クラスのメンバーの変更にすぐに応答するように UI を設定できました。
おそらくもっと興味深いのは、クラスに定義されたすべてのメソッドとメンバーを文字列形式でダンプすることもできるという事実です。
煩わしさを思いとどまらせる可能性のあるシステムの欠点: すべてのメッセージとキー値を追加するのは非常に面倒です。反射がない場合よりも遅くなります。暴力的な情熱を持ってコードベース全体を見るboost::static_pointer_cast
のが嫌いになるでしょう。boost::dynamic_pointer_cast
厳密に型指定されたシステムの制限はまだあります。実際には、それらを少し隠しているだけなので、それほど明白ではありません。文字列のタイプミスも楽しいものではなく、発見しやすい驚きでもありません。
このようなものを実装する方法については、いくつかの共通ベースへの共有ポインターと弱いポインターを使用するだけです(私のものは非常に想像力に富んで「オブジェクト」と呼ばれていました)、使用するすべてのタイプを派生させます。私が行った方法ではなく、Boost.Function をインストールすることをお勧めします。これには、関数ポインター呼び出しをラップするためのカスタムがらくたと大量の醜いマクロが含まれていました。すべてがマップされているため、オブジェクトの検査はすべてのキーを反復処理するだけです。私のクラスは基本的に、C++ のみを使用して可能な限り Cocoa の直接のぼったくりに近いものだったので、そのようなものが必要な場合は、Cocoa のドキュメントを青写真として使用することをお勧めします。
C++ 時代から私が知っている 2 つのリフレクションのようなソリューションは次のとおりです。
1) すべてのクラスを「オブジェクト」基本クラスから派生させることができる場合は、リフレクションのような動作を構築するためのブートストラップを提供する RTTI を使用します。そのクラスは、GetMethod、GetBaseClass などのいくつかのメソッドを提供できます。これらのメソッドがどのように機能するかについては、手動でいくつかのマクロを追加して型を装飾する必要があります。これは、舞台裏で型にメタデータを作成して、GetMethods などへの応答を提供します。
2) コンパイラ オブジェクトにアクセスできる場合の別のオプションは、DIA SDKを使用することです。私の記憶が正しければ、これにより、C++ 型のメタデータを含む pdbs を開くことができます。必要なことをするだけで十分かもしれません。このページでは、たとえば、クラスのすべての基本型を取得する方法を示します。
ただし、これらのソリューションはどちらも少し醜いです! C# の贅沢さを理解させるのに、C++ ほどのものはありません。
幸運を。
この質問は今では少し古くなっています (なぜ今日も古い質問を続けているのかわかりません) が、コンパイル時のリフレクションを導入するBOOST_FUSION_ADAPT_STRUCTについて考えていました。
もちろん、これを実行時のリフレクションにマップするのはあなた次第であり、それほど簡単ではありませんが、この方向では可能ですが、逆にはなりません:)
BOOST_FUSION_ADAPT_STRUCT
カプセル化するマクロは、実行時の動作を取得するために必要なメソッドを生成できると本当に思います。
Dominic Filion による記事「Using Templates for Reflection in C++」が興味深いと思われるかもしれません。Game Programming Gems 5のセクション 1.4 にあります。残念ながらコピーを手元に持っていませんが、あなたが何を求めているかを説明していると思うので探してください。
リフレクションは、基本的に、ランタイムコードがクエリできるコードのフットプリントとしてコンパイラが残すことを決定したものに関するものです。C ++は、使用しないものにお金を払わないことで有名です。ほとんどの人はリフレクションを使用しない/望んでいないため、C++コンパイラは何も記録しないことでコストを回避します。
したがって、C ++はリフレクションを提供せず、他の回答が指摘しているように、一般的なルールとして自分でそれを「シミュレート」するのは簡単ではありません。
「その他の手法」では、リフレクションを備えた言語がない場合は、コンパイル時に必要な情報を抽出できるツールを入手してください。
当社のDMSSoftwareReengineering Toolkitは、明示的な言語定義によってパラメーター化された一般化されたコンパイラーテクノロジです。C、C ++、Java、COBOL、PHPなどの言語定義があります...
C、C ++、Java、およびCOBOLバージョンの場合、解析ツリーおよびシンボルテーブル情報への完全なアクセスを提供します。そのシンボルテーブル情報には、「リフレクション」から必要になる可能性のあるデータの種類が含まれています。フィールドまたはメソッドのセットを列挙してそれらを使用して何かを行うことが目標の場合は、DMSを使用して、シンボルテーブルで見つけたものに応じて任意の方法でコードを変換できます。
編集: 2017 年 2 月 7 日現在の壊れたリンクを更新しました。
誰もこれについて言及していないと思います:
CERN では、C++ に完全なリフレクション システムを使用しています。
CERN反射。とてもうまくいくようです。
Ponderは、この質問に答える C++ リフレクション ライブラリです。オプションを検討し、すべてのボックスにチェックマークを付けたものを見つけることができなかったので、自分で作成することにしました.
この質問には素晴らしい答えがありますが、大量のマクロを使用したり、Boost に依存したりしたくありません。Boost は優れたライブラリですが、よりシンプルでコンパイル時間の短いカスタムメイドの C++0x プロジェクトが数多く出回っています。C++11 をサポートしていない (まだ?) C++ ライブラリをラップするなど、クラスを外部でデコレートできる利点もあります。Boost が不要になったのは、C++11 を使用した CAMP のフォークです。
ここで別のライブラリを見つけることができます: http://www.garret.ru/cppreflection/docs/reflect.html 2 つの方法をサポートしています: デバッグ情報から型情報を取得し、プログラマーがこの情報を提供できるようにします。
私は自分のプロジェクトのリフレクションにも興味があり、このライブラリを見つけました。まだ試していませんが、この男の他のツールを試してみましたが、それらがどのように機能するかが気に入っています:-)
C++ でリフレクションが必要になったとき、この記事を読み、そこで見たものを改善しました。申し訳ありませんが、缶はありません。私は結果を所有していません...しかし、あなたは確かに私が持っていたものを手に入れ、そこから行くことができます.
私は現在、気が向いたときに、inherit_linearly を使用してリフレクト可能な型の定義をより簡単にする方法を研究しています。実際にはかなり進んでいますが、まだ先があります。C++0x の変更は、この分野で大いに役立つ可能性が非常に高いです。
リフレクションは C++ ですぐにサポートされていませんが、実装するのはそれほど難しくありません。私はこの素晴らしい記事に遭遇しました: http://replicaisland.blogspot.co.il/2010/11/building-reflective-object-system-in-c.html
この記事では、非常に単純で初歩的なリフレクション システムを実装する方法について詳しく説明しています。それは最も健全な解決策ではないことを認め、整理する必要がある大まかなエッジが残っていますが、私のニーズには十分でした.
要するに、リフレクションは正しく行われれば報われる可能性があり、C++ では完全に実行可能です。
Classdesc http://classdesc.sf.netをチェックしてください。クラス「記述子」の形式でリフレクションを提供し、標準の C++ コンパイラで動作し (GCC と同様に Visual Studio で動作することが知られています)、ソース コードの注釈を必要としません (トリッキーな状況を処理するためのプラグマがいくつか存在しますが)。 )。10 年以上にわたって開発が続けられており、多くの産業規模のプロジェクトで使用されています。
C++ にはまだこの機能がないようです。また、C++11 ではリフレクションも延期されました ((
いくつかのマクロを検索するか、独自のものを作成してください。Qt はリフレクションにも役立ちます (使用できる場合)。
自動内省・振り返りツールキット「IDK」の存在を宣伝したいと思います。Qt のようなメタ コンパイラを使用し、メタ情報をオブジェクト ファイルに直接追加します。使いやすいと言われています。外部依存関係はありません。std::string を自動的に反映して、スクリプトで使用することもできます。IDKをご覧ください
Boost::Hana ライブラリのBOOST_HANA_DEFINE_STRUCTを使用して、構造体のクールな静的リフレクション機能を実現できます。
Hana は、考えているユースケースだけでなく、多くのテンプレート メタプログラミングにも非常に用途が広いです。