Wie kann ich einer C ++ - Anwendung Reflexion hinzufügen?

263

Ich möchte in der Lage sein, eine C ++ - Klasse auf ihren Namen, Inhalt (dh Mitglieder und ihre Typen) usw. zu überprüfen. Ich spreche hier von nativem C ++, nicht von verwaltetem C ++, das reflektiert wird. Mir ist klar, dass C ++ mit RTTI nur begrenzte Informationen liefert. Welche zusätzlichen Bibliotheken (oder andere Techniken) könnten diese Informationen liefern?

Nick
quelle
18
Pech, Sie können es nicht ohne Makros und andere Vorverarbeitung tun, da die erforderlichen Metadaten nur vorhanden sind, wenn Sie sie manuell durch eine Makro-Vorverarbeitungsmagie erstellen.
Jalf
6
Die Informationen, die Sie von RTTI zurückerhalten können, reichen jedoch nicht aus, um die meisten Dinge zu tun, für die Sie tatsächlich eine Reflexion wünschen. Sie können beispielsweise nicht über die Elementfunktionen einer Klasse iterieren.
Joseph Garvin

Antworten:

260

Was Sie tun müssen, ist, dass der Präprozessor Reflexionsdaten über die Felder generiert. Diese Daten können als verschachtelte Klassen gespeichert werden.

Um es einfacher und sauberer zu machen, es in den Präprozessor zu schreiben, verwenden wir zunächst einen typisierten Ausdruck. Ein typisierter Ausdruck ist nur ein Ausdruck, der den Typ in Klammern setzt. Also anstatt zu schreiben, int xwirst du schreiben (int) x. Hier sind einige nützliche Makros, die bei getippten Ausdrücken helfen:

#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

Als nächstes definieren wir ein REFLECTABLEMakro, um die Daten für jedes Feld (plus das Feld selbst) zu generieren. Dieses Makro wird folgendermaßen aufgerufen:

REFLECTABLE
(
    (const char *) name,
    (int) age
)

Mit Boost.PP iterieren wir also über jedes Argument und generieren die Daten wie folgt :

// 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)); \
    } \
}; \

Dadurch wird eine Konstante generiert fields_n, die die Anzahl der reflektierbaren Felder in der Klasse darstellt. Dann ist es das field_datafür jedes Feld spezialisiert. Es befreundet auch die reflectorKlasse, so dass es auf die Felder zugreifen kann, auch wenn sie privat sind:

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;
    };
};

Um nun über die Felder zu iterieren, verwenden wir das Besuchermuster. Wir erstellen einen MPL-Bereich von 0 bis zur Anzahl der Felder und greifen auf die Felddaten an diesem Index zu. Anschließend werden die Felddaten an den vom Benutzer bereitgestellten Besucher weitergeleitet:

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));
}

Jetzt für den Moment der Wahrheit setzen wir alles zusammen. So können wir eine PersonKlasse definieren , die reflektierbar ist:

struct Person
{
    Person(const char *name, int age)
        :
        name(name),
        age(age)
    {
    }
private:
    REFLECTABLE
    (
        (const char *) name,
        (int) age
    )
};

Hier ist eine verallgemeinerte print_fieldsFunktion, die die Reflexionsdaten verwendet, um über die Felder zu iterieren:

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());
}

Ein Beispiel für die Verwendung der print_fieldsmit der reflektierbaren PersonKlasse:

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

Welche Ausgänge:

name=Tom
age=82

Und voila, wir haben gerade die Reflexion in C ++ in weniger als 100 Codezeilen implementiert.

Paul Fultz II
quelle
106
Ein großes Lob dafür, dass gezeigt wird, wie man Reflexion umsetzt, anstatt zu sagen, dass dies nicht möglich ist. Es sind Antworten wie diese, die SO zu einer großartigen Ressource machen.
Fearless_fool
4
Beachten Sie, dass beim Versuch, dies unter Visual Studio zu kompilieren, eine Fehlermeldung angezeigt wird, da VS die variable Makroerweiterung nicht ordnungsgemäß verarbeitet. Versuchen Sie für VS, Folgendes hinzuzufügen: #define DETAIL_TYPEOF_INT2(tuple) DETAIL_TYPEOF_HEAD tupleund #define DETAIL_TYPEOF_INT(...) DETAIL_TYPEOF_INT2((__VA_ARGS__))und ändern Sie die Definition von TYPEOF (x) in:#define TYPEOF(x) DETAIL_TYPEOF_INT(DETAIL_TYPEOF_PROBE x,)
Phenglei Kai
Ich erhalte die Fehlermeldung 'BOOST_PP_IIF_0' benennt keinen Typ. Können Sie bitte helfen.
Ankit Zalani
3
Siehe meine eigene Antwort - stackoverflow.com/a/28399807/2338477 Ich habe alle Definitionen extrahiert und neu gepackt , und eine Boost-Bibliothek ist nicht erforderlich. Als Demo-Code biete ich Serialisierung für XML und Wiederherstellung von XML.
TarmoPikaro
107

Es gibt zwei Arten des reflectionSchwimmens.

  1. Überprüfung durch Durchlaufen von Elementen eines Typs, Auflisten seiner Methoden usw.

    Dies ist mit C ++ nicht möglich.
  2. Die Überprüfung durch Überprüfen, ob ein Klassentyp (Klasse, Struktur, Vereinigung) eine Methode oder einen verschachtelten Typ hat, wird von einem anderen bestimmten Typ abgeleitet.

    So etwas ist mit C ++ möglich template-tricks. Verwenden Sie es boost::type_traitsfür viele Dinge (z. B. um zu überprüfen, ob ein Typ ganzzahlig ist). Verwenden Sie zum Überprüfen der Existenz einer Mitgliedsfunktion die Option Ist es möglich, eine Vorlage zu schreiben, um die Existenz einer Funktion zu überprüfen? . Verwenden Sie einfaches SFINAE , um zu überprüfen, ob ein bestimmter verschachtelter Typ vorhanden ist .

