C++ アプリケーションにリフレクションを追加するにはどうすればよいですか?

StackOverflow https://stackoverflow.com/questions/41453

質問

C++ クラスの名前と内容をイントロスペクトできるようにしたいと考えています (つまり、メンバーとそのタイプ)など。ここで私が話しているのはネイティブ C++ であり、リフレクションのあるマネージド C++ ではありません。C++ が RTTI を使用して提供する情報は限られていることがわかりました。この情報を提供できる追加のライブラリ (またはその他の技術) はどれですか?

役に立ちましたか?

解決 17

じっくり考える は、この質問に答えるための C++ リフレクション ライブラリです。オプションを検討しましたが、すべてのボックスにチェックを入れたものが見つからなかったので、自分で作成することにしました。

この質問に対する素晴らしい答えはありますが、私は大量のマクロを使用したり、Boost に依存したくありません。Boost は優れたライブラリですが、よりシンプルでコンパイル時間が短い、小規模な特注の C++0x プロジェクトがたくさんあります。C++11 を (まだ?) サポートしていない C++ ライブラリをラップするなど、クラスを外部で装飾できることにも利点があります。C++11 を使用した CAMP のフォークです。 ブーストはもう必要ありません.

他のヒント

必要なのは、プリプロセッサにフィールドに関するリフレクション データを生成させることです。このデータはネストされたクラスとして保存できます。

まず、プリプロセッサでの記述を簡単かつクリーンにするために、型付き式を使用します。型付き式は、型を括弧で囲んだだけの式です。だから、書く代わりに 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
)

それで、使用して ブースト.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 反射可能なもので Person クラス:

int main()
{
    Person p("Tom", 82);
    print_fields(p);
    return 0;
}

どの出力:

name=Tom
age=82

これで、100 行未満のコードで C++ のリフレクションが実装されました。

2種類あります reflection 泳ぎ回っている。

  1. 型のメンバーを反復処理したり、そのメソッドを列挙したりすることによる検査。

    これは C++ では不可能です。
  2. クラス型 (class、struct、union) にメソッドまたはネストされた型があるかどうかをチェックする検査は、別の特定の型から派生したものです。

    この種のことは、C++ を使用して可能です。 template-tricks. 。使用 boost::type_traits 多くの目的で使用できます (型が整数であるかどうかの確認など)。メンバー関数の存在を確認するには、次を使用します。 関数の存在を確認するテンプレートを作成することは可能ですか? 。特定のネストされた型が存在するかどうかを確認するには、プレーンを使用します スフィナエ .

むしろ、クラスにあるメソッドの数を確認したり、クラス ID の文字列表現を取得したりするなど、1) を達成する方法を探している場合は、標準 C++ でこれを行う方法はないと思います。どちらかを使用する必要があります

  • コードを翻訳して追加のメタ情報を追加する Qt Meta Object Compiler のようなメタ コンパイラー。
  • 必要なメタ情報を追加できるマクロで構成されるフレームワーク。すべてのメソッド、クラス名、基本クラス、および必要なものすべてをフレームワークに伝える必要があります。

C++ は速度を念頭に置いて作られています。C# や Java のような高レベルの検査が必要な場合は、残念ですが、ある程度の努力なしには方法はないと言わざるを得ません。

そして、私はポニーが大好きですが、ポニーは無料ではありません。:-p

http://en.wikibooks.org/wiki/C%2B%2B_Programming/RTTI それがあなたが得るものです。あなたが考えているようなリフレクション -- 実行時に利用可能な完全に記述的なメタデータ -- は、デフォルトでは C++ には存在しません。

RTTI は C++ には存在しません。

これは単純に間違っています。実際、「RTTI」という用語自体が C++ 標準によって作られたものです。一方、RTTI ではリフレクションの実装はそれほど進んでいません。

情報は存在しますが、必要な形式ではなく、クラスをエクスポートする場合にのみ存在します。これは 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 との間のストリーミングなどに非常に便利であり、特定の基本クラスから派生する必要がないことがわかりました。十分な需要があれば、リリースに向けて形を整えることができるかもしれません。

何をしようとしているのか、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, 、これは実行時 (構成ファイルの読み取り時) に一連のメソッド呼び出しに変換されるため、かなり効率的です。本当に必要な場合を除いて、これを行うことはお勧めしませんが、そうする場合は、非常に素晴らしいことができます。