Wenn Sie eher nach Möglichkeiten suchen, um 1) zu erreichen, z. B. nach der Anzahl der Methoden einer Klasse oder nach der Zeichenfolgendarstellung einer Klassen-ID, dann gibt es leider keine Standard-C ++ - Methode, um dies zu tun. Sie müssen entweder verwenden

  • Ein Meta-Compiler wie der Qt Meta Object Compiler, der Ihren Code übersetzt und zusätzliche Meta-Informationen hinzufügt.
  • Ein Framework, das aus Makros besteht, mit denen Sie die erforderlichen Metainformationen hinzufügen können. Sie müssten dem Framework alle Methoden, Klassennamen, Basisklassen und alles, was es benötigt, mitteilen.

C ++ ist auf Geschwindigkeit ausgelegt. Wenn Sie eine Inspektion auf hoher Ebene wünschen, wie dies bei C # oder Java der Fall ist, muss ich Ihnen leider sagen, dass es keinen Weg ohne Aufwand gibt.

Johannes Schaub - litb
quelle
122
C ++ ist auf Geschwindigkeit ausgelegt, aber die Philosophie ist nicht "so schnell wie möglich", sondern "Sie zahlen nicht dafür, wenn Sie es nicht verwenden". Ich glaube, es ist möglich, dass eine Sprache Introspektion auf eine Weise implementiert, die zu dieser Philosophie passt. C ++ fehlt es einfach.
Joseph Garvin
8
@ Joseph: Wie soll das gemacht werden? Alle Metadaten müssten gespeichert werden. Das heißt, Sie müssen dafür bezahlen, auch wenn Sie es nicht benutzen. (Es sei denn, Sie könnten einzelne Typen als "unterstützende Reflexion" markieren, aber dann sind wir fast am Ende, wo wir genauso gut die vorhandenen Makro-Tricks
anwenden
25
@jalf: Nur die Metadaten, die möglicherweise benötigt werden. Wenn wir nur die Reflexion zur Kompilierungszeit betrachten, ist dies trivial. Beispiel: Eine Funktion zur Kompilierungszeit, members<T>die eine Liste aller Mitglieder von T zurückgibt. Wenn wir eine Laufzeitreflexion wünschen würden (dh RTTI gemischt mit Reflexion), würde der Compiler immer noch alle reflektierten Basistypen kennen. Es ist sehr wahrscheinlich members<T>(T&), dass T = std :: string niemals instanziiert wird, daher muss die RTTI für std :: string oder die abgeleiteten Klassen nicht enthalten sein.
MSalters
9
Die Reflexbibliothek (unten erwähnt) fügt Reflexion zu C ++ hinzu, ohne den vorhandenen Code zu verlangsamen: root.cern.ch/drupal/content/reflex
Joseph Lisee
6
@ Joe: Reflexion verlangsamt niemals vorhandenen Code. Es macht nur das gelieferte Zeug größer (da Sie eine Typinfodatenbank liefern müssen ...).
mmmmmmmm
57

Und ich würde ein Pony lieben, aber Ponys sind nicht frei. :-p

http://en.wikibooks.org/wiki/C%2B%2B_Programming/RTTI ist das, was Sie bekommen werden. Reflexion, über die Sie nachdenken - vollständig beschreibende Metadaten zur Laufzeit verfügbar - ist für C ++ standardmäßig nicht vorhanden.

Brad Wilson
quelle
1
Ich bin zweiter Brad. C ++ - Vorlagen können sehr leistungsfähig sein, und es gibt eine Fülle von Erfahrungen mit verschiedenen Verhaltensweisen des Typs "Reflektion", z. B. beim Boosten "jeder" Bibliothek, Typmerkmale, C ++ RTTI usw., die viele der Probleme lösen können, für die die Reflexion gelöst wird. Also Nick, was ist dein Ziel hier?
Aaron
7
Upvote für die Ponys Bemerkung! Ich würde zweimal upvoten, da Ihre Antwort es auch verdient, aber leider bekomme ich nur eine, also gewinnen Ponys. :-)
Franci Penov
6
Ich verstehe nicht wirklich, warum dies eine kluge Antwort ist. Ich habe bereits gesagt, ich möchte Verweise auf Bibliotheken usw., um dies zu implementieren. Die Reflexion / Selbstbeobachtung ist für verschiedene Systeme vorgesehen, um Skriptzugriff, Serialisierung usw. zu ermöglichen
Nick
3
@ Nick: Das hat er schon beantwortet. Dies ist nicht möglich, die Daten sind nicht vorhanden und daher kann keine Bibliothek sie für Sie implementieren.
Jalf
@jalf Immer noch seltsam für mich, lesen Leute in der Programmierwelt, die sagen: "Es ist nicht möglich" und nicht "Ich weiß nicht wie". Sicher, die Metadaten existieren nicht, können aber mit Makros eingefügt werden
Freddx L.
39

Die Informationen sind vorhanden - jedoch nicht in dem von Ihnen benötigten Format und nur, wenn Sie Ihre Klassen exportieren. Dies funktioniert unter Windows, ich weiß nichts über andere Plattformen. Verwenden Sie die Speicherklassenspezifizierer wie in:

class __declspec(export) MyClass
{
public:
    void Foo(float x);
}

Dadurch erstellt der Compiler die Klassendefinitionsdaten in die DLL / Exe. Aber es ist nicht in einem Format, das Sie leicht zum Nachdenken verwenden können.

In meiner Firma haben wir eine Bibliothek erstellt, die diese Metadaten interpretiert und es Ihnen ermöglicht, eine Klasse zu reflektieren, ohne zusätzliche Makros usw. in die Klasse selbst einzufügen. Damit können Funktionen wie folgt aufgerufen werden:

MyClass *instance_ptr=new MyClass;
GetClass("MyClass")->GetFunction("Foo")->Invoke(instance_ptr,1.331);

Dies bewirkt effektiv:

instance_ptr->Foo(1.331);

Die Funktion Invoke (this_pointer, ...) verfügt über variable Argumente. Wenn Sie eine Funktion auf diese Weise aufrufen, umgehen Sie natürlich Dinge wie const-security usw., sodass diese Aspekte als Laufzeitprüfungen implementiert werden.