反省して何をしようとしているのですか?
ブーストが使える タイプの特徴 そして の種類 コンパイル時リフレクションの限定された形式としてのライブラリ。つまり、テンプレートに渡された型の基本プロパティを検査および変更できます。

使用することをお勧めします Qt.

オープンソース ライセンスと商用ライセンスがあります。

編集: キャンプ は維持されなくなりました。2 つのフォークが利用可能です。

  • 1つとも呼ばれます キャンプ も同じ API に基づいています。
  • じっくり考える これは部分的な書き換えであり、 Boost を必要としないため推奨されます。C++11を使用しています。

キャンプ は、C++ 言語にリフレクションを追加する MIT ライセンス ライブラリ (以前の LGPL) です。コンパイル時に特定の前処理ステップは必要ありませんが、バインドは手動で行う必要があります。

現在の Tegesoft ライブラリは Boost を使用していますが、 フォーク C++11を使用すると、 ブーストはもう必要ありません.

私はあなたが求めているようなことを一度実行しました。ある程度の反省を得ることができ、より高いレベルの機能にアクセスすることは可能ですが、メンテナンスの悩みはそれだけの価値がないかもしれません。私のシステムは、Objective-C のメッセージの受け渡しと転送の概念に似た委任によって、UI クラスをビジネス ロジックから完全に分離するために使用されました。その方法は、シンボル (文字列プールを使用しましたが、完全な柔軟性よりも速度とコンパイル時のエラー処理を優先する場合は列挙型を使用することもできます) を関数ポインター (実際にはそうではありません) にマッピングできる基本クラスを作成することです。純粋な関数ポインターですが、Boost の Boost.Function に似たものです (当時はアクセスできませんでした)。任意の値を表すことができる共通の基本クラスがある限り、メンバー変数に対して同じことを行うことができます。システム全体は、Key-Value コーディングと委任を恥ずかしがらずにパクったもので、システムを使用するすべてのクラスをそのすべてのメソッドとメンバーを正当な呼び出しと一致させるのに必要な膨大な時間を費やす価値のある副作用がいくつかありました。 :1) ヘッダーを組み込んだり、偽の基本クラスを記述したりすることなく、どのクラスでも他のクラスのメソッドを呼び出すことができるため、コンパイラ用にインターフェイスを事前定義できます。2) メンバー変数のゲッターとセッターは、値の変更またはアクセスが常にすべてのオブジェクトの基本クラスの 2 つのメソッドを通じて行われるため、簡単にスレッドセーフにすることができました。

また、C++ では簡単ではない、非常に奇妙なことができる可能性も生まれました。たとえば、それ自体を含む任意の型の項目を含む Array オブジェクトを作成し、すべての配列項目にメッセージを渡して戻り値を収集することによって (Lisp のマップと同様に) 新しい配列を動的に作成できます。もう 1 つは、キーと値の監視の実装です。これにより、データを常にポーリングしたり、不必要に表示を再描画したりする代わりに、バックエンド クラスのメンバーの変更に即座に応答するように UI をセットアップできました。

おそらく、さらに興味深いのは、クラスに定義されているすべてのメソッドとメンバーを文字列形式でダンプできるという事実です。

このシステムの欠点としては、次のようなことが考えられます。すべてのメッセージと Key-Value を追加するのは非常に面倒です。反射がない場合よりも遅くなります。見るのが嫌になるだろう boost::static_pointer_cast そして boost::dynamic_pointer_cast 猛烈な情熱を持ってコードベース全体に働きかけます。強く型付けされたシステムの制限はまだ存在します。実際には、それほど明白ではないように、それらを少し隠しているだけです。文字列のタイプミスも、驚くべきことではなく、簡単に発見できるものではありません。

このようなものを実装する方法については、次のようになります。いくつかの共通ベース (私のものは非常に想像力豊かに「オブジェクト」と呼ばれていました) への共有ポインターと弱いポインターを使用し、使用したいすべての型を派生するだけです。私がやった方法ではなく、Boost.Function をインストールすることをお勧めします。これは、関数ポインター呼び出しをラップするためのカスタムのくだらないマクロと大量の醜いマクロを使用していました。すべてがマップされているため、オブジェクトの検査はすべてのキーを反復処理するだけです。私のクラスは基本的に C++ のみを使用して Cocoa の直接のパクリに限りなく近いものであったため、そのようなものが必要な場合は、Cocoa のドキュメントをブループリントとして使用することをお勧めします。

C++ 時代から私が知っているリフレクションのような解決策は次の 2 つです。

1) RTTI を使用します。これは、すべてのクラスを「オブジェクト」基本クラスから派生させることができる場合、リフレクションのような動作を構築するためのブートストラップを提供します。このクラスは、GetMethod、GetBaseClass などのメソッドを提供できます。これらのメソッドがどのように機能するかについては、型を修飾するいくつかのマクロを手動で追加する必要があります。マクロは、GetMethods などに応答を提供するために、バックグラウンドで型にメタデータを作成します。

2) コンパイラ オブジェクトにアクセスできる場合の別のオプションは、 DIA SDK. 。私の記憶が正しければ、これにより、C++ 型のメタデータが含まれる pdb を開くことができます。必要なことを行うだけで十分かもしれません。 このページ たとえば、クラスのすべての基本型を取得する方法を示します。

ただし、これらの解決策は両方とも少し醜いです。C# の贅沢さを理解するには、ちょっとした C++ ほど便利なものはありません。

幸運を。

C++ にはリフレクション用の別の新しいライブラリがあります。 RTTR (実行時タイプの反映、も参照してください) ギットハブ).

インターフェイスは C# のリフレクションに似ており、RTTI なしで動作します。

編集:2017 年 2 月 7 日時点でリンク切れを更新しました。

誰もこれについて言及していないと思います:

CERN では、C++ のフル リフレクション システムを使用しています。

CERN 反射. 。とてもうまくいきそうです。

リフレクションは、C++ ではそのままではサポートされていません。防御テストが面倒になるので、これは悲しいことです。

リフレクションを行うには、いくつかのアプローチがあります。

  1. デバッグ情報を使用します (移植性はありません)。
  2. コードにマクロ/テンプレート、またはその他のソース アプローチを散りばめます (見苦しくなります)。
  3. Clang/gcc などのコンパイラを変更してデータベースを作成します。
  4. Qt moc アプローチを使用する
  5. ブーストリフレクト
  6. 正確でフラットな反射

最初のリンクが最も有望に見えます (Clang に MOD を使用)、2 番目のリンクでは多くのテクニックについて説明し、3 番目のリンクは gcc を使用した別のアプローチです。

  1. http://www.donw.org/rfl/

  2. https://bitbucket.org/dwilliamson/clreflect

  3. https://root.cern.ch/how/how-use-reflex

現在、C++ リフレクションのためのワーキング グループが存在します。C++14 @ CERN のニュースを参照してください。

編集 13/08/17:最初の投稿以来、リフレクションに関しては多くの潜在的な進歩が見られました。以下に、さまざまなテクニックとステータスの詳細と説明を示します。

  1. 静的反射の概要
  2. 静的反射
  3. 静的反射を考慮した設計

ただし、C++ でのリフレクションのサポートに対するコミュニティからの関心がさらに高くならない限り、近い将来、C++ での標準化されたリフレクション アプローチは期待できそうにありません。

以下は、前回の C++ 標準会議からのフィードバックに基づいた現在のステータスの詳細です。

編集 2017/12/13

Reflection は C++ 20、あるいはおそらく TSR に向けて移行しているようです。ただし動きは遅いです。

編集 2018/09/15

TS 草案が投票のために各国機関に送信されました。

テキストは次の場所にあります。 https://github.com/cplusplus/reflection-ts

この質問は今では少し古いものです(なぜ今日古い質問を繰り返しているのかわかりません)が、私は次のことを考えていました BOOST_FUSION_ADAPT_STRUCT これにより、コンパイル時のリフレクションが導入されます。

もちろん、これを実行時のリフレクションにマッピングするのはあなた次第で、それほど簡単ではありませんが、この方向では可能ですが、その逆は不可能です:)

私は本当にマクロをカプセル化すると思います BOOST_FUSION_ADAPT_STRUCT 実行時の動作を取得するために必要なメソッドを生成できます。

Dominic Filion による記事「Using Templates for Reflection in C++」が興味深いかもしれません。のセクション 1.4 にあります。 ゲームプログラミングの宝石 5. 。残念ながら、私のコピーは手元にありませんが、あなたが求めていることを説明していると思うので探してください。

リフレクションは基本的に、ランタイム コードがクエリできるコード内に、コンパイラがフットプリントとして残すことを決定したものに関するものです。C++ は、使用しないものには料金を支払わないことで有名です。ほとんどの人はリフレクションを使用しない/望んでいないため、C++ コンパイラーは記録を行わないことでコストを回避します。 何でも.