Ich bin sicher, dass die Syntax verbessert werden könnte und bisher nur unter Win32 und Win64 funktioniert. Wir haben festgestellt, dass es sehr nützlich ist, automatische GUI-Schnittstellen zu Klassen zu haben, Eigenschaften in C ++ zu erstellen, zu und von XML zu streamen usw. Es ist nicht erforderlich, von einer bestimmten Basisklasse abzuleiten. Wenn es genügend Nachfrage gibt, könnten wir es vielleicht für die Veröffentlichung in Form bringen.

Roderick
quelle
1
Ich denke du meinst __declspec(dllexport)und du kannst die Informationen aus einer .map-Datei abrufen, wenn du die Erstellung solcher während des Builds aktivierst.
Orwellophil
17

Die Reflexion wird von C ++ nicht sofort unterstützt. Das ist traurig, weil es Defensivtests zu einem Schmerz macht.

Es gibt verschiedene Ansätze zur Reflexion:

  1. Verwenden Sie die Debug-Informationen (nicht portabel).
  2. Besprühen Sie Ihren Code mit Makros / Vorlagen oder einem anderen Quellansatz (sieht hässlich aus).
  3. Ändern Sie einen Compiler wie clang / gcc, um eine Datenbank zu erstellen.
  4. Verwenden Sie den Qt moc-Ansatz
  5. Boost Reflect
  6. Präzise und flache Reflexion

Der erste Link sieht am vielversprechendsten aus (verwendet Mods zum Klirren), der zweite beschreibt eine Reihe von Techniken, der dritte ist ein anderer Ansatz mit gcc:

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

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

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

Es gibt jetzt eine Arbeitsgruppe für C ++ - Reflexion. Siehe die Nachrichten für C ++ 14 @ CERN:

Bearbeiten 13/08/17:

Seit dem ursprünglichen Beitrag gab es eine Reihe möglicher Fortschritte bei der Reflexion. Im Folgenden finden Sie weitere Details und eine Diskussion zu den verschiedenen Techniken und dem Status:

  1. Statische Reflexion auf den Punkt gebracht
  2. Statische Reflexion
  3. Ein Design für statische Reflexion

Ein standardisierter Reflexionsansatz in C ++ sieht jedoch in naher Zukunft nicht vielversprechend aus, es sei denn, die Community hat ein viel größeres Interesse an der Unterstützung der Reflexion in C ++.

Im Folgenden wird der aktuelle Status basierend auf dem Feedback des letzten C ++ - Standardtreffens beschrieben:

Bearbeiten 13/12/2017

Die Reflexion scheint sich in Richtung C ++ 20 oder wahrscheinlicher zu bewegen, wahrscheinlich als TSR. Die Bewegung ist jedoch langsam.

Bearbeiten 15/09/2018

Ein Entwurf eines TS wurde zur Abstimmung an die nationalen Stellen geschickt.

Den Text finden Sie hier: https://github.com/cplusplus/reflection-ts

Bearbeiten 11/07/2019

Die Reflexion TS ist vollständig und kann im Sommer (2019) kommentiert und abgestimmt werden.

Der Meta-Template-Programmieransatz soll durch einen einfacheren Compile-Time-Code-Ansatz ersetzt werden (nicht im TS enthalten).

Bearbeiten 10/02/2020

Hier wird die Anforderung zur Unterstützung des Reflection TS in Visual Studio angefordert:

Vortrag des Autors David Sankel über die TS:

Bearbeiten 17. März 2020

Fortschritte bei der Reflexion werden gemacht. Einen Bericht aus dem Reisebericht des ISO C ++ - Komitees 2020-02 in Prag finden Sie hier:

Details dazu, was für C ++ 23 in Betracht gezogen wird, finden Sie hier (einschließlich eines kurzen Abschnitts über Reflexion):

Bearbeiten 4. Juni 2020

Jeff Preshing hat ein neues Framework namens "Plywood" veröffentlicht, das einen Mechanismus zur Laufzeitreflexion enthält. Weitere Details finden Sie hier:

Die Werkzeuge und der Ansatz scheinen die poliertesten und am einfachsten zu verwendenden zu sein.

Damian Dixon
quelle
1
Die Cern-Verbindung ist unterbrochen.
Mostowski Zusammenbruch
cern links sollten jetzt behoben sein. Sie neigen dazu, ziemlich häufig zu brechen, was ein Schmerz ist.
Damian Dixon
Berücksichtigt diese Antwort nur die Reflexion zur Kompilierungszeit?
Einpoklum
@einpoklum Die einzigen aktuellen Lösungen für die Reflexion sind die Kompilierungszeiten, normalerweise mit Meta-Template-Code oder Makros. Der neueste TS-Entwurf scheint für die Laufzeit zu funktionieren, aber Sie müssen alle Bibliotheken mit dem richtigen Compiler erstellt haben, damit die erforderlichen Metadaten gespeichert werden können.
Damian Dixon
@ DamianDixon: Das stimmt nicht. Es gibt mehrere Laufzeitreflexionsbibliotheken. Zugegeben, sie sind ziemlich klobig und entweder opt-in oder erfordern Compiler-Nodifikationen, aber sie existieren immer noch. Wenn Sie, wie ich Ihren Kommentar verstehe, nur auf die Reflexion zur Kompilierungszeit Bezug genommen haben, bearbeiten Sie bitte Ihre Antwort, um sie klarer zu machen.
Einpoklum
15

Sie müssen sich ansehen, was Sie versuchen und ob RTTI Ihre Anforderungen erfüllt. Ich habe meine eigene Pseudoreflexion für einige ganz bestimmte Zwecke implementiert. Zum Beispiel wollte ich einmal flexibel konfigurieren können, was eine Simulation ausgeben würde. Es war erforderlich, den Klassen, die ausgegeben werden sollten, Boilerplate-Code hinzuzufügen:

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;
}

Der erste Aufruf fügt dieses Objekt dem Filtersystem hinzu, das die BuildMap()Methode aufruft , um herauszufinden, welche Methoden verfügbar sind.

In der Konfigurationsdatei können Sie dann Folgendes tun:

FILTER-OUTPUT-OBJECT   MyObject
FILTER-OUTPUT-FILENAME file.txt
FILTER-CLAUSE-1        person == 1773
FILTER-CLAUSE-2        time > 2000

Durch einige Vorlagenmagie boostwird dies zur Laufzeit (wenn die Konfigurationsdatei gelesen wird) in eine Reihe von Methodenaufrufen übersetzt, sodass es ziemlich effizient ist. Ich würde dies nicht empfehlen, es sei denn, Sie müssen es wirklich tun, aber wenn Sie es tun, können Sie einige wirklich coole Sachen machen.

KeithB
quelle
Muss ich diese Funktionen lieben, die immer wahr zurückgeben;) Ich nehme an, dies ist immun gegen statische Init-Ordnungsprobleme?
Paulm
14

Ich würde die Verwendung von Qt empfehlen .

Es gibt eine Open-Source-Lizenz sowie eine kommerzielle Lizenz.

Jérôme
quelle
1
Ich habe mir das angeschaut, aber es verwendet Makros und der Quellcode muss analysiert werden, um den Metadatencode zu generieren. Ich möchte diesen zusätzlichen Schritt vermeiden. Ich würde lieber eine C ++ - Bibliothek oder einfache Makros verwenden. Danke für die Idee.
Nick
10
QT oder eine andere Bibliothek, die einen ähnlichen Ansatz implementiert, ist das Beste, was Sie bekommen werden
Jalf
5
Zahlen Sie zur Kompilierungszeit oder zur Laufzeit - egal wie Sie bezahlen!
Martin Beckett
13

Was versuchst du mit Reflexion zu tun?
Sie können die boost Typ Züge und TypeOf Bibliotheken als begrenzte Form der Kompilierung-Reflexion. Das heißt, Sie können die grundlegenden Eigenschaften eines an eine Vorlage übergebenen Typs überprüfen und ändern.

Ferruccio
quelle
13

EDIT : CAMP wird nicht mehr gepflegt; Es stehen zwei Gabeln zur Verfügung:

  • Eines wird auch als CAMP bezeichnet und basiert auf derselben API.
  • Ponder ist ein teilweises Umschreiben und wird bevorzugt, da es keinen Boost erfordert. Es verwendet C ++ 11.

CAMP ist eine MIT-lizenzierte Bibliothek (ehemals LGPL), die die C ++ - Sprache reflektiert. Es ist kein spezifischer Vorverarbeitungsschritt in der Kompilierung erforderlich, aber die Bindung muss manuell erfolgen.

Die aktuelle Tegesoft-Bibliothek verwendet Boost, aber es gibt auch einen Fork mit C ++ 11, für den Boost nicht mehr erforderlich ist .

Philant
quelle
11

Ich habe so etwas wie das getan, wonach Sie einmal gesucht haben, und obwohl es möglich ist, ein gewisses Maß an Reflexion und Zugriff auf übergeordnete Funktionen zu erhalten, sind die Wartungsprobleme möglicherweise nicht wert. Mein System wurde verwendet, um die UI-Klassen durch Delegierung vollständig von der Geschäftslogik zu trennen, ähnlich dem Objective-C-Konzept der Nachrichtenübermittlung und -weiterleitung. Die Möglichkeit besteht darin, eine Basisklasse zu erstellen, die Symbole (ich habe einen Zeichenfolgenpool verwendet, aber Sie können dies mit Aufzählungen tun, wenn Sie die Fehlerbehandlung bei Geschwindigkeit und Kompilierungszeit der vollständigen Flexibilität vorziehen) Funktionszeigern zuordnen kann (eigentlich nicht) reine Funktionszeiger, aber etwas Ähnliches wie Boost mit Boost.Function (auf das ich damals keinen Zugriff hatte). Sie können dasselbe für Ihre Mitgliedsvariablen tun, solange Sie über eine gemeinsame Basisklasse verfügen, die einen beliebigen Wert darstellen kann. Das gesamte System war eine unerschrockene Abzocke der Codierung und Delegierung von Schlüsselwerten, mit einigen Nebenwirkungen, die möglicherweise die Zeit wert waren, die erforderlich war, um jede Klasse, die das System verwendete, dazu zu bringen, alle ihre Methoden und Mitglieder mit rechtlichen Aufrufen abzustimmen : 1) Jede Klasse kann jede Methode für jede andere Klasse aufrufen, ohne Header einschließen oder gefälschte Basisklassen schreiben zu müssen, damit die Schnittstelle für den Compiler vordefiniert werden kann. und 2) Die Getter und Setter der Mitgliedsvariablen waren leicht threadsicher zu machen, da das Ändern oder Zugreifen auf ihre Werte immer über zwei Methoden in der Basisklasse aller Objekte erfolgte. Das gesamte System war eine unerschrockene Abzocke der Codierung und Delegierung von Schlüsselwerten, mit einigen Nebenwirkungen, die möglicherweise die Zeit wert waren, die erforderlich war, um jede Klasse, die das System verwendete, dazu zu bringen, alle ihre Methoden und Mitglieder mit rechtlichen Aufrufen abzustimmen : 1) Jede Klasse kann jede Methode für jede andere Klasse aufrufen, ohne Header einschließen oder gefälschte Basisklassen schreiben zu müssen, damit die Schnittstelle für den Compiler vordefiniert werden kann. und 2) Die Getter und Setter der Mitgliedsvariablen waren leicht threadsicher zu machen, da das Ändern oder Zugreifen auf ihre Werte immer über zwei Methoden in der Basisklasse aller Objekte erfolgte. Das gesamte System war eine unerschrockene Abzocke der Codierung und Delegierung von Schlüsselwerten, mit einigen Nebenwirkungen, die möglicherweise die Zeit wert waren, die erforderlich war, um jede Klasse, die das System verwendete, dazu zu bringen, alle ihre Methoden und Mitglieder mit rechtlichen Aufrufen abzustimmen : 1) Jede Klasse kann jede Methode für jede andere Klasse aufrufen, ohne Header einschließen oder gefälschte Basisklassen schreiben zu müssen, damit die Schnittstelle für den Compiler vordefiniert werden kann. und 2) Die Getter und Setter der Mitgliedsvariablen waren leicht threadsicher zu machen, da das Ändern oder Zugreifen auf ihre Werte immer über zwei Methoden in der Basisklasse aller Objekte erfolgte. 1) Jede Klasse kann jede Methode für jede andere Klasse aufrufen, ohne Header einschließen oder gefälschte Basisklassen schreiben zu müssen, damit die Schnittstelle für den Compiler vordefiniert werden kann. und 2) Die Getter und Setter der Mitgliedsvariablen waren leicht threadsicher zu machen, da das Ändern oder Zugreifen auf ihre Werte immer über zwei Methoden in der Basisklasse aller Objekte erfolgte. 1) Jede Klasse kann jede Methode für jede andere Klasse aufrufen, ohne Header einschließen oder gefälschte Basisklassen schreiben zu müssen, damit die Schnittstelle für den Compiler vordefiniert werden kann. und 2) Die Getter und Setter der Mitgliedsvariablen waren leicht threadsicher zu machen, da das Ändern oder Zugreifen auf ihre Werte immer über zwei Methoden in der Basisklasse aller Objekte erfolgte.