したがって、C ++はリフレクションを提供せず、他の回答が指摘したように、原則として自分でそれを「シミュレート」するのは簡単ではありません。

「その他のテクニック」で、リフレクションを備えた言語を持っていない場合は、 コンパイル時に必要な情報を抽出できるツールを入手してください。

私たちの DMS ソフトウェア リエンジニアリング ツールキット 明示的な言語定義によってパラメータ化された汎用コンパイラ テクノロジです。C、C++、Java、COBOL、PHP などの言語定義があります。

C、C++、Java、および COBOL バージョンの場合、解析ツリーおよびシンボル テーブル情報への完全なアクセスが提供されます。そのシンボル テーブル情報には、「リフレクション」から必要になる可能性が高い種類のデータが含まれています。目的がフィールドまたはメソッドのセットを列挙することである場合、 する これらを使用すると、DMS を使用して、シンボル テーブルで見つかった内容に従って任意の方法でコードを変換できます。

ここで別のライブラリを見つけることができます。 http://www.garret.ru/cppreflection/docs/reflect.html次の 2 つの方法をサポートしています。デバッグ情報から型情報を取得し、プログラマがこの情報を提供できるようにします。

私は自分のプロジェクトのリフレクションにも興味があり、このライブラリを見つけました。まだ試していませんが、この人の他のツールを試してみたところ、その動作が気に入っています:-)

クラスデスクをチェックしてください http://classdesc.sf.net. 。これはクラス「記述子」の形式でリフレクションを提供し、標準の C++ コンパイラで動作し (はい、Visual Studio および GCC で動作することが知られています)、ソース コードの注釈を必要としません (ただし、難しい状況を処理するためにいくつかのプラグマが存在します) )。10 年以上にわたって開発されており、多くの産業規模のプロジェクトで使用されています。

C++でリフレクションが必要なときに読んだ この記事 そこで見たものを改良しました。申し訳ありませんが、缶にはありません。私は結果を所有していません...しかし、あなたは確かに私が持っていたものを手に入れ、そこから進むことができます。

現在、気が向いたときに、inherit_lineearly を使用してリフレクタブルな型の定義を簡単にする方法を研究しています。実際にはかなりのところまで進んできましたが、まだ道はあります。C++0x の変更は、この分野で大いに役立つ可能性があります。