Es führte auch zu der Möglichkeit, einige wirklich seltsame Dinge zu tun, die in C ++ sonst nicht einfach sind. Zum Beispiel könnte ich ein Array-Objekt erstellen, das beliebige Elemente eines beliebigen Typs enthält, einschließlich sich selbst, und neue Arrays dynamisch erstellen, indem ich eine Nachricht an alle Array-Elemente übergebe und die Rückgabewerte sammle (ähnlich wie bei Map in Lisp). Ein weiterer Grund war die Implementierung der Schlüsselwertbeobachtung, bei der ich die Benutzeroberfläche so einrichten konnte, dass sie sofort auf Änderungen in den Mitgliedern der Backend-Klassen reagiert, anstatt die Daten ständig abzufragen oder die Anzeige unnötig neu zu zeichnen.

Vielleicht interessanter für Sie ist die Tatsache, dass Sie auch alle für eine Klasse definierten Methoden und Mitglieder und nicht weniger in Zeichenfolgenform sichern können.

Nachteile des Systems, die Sie möglicherweise davon abhalten, sich Sorgen zu machen: Das Hinzufügen aller Nachrichten und Schlüsselwerte ist äußerst mühsam. es ist langsamer als ohne Reflexion; Sie werden wachsen, um zu sehen boost::static_pointer_castund zu hassenboost::dynamic_pointer_cast Ihre gesamte Codebasis mit einer gewalttätigen Leidenschaft . Die Einschränkungen des stark typisierten Systems sind immer noch vorhanden. Sie verstecken sie nur ein wenig, sodass dies nicht so offensichtlich ist. Tippfehler in Ihren Saiten sind auch keine lustige oder leicht zu entdeckende Überraschung.

Um so etwas zu implementieren: Verwenden Sie einfach gemeinsame und schwache Zeiger auf eine gemeinsame Basis (meine wurde sehr einfallsreich als "Objekt" bezeichnet) und leiten Sie sie für alle Typen ab, die Sie verwenden möchten. Ich würde empfehlen, Boost.Function zu installieren, anstatt es so zu machen, wie ich es getan habe, was mit etwas benutzerdefiniertem Mist und einer Menge hässlicher Makros geschehen war, um die Funktionszeigeraufrufe zu verpacken. Da alles zugeordnet ist, müssen beim Überprüfen von Objekten nur alle Schlüssel durchlaufen werden. Da meine Klassen im Wesentlichen so nah wie möglich an einer direkten Abzocke von Cocoa mit nur C ++ waren, würde ich vorschlagen, die Cocoa-Dokumentation als Blaupause zu verwenden, wenn Sie so etwas wollen.

Michel
quelle
Hey, @Michael; Haben Sie noch den Quellcode dafür oder haben Sie ihn losgeworden? Ich würde es mir gerne ansehen, wenn es Ihnen nichts ausmacht.
RandomDSdevel
Hoppla, dein Name ist falsch geschrieben! Kein Wunder, dass ich nie eine Antwort bekommen habe ...
RandomDSdevel
10

Es gibt eine weitere neue Bibliothek für die Reflexion in C ++, RTTR (Run Time Type Reflection, siehe auch github ).

Die Schnittstelle ähnelt der Reflexion in C # und funktioniert ohne RTTI.

Zack
quelle
8

Die zwei reflexionsartigen Lösungen, die ich aus meiner C ++ - Zeit kenne, sind:

1) Verwenden Sie RTTI, das Ihnen einen Bootstrap zum Erstellen Ihres reflexionsähnlichen Verhaltens bietet, wenn Sie alle Ihre Klassen von einer 'Objekt'-Basisklasse ableiten können. Diese Klasse kann einige Methoden wie GetMethod, GetBaseClass usw. bereitstellen. Für die Funktionsweise dieser Methoden müssen Sie manuell einige Makros hinzufügen, um Ihre Typen zu dekorieren. Hinter den Kulissen werden Metadaten im Typ erstellt, um Antworten auf GetMethods usw. bereitzustellen.

2) Wenn Sie Zugriff auf die Compilerobjekte haben, können Sie auch das DIA SDK verwenden . Wenn ich mich richtig erinnere, können Sie damit pdbs öffnen, die Metadaten für Ihre C ++ - Typen enthalten sollten. Es könnte ausreichen, um das zu tun, was Sie brauchen. Diese Seite zeigt, wie Sie beispielsweise alle Basistypen einer Klasse abrufen können.

Beide Lösungen sind allerdings etwas hässlich! Es gibt nichts Schöneres als ein bisschen C ++, um den Luxus von C # zu schätzen.

Viel Glück.

user4385
quelle
Das ist schlau und ein riesiger Hack, mit dem DIA SDK-Ding, das Sie dort vorgeschlagen haben.
Sqeaky
7

BEARBEITEN: Der defekte Link wurde am 7. Februar 2017 aktualisiert.

Ich denke, niemand hat dies erwähnt:

Am CERN verwenden sie ein Vollreflexionssystem für C ++:

CERN Reflex . Es scheint sehr gut zu funktionieren.

Germán Diago
quelle
@ j4nbur53 Der Link ist unterbrochen, weil es scheint, dass sie einen Meilenstein erreicht haben: root.cern.ch
Germán Diago
Könnte es sein, dass Sie diesen Link root.cern.ch/root/doc/ROOTUsersGuideHTML/ch07.html Kapitel Reflex meinen ?
Mostowski Zusammenbruch
Versuchen Sie dies root.cern.ch/how/how-use-reflex . Reflex arbeitet als Generator, der Ihre Header-Dateien analysiert und C ++ - Introspection-Code / Bibliothek generiert, mit dem Sie eine einfache API verknüpfen und verwenden können.
Adam Ryczkowski
6

Diese Frage ist jetzt etwas alt (ich weiß nicht, warum ich heute immer wieder alte Fragen stelle), aber ich habe über BOOST_FUSION_ADAPT_STRUCT nachgedacht, das die Reflexion zur Kompilierungszeit einführt.

Es liegt natürlich an Ihnen, dies der Laufzeitreflexion zuzuordnen, und es wird nicht allzu einfach sein, aber es ist in dieser Richtung möglich, während es nicht umgekehrt wäre :)

Ich denke wirklich, dass ein Makro zum Einkapseln des BOOST_FUSION_ADAPT_STRUCTeinen die notwendigen Methoden generieren könnte, um das Laufzeitverhalten zu erhalten.

Matthieu M.
quelle
2
von minghua (der den Beitrag ursprünglich bearbeitet hat): Ich habe mich in diese BOOST_FUSION_ADAPT_STRUCT-Lösung vertieft und schließlich ein Beispiel gefunden. Siehe diese neuere SO-Frage - C ++ iteriert mit Boost Fusion adapt_struct in ein verschachteltes Strukturfeld .
Matthieu M.
Großartig, Matthieu! Ich habe gerade festgestellt, dass ich Ihre Hinweise im Laufe des letzten Jahres hier und da gesehen habe. Ich habe bis jetzt nicht bemerkt, dass sie verwandt sind. Die waren sehr inspirierend.
Minghua
6

Ich denke, Sie könnten den Artikel "Verwenden von Vorlagen zur Reflexion in C ++" von Dominic Filion interessant finden. Es befindet sich in Abschnitt 1.4 von Game Programming Gems 5 . Leider habe ich mein Exemplar nicht dabei, aber suchen Sie danach, weil es meiner Meinung nach erklärt, wonach Sie fragen.

Luis
quelle
4

Ponder ist eine C ++ - Reflexionsbibliothek zur Beantwortung dieser Frage. Ich überlegte mir die Optionen und beschloss, meine eigenen zu machen, da ich keine finden konnte, die alle meine Kriterien erfüllte.

Obwohl es gute Antworten auf diese Frage gibt, möchte ich keine Tonnen von Makros verwenden oder mich auf Boost verlassen. Boost ist eine großartige Bibliothek, aber es gibt viele kleine maßgeschneiderte C ++ 0x-Projekte, die einfacher sind und schnellere Kompilierungszeiten haben. Es hat auch Vorteile, eine Klasse extern dekorieren zu können, z. B. das Umschließen einer C ++ - Bibliothek, die (noch?) C ++ 11 nicht unterstützt. Es ist eine Verzweigung von CAMP unter Verwendung von C ++ 11, die Boost nicht mehr benötigt .

Nick
quelle
4

Bei der Reflexion geht es im Wesentlichen darum, was der Compiler als Footprints im Code hinterlassen hat, den der Laufzeitcode abfragen kann. C ++ ist dafür bekannt, dass Sie nicht für das bezahlen, was Sie nicht verwenden. weil die meisten Menschen nicht verwenden / wollen Reflexion, die C ++ Compiler , die Kosten durch nicht Aufzeichnung vermeidet alles .

C ++ bietet also keine Reflexion und es ist nicht einfach, es selbst als allgemeine Regel zu "simulieren", wie andere Antworten festgestellt haben.

Wenn Sie unter "Andere Techniken" keine Sprache mit Reflexion haben, erhalten Sie ein Tool, mit dem Sie die gewünschten Informationen zur Kompilierungszeit extrahieren können.

Unser DMS Software Reengineering Toolkit ist eine verallgemeinerte Compilertechnologie, die durch explizite Sprachdefinitionen parametrisiert wird. Es hat Sprachdefinitionen für C, C ++, Java, COBOL, PHP, ...

Für C-, C ++ -, Java- und COBOL-Versionen bietet es vollständigen Zugriff auf Analysebäume und Symboltabelleninformationen. Diese Symboltabelleninformationen enthalten die Art von Daten, die Sie wahrscheinlich von "Reflexion" erwarten. Wenn Sie bestimmte Felder oder Methoden auflisten und etwas damit tun möchten, können Sie mit DMS den Code auf beliebige Weise entsprechend den Angaben in den Symboltabellen transformieren.

Ira Baxter
quelle
3

Eine weitere Bibliothek finden Sie hier: http://www.garret.ru/cppreflection/docs/reflect.html Es werden zwei Möglichkeiten unterstützt: Typinformationen aus Debug-Informationen abrufen und Programmierer diese Informationen bereitstellen lassen.

Ich habe mich auch für Reflexion für mein Projekt interessiert und diese Bibliothek gefunden. Ich habe sie noch nicht ausprobiert, aber andere Tools von diesem Typen ausprobiert und ich mag, wie sie funktionieren :-)

alariq
quelle
3

Schauen Sie sich Classdesc http://classdesc.sf.net an . Es bietet Reflexion in Form von Klassendeskriptoren, funktioniert mit jedem Standard-C ++ - Compiler (ja, es ist bekannt, dass es sowohl mit Visual Studio als auch mit GCC funktioniert) und erfordert keine Quellcode-Annotation (obwohl einige Pragmas existieren, um schwierige Situationen zu bewältigen ). Es befindet sich seit mehr als einem Jahrzehnt in der Entwicklung und wird in einer Reihe von Projekten im industriellen Maßstab eingesetzt.