C++ にはまだこの機能がないようです。そして C++11 反省も後回し((

いくつかのマクロを検索するか、独自に作成してください。Qt はリフレクションにも役立ちます (使用できる場合)。

このプロジェクトを見てみてください http://www.garret.ru/cppreflection/docs/reflect.htmlC++ にリフレクションが追加されました。これにより、使用できるメタデータがクラスに追加されました。

C++ ではリフレクションがすぐにサポートされていませんが、実装はそれほど難しくありません。こんな素晴らしい記事に出会いました。http://replicaisland.blogspot.co.il/2010/11/building-reflective-object-system-in-c.html

この記事では、非常にシンプルで初歩的なリフレクション システムを実装する方法について詳しく説明しています。確かに、これは最も健全な解決策ではなく、未解決の部分も残っていますが、私のニーズには十分でした。

結論としては、リフレクションは正しく行われれば効果があり、C++ では完全に実現可能です。

自動内省・振り返りツールキット「IDK」の存在を宣伝したいと思います。Qt のようなメタコンパイラーを使用し、メタ情報をオブジェクト ファイルに直接追加します。使いやすいと主張されています。外部依存関係はありません。std::string を自動的に反映してスクリプトで使用することもできます。見てください IDK

C++ のリフレクションは、メンバーごとに何らかのメソッドを実行する必要がある場合に非常に便利です (例:シリアル化、ハッシュ、比較)。非常に単純な構文を使用した一般的なソリューションを用意しました。

struct S1
{
    ENUMERATE_MEMBERS(str,i);
    std::string str;
    int i;
};
struct S2
{
    ENUMERATE_MEMBERS(s1,i2);
    S1 s1;
    int i2;
};

ここで、ENUMERATE_MEMBERS はマクロであり、これについては後で説明します (UPDATE)。

次のように int と std::string のシリアル化関数を定義したとします。

void EnumerateWith(BinaryWriter & writer, int val)
{
    //store integer
    writer.WriteBuffer(&val, sizeof(int));
}
void EnumerateWith(BinaryWriter & writer, std::string val)
{
    //store string
    writer.WriteBuffer(val.c_str(), val.size());
}

そして、「秘密マクロ」の近くに汎用関数があります ;)

template<typename TWriter, typename T>
auto EnumerateWith(TWriter && writer, T && val) -> is_enumerable_t<T>
{
    val.EnumerateWith(write); //method generated by ENUMERATE_MEMBERS macro
}

今、あなたは書くことができます

S1 s1;
S2 s2;
//....
BinaryWriter writer("serialized.bin");

EnumerateWith(writer, s1); //this will call EnumerateWith for all members of S1
EnumerateWith(writer, s2); //this will call EnumerateWith for all members of S2 and S2::s1 (recursively)

したがって、構造体定義に ENUMERATE_MEMBERS マクロを含めると、元の型に触れることなくシリアル化、比較、ハッシュなどを構築できます。唯一の要件は、列挙子 (BinaryWriter など) ごとに、列挙可能ではない各型の "EnumerateWith" メソッドを実装することです。 。通常、プロジェクト内の任意の型をサポートするには、10 ~ 20 個の「単純な」型を実装する必要があります。

このマクロは、実行時に構造体を作成/破棄するオーバーヘッドがゼロである必要があり、T.EnumerateWith() のコードはオンデマンドで生成される必要があります。これは、テンプレートインライン関数にすることで実現できるため、唯一のオーバーヘッドが発生します。すべての話は、各構造体に ENUMERATE_MEMBERS(m1,m2,m3...) を追加することですが、メンバーの型ごとに特定のメソッドを実装することはどのソリューションでも必須であるため、それがオーバーヘッドであるとは想定しません。

アップデート:ENUMERATE_MEMBERS マクロの非常に単純な実装があります (ただし、列挙可能な構造体からの継承をサポートするために少し拡張することができます)。

#define ENUMERATE_MEMBERS(...) \
template<typename TEnumerator> inline void EnumerateWith(TEnumerator & enumerator) const { EnumerateWithHelper(enumerator, __VA_ARGS__ ); }\
template<typename TEnumerator> inline void EnumerateWith(TEnumerator & enumerator) { EnumerateWithHelper(enumerator, __VA_ARGS__); }

// EnumerateWithHelper
template<typename TEnumerator, typename ...T> inline void EnumerateWithHelper(TEnumerator & enumerator, T &...v) 
{ 
    int x[] = { (EnumerateWith(enumerator, v), 1)... }; 
}

// Generic EnumerateWith
template<typename TEnumerator, typename T>
auto EnumerateWith(TEnumerator & enumerator, T & val) -> std::void_t<decltype(val.EnumerateWith(enumerator))>
{
    val.EnumerateWith(enumerator);
}

そして、これらの 15 行のコードにはサードパーティのライブラリは必要ありません ;)

C++20 では、次のことが可能になります。 展開ステートメント, これにより、集計タイプを反復処理できるようになります。

struct my_type {
    double data;
    std::string another_data;
    int last_data;
};

auto object = my_type{};

for...(auto& member : object) {
    using member_type = std::remove_cvref_t<decltype(member)>;
    member = get_data<member_type>();
}

比較的単純な C++ リフレクションを探している場合は、さまざまなソースからマクロ/定義を収集し、それらがどのように機能するかをコメントアウトしました。ここからヘッダーファイルをダウンロードできます。

https://github.com/tapika/TestCppReflect/blob/master/MacroHelpers.h

一連の定義とその上の機能:

https://github.com/tapika/TestCppReflect/blob/master/CppReflect.h https://github.com/tapika/TestCppReflect/blob/master/CppReflect.cpp https://github.com/tapika/TestCppReflect/blob/master/TypeTraits.h

サンプル アプリケーションは git リポジトリにも存在します。次の場所にあります。https://github.com/tapika/TestCppReflect/

説明とともに部分的にここにコピーします。

#include "CppReflect.h"
using namespace std;


class Person
{
public:

    // Repack your code into REFLECTABLE macro, in (<C++ Type>) <Field name>
    // form , like this:

    REFLECTABLE( Person,
        (CString)   name,
        (int)       age,
...
    )
};