Russell Standish
quelle
1
Willkommen bei Stack Overflow. Obwohl diese Antwort zum Thema gehört, ist es wichtig darauf hinzuweisen, dass Sie der Autor dieser Software sind, um zu verdeutlichen, dass es sich nicht um eine unvoreingenommene Empfehlung handelt :-)
Matthew Strawbridge
2

Als ich in C ++ nachdenken wollte, las ich diesen Artikel und verbesserte das, was ich dort sah. Sorry, keine Dose hat. Ich besitze das Ergebnis nicht ... aber Sie können sicher bekommen, was ich hatte und von dort aus gehen.

Ich recherchiere derzeit nach Methoden, um die Definition von reflektierbaren Typen zu vereinfachen, wenn ich Lust dazu habe. Ich bin eigentlich ziemlich weit gekommen, aber ich habe noch einen weiten Weg vor mir. Die Änderungen in C ++ 0x sind in diesem Bereich sehr wahrscheinlich eine große Hilfe.

Edward Strange
quelle
2

Es sieht so aus, als ob C ++ diese Funktion immer noch nicht hat. Und C ++ 11 hat auch die Reflexion verschoben ((

Suchen Sie nach Makros oder erstellen Sie eigene. Qt kann auch bei der Reflexion helfen (wenn es verwendet werden kann).

Bohdan
quelle
2

Auch wenn Reflection in C ++ nicht sofort unterstützt wird, ist die Implementierung nicht allzu schwierig. Ich bin auf diesen großartigen Artikel gestoßen: http://replicaisland.blogspot.co.il/2010/11/building-reflective-object-system-in-c.html

Der Artikel erklärt ausführlich, wie Sie ein ziemlich einfaches und rudimentäres Reflexionssystem implementieren können. Zugegeben, es ist nicht die gesundeste Lösung, und es gibt noch Ecken und Kanten, die aussortiert werden müssen, aber für meine Bedürfnisse war es ausreichend.

Fazit: Reflexion kann sich bei richtiger Ausführung auszahlen und ist in c ++ durchaus machbar.

Naore Azenkut
quelle
2

Ich möchte für die Existenz des automatischen Introspektions- / Reflexions-Toolkits "IDK" werben. Es verwendet einen Meta-Compiler wie den von Qt und fügt Metainformationen direkt in Objektdateien ein. Es soll einfach zu bedienen sein. Keine externen Abhängigkeiten. Sie können sogar std :: string automatisch widerspiegeln und dann in Skripten verwenden. Bitte schauen Sie sich IDK an

Eugene G.
quelle
2

Wenn Sie nach einer relativ einfachen C ++ - Reflexion suchen - ich habe Makros / Definitionen aus verschiedenen Quellen gesammelt und sie kommentiert, wie sie funktionieren. Sie können Header-Dateien hier herunterladen:

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

Satz von Definitionen plus Funktionalität darüber hinaus:

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

Die Beispielanwendung befindet sich auch im Git-Repository hier: https://github.com/tapika/TestCppReflect/

Ich werde es hier teilweise mit Erklärung kopieren:

#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 );

}

REFLECTABLEdefine verwendet Klassennamen + Feldnamen mit offsetof-, um zu identifizieren, an welcher Stelle im Speicher sich ein bestimmtes Feld befindet. Ich habe versucht, die .NET-Terminologie so weit wie möglich zu übernehmen, aber C ++ und C # unterscheiden sich, sodass sie nicht 1 zu 1 sind. Das gesamte C ++ - Reflexionsmodell befindet sich in TypeInfound FieldInfoKlassen.

Ich habe den pugi xml Parser verwendet, um Demo-Code in xml abzurufen und aus xml wiederherzustellen.

Die vom Demo-Code erzeugte Ausgabe sieht also folgendermaßen aus:

<?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>

Es ist auch möglich, die Unterstützung von Klassen / Strukturen von Drittanbietern über die TypeTraits-Klasse und die teilweise Vorlagenspezifikation zu aktivieren, um Ihre eigene TypeTraitsT-Klasse zu definieren, ähnlich wie bei CString oder int - siehe Beispielcode in

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

Diese Lösung gilt für Windows / Visual Studio. Es ist möglich, es auf andere Betriebssysteme / Compiler zu portieren, hat dies aber noch nicht getan. (Fragen Sie mich, ob Ihnen die Lösung wirklich gefällt, ich kann Ihnen möglicherweise helfen.)

Diese Lösung ist für die einmalige Serialisierung einer Klasse mit mehreren Unterklassen anwendbar.

Wenn Sie jedoch nach einem Mechanismus suchen, um Klassenteile zu serialisieren oder sogar zu steuern, welche Funktionsreflexionsaufrufe erzeugt werden, können Sie sich folgende Lösung ansehen:

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

Weitere Informationen finden Sie im YouTube-Video:

C ++ Runtime Type Reflection https://youtu.be/TN8tJijkeFE

Ich versuche etwas tiefer zu erklären, wie die C ++ - Reflexion funktioniert.

Der Beispielcode sieht beispielsweise so aus:

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;

Aber jeder Schritt hier führt tatsächlich zum Funktionsaufruf Verwenden von C ++ - Eigenschaften mit __declspec(property(get =, put ... ) .

Das Unternehmen erhält vollständige Informationen zu C ++ - Datentypen, C ++ - Eigenschaftsnamen und Klasseninstanzzeigern in Form eines Pfads. Basierend auf diesen Informationen können Sie XML, JSON generieren oder diese sogar über das Internet serialisieren.

Beispiele für solche virtuellen Rückruffunktionen finden Sie hier:

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

Siehe Funktionen ReflectCopyund virtuelle Funktion::OnAfterSetProperty .

Da das Thema jedoch sehr fortgeschritten ist, empfehle ich, zuerst das Video durchzusehen.

Wenn Sie Verbesserungsvorschläge haben, können Sie mich gerne kontaktieren.

TarmoPikaro
quelle
1

Die Reflexion in C ++ ist sehr nützlich, wenn Sie dort für jedes Mitglied eine Methode ausführen müssen (z. B. Serialisierung, Hashing, Vergleichen). Ich kam mit einer generischen Lösung mit sehr einfacher Syntax:

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

Wobei ENUMERATE_MEMBERS ein Makro ist, das später beschrieben wird (UPDATE):

Angenommen, wir haben die Serialisierungsfunktion für int und std :: string wie folgt definiert:

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());
}