void main(void)
{
    Person p;
    p.name = L"Roger";
    p.age = 37;
...

    // And here you can convert your class contents into xml form:

    CStringW xml = ToXML( &p );
    CStringW errors;

    People ppl2;

    // And here you convert from xml back to class:

    FromXml( &ppl2, xml, errors );
    CStringA xml2 = ToXML( &ppl2 );
    printf( xml2 );

}

REFLECTABLE 定義ではクラス名 + フィールド名を使用します offsetof - 特定のフィールドがメモリ内のどの場所にあるかを識別します。.NET の用語については可能な限り拾ってみましたが、C++ と C# は異なるため、1 対 1 ではありません。C++ リフレクション モデル全体が存在します。 TypeInfo そして FieldInfo クラス。

私はpugi xmlパーサーを使用してデモコードをxmlにフェッチし、xmlから復元しました。

したがって、デモ コードによって生成される出力は次のようになります。

<?xml version="1.0" encoding="utf-8"?>
<People groupName="Group1">
    <people>
        <Person name="Roger" age="37" />
        <Person name="Alice" age="27" />
        <Person name="Cindy" age="17" />
    </people>
</People>

TypeTraits クラスおよび部分的なテンプレート仕様を介してサードパーティのクラス/構造のサポートを有効にすることもできます。CString または int と同様の方法で、独自の TypeTraitsT クラスを定義します。サンプル コードを参照してください。

https://github.com/tapika/TestCppReflect/blob/master/TypeTraits.h#L195

このソリューションは Windows / Visual Studio に適用できます。他の OS/コンパイラに移植することは可能ですが、まだ行っていません。(解決策が本当に気に入っているかどうか聞いてください。お手伝いできるかもしれません)

このソリューションは、複数のサブクラスを持つ 1 つのクラスのワンショット シリアル化に適用できます。

ただし、クラス部分をシリアル化するメカニズム、またはリフレクション呼び出しが生成する機能を制御するメカニズムを探している場合は、次の解決策を検討してください。

https://github.com/tapika/cppscriptcore/tree/master/SolutionProjectModel

より詳細な情報は YouTube ビデオからご覧いただけます。

C++ ランタイム型リフレクションhttps://youtu.be/TN8tJijkeFE

C++ リフレクションがどのように機能するかについてもう少し詳しく説明しようとしています。

サンプルコードは、たとえば次のようになります。

https://github.com/tapika/cppscriptcore/blob/master/SolutionProjectModel/testCppApp.cpp

c.General.IntDir = LR"(obj\$(ProjectName)_$(Configuration)_$(Platform)\)";
c.General.OutDir = LR"(bin\$(Configuration)_$(Platform)\)";
c.General.UseDebugLibraries = true;
c.General.LinkIncremental = true;
c.CCpp.Optimization = optimization_Disabled;
c.Linker.System.SubSystem = subsystem_Console;
c.Linker.Debugging.GenerateDebugInformation = debuginfo_true;

しかし、ここでの各ステップは、実際にはC ++プロパティを使用して関数呼び出しをもたらします __declspec(property(get =, put ... ).

これは、C++ データ型、C++ プロパティ名、クラス インスタンス ポインターに関する完全な情報をパスの形式で受け取り、その情報に基づいて XML、JSON を生成したり、インターネット経由で XML や JSON をシリアル化したりすることができます。

このような仮想コールバック関数の例は、次の場所にあります。

https://github.com/tapika/cppscriptcore/blob/master/SolutionProjectModel/VCConfiguration.cpp

機能を参照 ReflectCopy, 、および仮想関数 ::OnAfterSetProperty.

ただし、トピックは非常に高度なので、最初にビデオで確認することをお勧めします。

改善アイデアがありましたら、お気軽にご連絡ください。

Root Reflex プロジェクトはこれをサポートしています。

見る https://root.cern.ch/how/how-use-reflex

次のように関数へのポインタを宣言すると、次のようになります。

int (*func)(int a, int b);

次のようにして、メモリ内の場所をその関数に割り当てることができます (必須 libdl そして dlopen)

#include <dlfcn.h>

int main(void)
{
    void *handle;
    char *func_name = "bla_bla_bla";
    handle = dlopen("foo.so", RTLD_LAZY);
    *(void **)(&func) = dlsym(handle, func_name);
    return func(1,2);
}

間接指定を使用してローカル シンボルをロードするには、次を使用できます。 dlopen 呼び出し側バイナリ (argv[0]).

このための唯一の要件(以外) dlopen(), libdl, 、 そして dlfcn.h)関数の引数と型を知っていることです。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top