Und wir haben eine generische Funktion in der Nähe des "geheimen Makros";)

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

Jetzt kannst du schreiben

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)

Wenn Sie also das Makro ENUMERATE_MEMBERS in der Strukturdefinition haben, können Sie Serialisierung, Vergleichen, Hashing und andere Dinge erstellen, ohne den Originaltyp zu berühren. Die einzige Voraussetzung ist die Implementierung der Methode "EnumerateWith" für jeden Typ, der nicht aufzählbar ist, pro Enumerator (wie BinaryWriter). . Normalerweise müssen Sie 10-20 "einfache" Typen implementieren, um jeden Typ in Ihrem Projekt zu unterstützen.

Dieses Makro sollte zur Laufzeit keinen Overhead für die Erstellung / Zerstörung von Strukturen haben, und der Code von T.EnumerateWith () sollte bei Bedarf generiert werden. Dies kann erreicht werden, indem die Template-Inline-Funktion aktiviert wird, also der einzige Overhead in Die ganze Geschichte besteht darin, jeder Struktur ENUMERATE_MEMBERS (m1, m2, m3 ...) hinzuzufügen, während die Implementierung einer bestimmten Methode pro Elementtyp in jeder Lösung ein Muss ist, daher gehe ich nicht davon aus, dass dies Overhead ist.

UPDATE: Es gibt eine sehr einfache Implementierung des Makros ENUMERATE_MEMBERS (es könnte jedoch ein wenig erweitert werden, um die Vererbung von aufzählbaren Strukturen zu unterstützen).

#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);
}

Und für diese 15 Codezeilen benötigen Sie keine Drittanbieter-Bibliothek;)

Jenkins
quelle
1

Mit BOOST_HANA_DEFINE_STRUCT aus der Boost :: Hana-Bibliothek können Sie coole statische Reflexionsfunktionen für Strukturen erzielen .
Hana ist sehr vielseitig, nicht nur für den Anwendungsfall, den Sie sich vorgestellt haben, sondern auch für viele Metaprogrammierungen von Vorlagen.

nnolte
quelle
1

Die Direktzugriffsreflexion Bibliothek ermöglicht eine relativ einfache und intuitive Reflexion. Alle Feld- / Typinformationen sind entweder in Arrays verfügbar oder fühlen sich wie Array-Zugriff an. Es wurde für C ++ 17 geschrieben und funktioniert mit Visual Studios, g ++ und Clang. Die Bibliothek ist nur eine Kopfzeile, dh Sie müssen nur "Reflect.h" in Ihr Projekt kopieren, um sie zu verwenden.

Reflektierte Strukturen oder Klassen benötigen das REFLECT-Makro, in dem Sie den Namen der Klasse angeben, die Sie reflektieren, sowie die Namen der Felder.

class FuelTank {
    public:
        float capacity;
        float currentLevel;
        float tickMarks[2];

    REFLECT(() FuelTank, () capacity, () currentLevel, () tickMarks)
};

Das ist alles, es ist kein zusätzlicher Code erforderlich, um die Reflexion einzurichten. Optional können Sie Oberklassen (in der Klammer des ersten Arguments) und Feldanmerkungen (in der Klammer vor dem Feld, das Sie mit Anmerkungen versehen möchten) angeben, um Oberklassen durchlaufen zu können oder einem Feld zusätzliche Informationen zur Kompilierungszeit hinzuzufügen (z. B. Json: :Ignorieren).

Das Durchlaufen von Feldern kann so einfach sein wie ...

for ( size_t i=0; i<FuelTank::Class::TotalFields; i++ )
    std::cout << FuelTank::Class::Fields[i].name << std::endl;

Sie können eine Objektinstanz durchlaufen, um auf Feldwerte (die Sie lesen oder ändern können) und Feldtypinformationen zuzugreifen ...

FuelTank::Class::ForEachField(fuelTank, [&](auto & field, auto & value) {
    using Type = typename std::remove_reference<decltype(value)>::type;
    std::cout << TypeToStr<Type>() << " " << field.name << ": " << value << std::endl;
});

Eine JSON-Bibliothek basiert auf RandomAccessReflection, die automatisch geeignete JSON-Ausgabedarstellungen zum Lesen oder Schreiben identifiziert und alle reflektierten Felder sowie Arrays und STL-Container rekursiv durchlaufen kann.

struct MyOtherObject { int myOtherInt; REFLECT(() MyOtherObject, () myOtherInt) };
struct MyObject
{
    int myInt;
    std::string myString;
    MyOtherObject myOtherObject;
    std::vector<int> myIntCollection;

    REFLECT(() MyObject, () myInt, () myString, (Reflected) myOtherObject, () myIntCollection)
};

int main()
{
    MyObject myObject = {};
    std::cout << "Enter MyObject:" << std::endl;
    std::cin >> Json::in(myObject);
    std::cout << std::endl << std::endl << "You entered:" << std::endl;
    std::cout << Json::pretty(myObject);
}

Das obige könnte so laufen ...

Enter MyObject:
{
  "myInt": 1337, "myString": "stringy", "myIntCollection": [2,4,6],
  "myOtherObject": {
    "myOtherInt": 9001
  }
}


You entered:
{
  "myInt": 1337,
  "myString": "stringy",
  "myOtherObject": {
    "myOtherInt": 9001
  },
  "myIntCollection": [ 2, 4, 6 ]
}

Siehe auch...

jjf28
quelle
0

Wenn Sie einen Zeiger auf eine Funktion wie diese deklarieren:

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

Sie können dieser Funktion wie folgt einen Speicherplatz zuweisen (erfordert libdlund 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);
}

Um ein lokales Symbol mithilfe der Indirektion zu laden, können Sie es dlopenfür die aufrufende Binärdatei ( argv[0]) verwenden.

Die einzige Voraussetzung für diesen (außer dlopen(), libdlund dlfcn.h), um die Argumente und die Art der Funktion zu kennen.

SS Anne
quelle