Frage

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

War es hilfreich?

Lösung 17

Nachdenken ist eine C++-Reflexionsbibliothek als Antwort auf diese Frage.Ich dachte über die Optionen nach und beschloss, meine eigene zu machen, da ich keine finden konnte, die alle meine Anforderungen erfüllte.

Obwohl es tolle Antworten auf diese Frage gibt, möchte ich nicht Unmengen an 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 gibt auch Vorteile, eine Klasse extern dekorieren zu können, etwa eine C++-Bibliothek einzuschließen, die C++11 (noch?) nicht unterstützt.Es ist eine Abzweigung von CAMP, die C++11 verwendet erfordert keinen Boost mehr.

Andere Tipps

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 im Präprozessor zu schreiben, verwenden wir zunächst typisierte Ausdrücke.Ein typisierter Ausdruck ist nur ein Ausdruck, der den Typ in Klammern setzt.Also statt zu schreiben int x du wirst schreiben (int) x.Hier sind einige praktische Makros, die bei eingegebenen 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 a REFLECTABLE Makro zum Generieren der Daten zu jedem Feld (plus dem Feld selbst).Dieses Makro wird wie folgt aufgerufen:

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

Also verwenden Boost.PP Wir durchlaufen 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 Das ist die Anzahl der reflektierbaren Felder in der Klasse.Dann spezialisiert es sich auf die field_data für jedes Feld.Es befreundet auch die reflector Klasse, damit auf die Felder zugegriffen werden kann, auch wenn diese 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));
}

Für den Moment der Wahrheit haben wir nun alles zusammengefügt.So können wir a definieren Person Klasse, 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 Verallgemeinerung print_fields Funktion, 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 von print_fields mit dem Reflektierbaren Person Klasse:

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

Welche Ausgaben:

name=Tom
age=82

Und voilà, wir haben gerade Reflection in C++ in weniger als 100 Codezeilen implementiert.

Es gibt zwei Arten reflection herumschwimmen.

  1. Inspektion durch Iteration über Mitglieder eines Typs, Aufzählung seiner Methoden usw.

    Dies ist mit C++ nicht möglich.
  2. Inspektion durch Prüfung, ob ein Klassentyp (Klasse, Struktur, Union) eine Methode oder einen verschachtelten Typ hat und von einem anderen bestimmten Typ abgeleitet ist.

    So etwas ist mit C++ möglich template-tricks.Verwenden boost::type_traits für viele Dinge (wie die Überprüfung, ob ein Typ ganzzahlig ist).Um zu prüfen, ob eine Mitgliedsfunktion vorhanden ist, verwenden Sie Ist es möglich, eine Vorlage zu schreiben, um die Existenz einer Funktion zu überprüfen? .Um zu überprüfen, ob ein bestimmter verschachtelter Typ vorhanden ist, verwenden Sie plain SFINAE .

Wenn Sie eher nach Möglichkeiten suchen, 1) zu erreichen, wie zum Beispiel herauszufinden, wie viele Methoden eine Klasse hat, oder wie Sie die String-Darstellung einer Klassen-ID erhalten möchten, dann fürchte ich, dass es hierfür keine Standard-C++-Methode gibt.Sie müssen beides verwenden

  • Ein Meta-Compiler wie der Qt Meta Object Compiler, der Ihren Code übersetzt und zusätzliche Metainformationen hinzufügt.
  • Ein Framework bestehend aus Makros, 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 es in C# oder Java der Fall ist, muss ich Ihnen leider sagen, dass es ohne etwas Aufwand keinen Weg gibt.

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

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

RTTI existiert für C++ nicht.

Das ist einfach falsch.Tatsächlich wurde der Begriff „RTTI“ durch den C++-Standard geprägt.Andererseits geht RTTI bei der Umsetzung der Reflexion nicht sehr weit.

Die Informationen sind zwar vorhanden, jedoch nicht in dem von Ihnen benötigten Format und nur, wenn Sie Ihre Kurse exportieren.Das funktioniert unter Windows, andere Plattformen kenne ich nicht.Verwendung der Speicherklassenspezifizierer wie zum Beispiel:

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

Dadurch baut der Compiler die Klassendefinitionsdaten in die DLL/Exe ein.Aber es liegt nicht in einem Format vor, das man ohne weiteres zum Nachdenken nutzen kann.

In meiner Firma haben wir eine Bibliothek erstellt, die diese Metadaten interpretiert und es Ihnen ermöglicht, eine Klasse widerzuspiegeln, ohne zusätzliche Makros usw. einzufügen.in die Klasse selbst.Es ermöglicht den Aufruf von Funktionen wie folgt:

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.Offensichtlich umgehen Sie durch den Aufruf einer Funktion auf diese Weise Dinge wie const-safety usw., sodass diese Aspekte als Laufzeitprüfungen implementiert werden.

Ich bin mir sicher, dass die Syntax verbessert werden könnte und sie bisher nur unter Win32 und Win64 funktioniert.Wir haben festgestellt, dass es sehr nützlich ist, um automatische GUI-Schnittstellen zu Klassen zu haben, Eigenschaften in C++ zu erstellen, zu und von XML zu streamen usw., und es besteht keine Notwendigkeit, von einer bestimmten Basisklasse abzuleiten.Wenn es genug Nachfrage gibt, könnten wir es vielleicht für die Veröffentlichung auf Vordermann bringen.

Sie müssen sich ansehen, was Sie tun möchten und ob RTTI Ihren Anforderungen gerecht wird.Ich habe meine eigene Pseudoreflexion für einige ganz bestimmte Zwecke implementiert.Ich wollte zum Beispiel einmal flexibel konfigurieren können, was eine Simulation ausgeben soll.Es war erforderlich, den auszugebenden Klassen einigen Standardcode 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 das aufruft BuildMap() Methode, um herauszufinden, welche Methoden verfügbar sind.

Dann können Sie in der Konfigurationsdatei so etwas tun:

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

Durch einige Vorlagenmagie, die beteiligt ist boost, wird dies zur Laufzeit (beim Lesen der Konfigurationsdatei) in eine Reihe von Methodenaufrufen übersetzt, ist also ziemlich effizient.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.

Was versuchst du mit der Reflexion zu erreichen?
Sie können den Boost verwenden Typmerkmale Und Art der Bibliotheken als begrenzte Form der Reflexion zur Kompilierzeit.Das heißt, Sie können die grundlegenden Eigenschaften eines an eine Vorlage übergebenen Typs überprüfen und ändern.

Ich würde die Verwendung empfehlen Qt.

Es gibt sowohl eine Open-Source-Lizenz als auch eine kommerzielle Lizenz.

BEARBEITEN: LAGER wird nicht mehr gepflegt;Es stehen zwei Gabeln zur Verfügung:

  • Einer wird auch genannt LAGER ebenfalls und basiert auf der gleichen API.
  • Nachdenken ist eine teilweise Neufassung und sollte bevorzugt werden, da kein Boost erforderlich ist;es verwendet C++11.

LAGER ist eine MIT-lizenzierte Bibliothek (ehemals LGPL), die der C++-Sprache Reflexion hinzufügt.Es ist kein spezieller Vorverarbeitungsschritt bei der Kompilierung erforderlich, die Bindung muss jedoch manuell vorgenommen werden.

Die aktuelle Tegesoft-Bibliothek verwendet Boost, aber es gibt auch Boost eine Gabel mit C++11 das erfordert keinen Boost mehr.

Ich habe einmal so etwas wie das gemacht, was Sie suchen, und obwohl es möglich ist, ein gewisses Maß an Reflexion und Zugriff auf Funktionen auf höherer Ebene zu erhalten, lohnt sich der Wartungsaufwand möglicherweise nicht.Mein System wurde verwendet, um die UI-Klassen durch Delegation, ähnlich dem Objective-C-Konzept der Nachrichtenübermittlung und -weiterleitung, vollständig von der Geschäftslogik zu trennen.Der Weg dazu besteht darin, eine Basisklasse zu erstellen, die in der Lage ist, Symbole (ich habe einen String-Pool verwendet, aber Sie könnten es mit Aufzählungen machen, wenn Sie Geschwindigkeit und Fehlerbehandlung während der Kompilierung gegenüber völliger Flexibilität bevorzugen) Funktionszeigern zuzuordnen (eigentlich nicht). reine Funktionszeiger, aber etwas Ähnliches wie Boost mit Boost.Function – worauf ich damals keinen Zugriff hatte).Sie können dasselbe für Ihre Mitgliedsvariablen tun, solange Sie über eine gemeinsame Basisklasse verfügen, die jeden Wert darstellen kann.Das gesamte System war eine unverhohlene Abzocke der Schlüsselwertcodierung und -delegierung, mit ein paar Nebenwirkungen, die vielleicht den schieren Zeitaufwand wert waren, der nötig war, um jede Klasse, die das System nutzte, dazu zu bringen, alle ihre Methoden und Mitglieder mit legalen Aufrufen abzugleichen :1) Jede Klasse könnte jede Methode einer anderen 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 ließen sich leicht threadsicher machen, da das Ändern oder Zugreifen auf ihre Werte immer über zwei Methoden in der Basisklasse aller Objekte erfolgte.

Es ergab sich auch die Möglichkeit, einige wirklich seltsame Dinge zu tun, die sonst in C++ nicht einfach wären.Ich könnte zum Beispiel ein Array-Objekt erstellen, das beliebige Elemente jeglichen 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, wodurch ich die Benutzeroberfläche so einrichten konnte, dass sie sofort auf Änderungen in den Mitgliedern von Backend-Klassen reagiert, anstatt ständig die Daten abzufragen oder die Anzeige unnötig neu zu zeichnen.

Interessanter für Sie ist vielleicht die Tatsache, dass Sie auch alle für eine Klasse definierten Methoden und Mitglieder sichern können, und zwar in String-Form.

Nachteile des Systems, die Sie möglicherweise davon abhalten, sich die Mühe zu machen:Das Hinzufügen aller Nachrichten und Schlüsselwerte ist äußerst mühsam.es ist langsamer als ohne jegliche Reflexion;Du wirst es zunehmend hassen, etwas zu sehen boost::static_pointer_cast Und boost::dynamic_pointer_cast überall in Ihrer Codebasis mit heftiger Leidenschaft;Die Einschränkungen des stark typisierten Systems sind immer noch vorhanden. Sie verstecken sie nur ein wenig, damit sie nicht so offensichtlich sind.Auch Tippfehler in Ihren Zeichenfolgen sind keine lustige oder leicht zu entdeckende Überraschung.

Wie man so etwas umsetzt:Verwenden Sie einfach gemeinsame und schwache Zeiger auf eine gemeinsame Basis (meine wurde sehr einfallsreich „Objekt“ genannt) 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 gemacht habe, nämlich mit etwas benutzerdefiniertem Mist und einer Menge hässlicher Makros, um die Funktionszeigeraufrufe zu umschließen.Da alles zugeordnet ist, ist die Untersuchung von Objekten lediglich eine Frage der Iteration durch alle Schlüssel.Da meine Kurse im Wesentlichen so nah wie möglich an einer direkten Kopie von Cocoa waren, indem ich nur C++ verwendete, würde ich, wenn Sie so etwas wollen, vorschlagen, die Cocoa-Dokumentation als Blaupause zu verwenden.

Die beiden reflexionsähnlichen Lösungen, die ich aus meiner C++-Zeit kenne, sind:

1) Verwenden Sie RTTI, das Ihnen einen Bootstrap für den Aufbau Ihres reflexionsähnlichen Verhaltens bietet, wenn Sie in der Lage sind, alle Ihre Klassen von einer „Objekt“-Basisklasse abzuleiten.Diese Klasse könnte einige Methoden wie GetMethod, GetBaseClass usw. bereitstellen.Was die Funktionsweise dieser Methoden betrifft, müssen Sie manuell einige Makros hinzufügen, um Ihre Typen zu dekorieren, die hinter den Kulissen Metadaten im Typ erstellen, um Antworten für GetMethods usw. bereitzustellen.

2) Eine weitere Option, wenn Sie Zugriff auf die Compiler-Objekte haben, ist die Verwendung von DIA SDK.Wenn ich mich richtig erinnere, können Sie damit PDFs öffnen, die Metadaten für Ihre C++-Typen enthalten sollten.Es könnte ausreichen, das zu tun, was Sie brauchen. Diese Seite zeigt, wie Sie beispielsweise alle Basistypen einer Klasse erhalten können.

Beide Lösungen sind allerdings etwas hässlich!Es gibt nichts Besseres als ein bisschen C++, damit Sie den Luxus von C# schätzen lernen.

Viel Glück.

Es gibt eine weitere neue Bibliothek zur Reflexion in C++, genannt RTTR (Laufzeittypreflexion, siehe auch Github).

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

BEARBEITEN:Defekter Link wurde am 7. Februar 2017 aktualisiert.

Ich glaube, das hat niemand erwähnt:

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

CERN-Reflex.Es scheint sehr gut zu funktionieren.

Reflection wird von C++ nicht standardmäßig unterstützt.Das ist traurig, weil es defensive Tests zu einer Qual macht.

Es gibt verschiedene Ansätze zur Reflexion:

  1. Verwenden Sie die Debug-Informationen (nicht portierbar).
  2. Streuen 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 bespricht 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

Mittlerweile gibt es eine Arbeitsgruppe zur C++-Reflexion.Sehen Sie sich die Neuigkeiten zu C++14 am CERN an:

Bearbeiten 13.08.17:Seit dem ursprünglichen Beitrag gab es eine Reihe potenzieller Fortschritte bei der Reflexion.Im Folgenden finden Sie weitere Einzelheiten und eine Diskussion zu den verschiedenen Techniken und ihrem Status:

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

Ein standardisierter Reflexionsansatz in C++ sieht in naher Zukunft jedoch nicht vielversprechend aus, es sei denn, die Community zeigt deutlich mehr Interesse an der Unterstützung der Reflexion in C++.

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

Bearbeiten 13.12.2017

Reflection scheint sich in Richtung C++ 20 oder, was wahrscheinlicher ist, einem TSR zu bewegen.Die Bewegung ist jedoch langsam.

Bearbeiten 15.09.2018

Ein TS-Entwurf wurde den nationalen Gremien zur Abstimmung zugesandt.

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

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

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

Ich denke wirklich, dass ein Makro das kapseln soll BOOST_FUSION_ADAPT_STRUCT man könnte die notwendigen Methoden generieren, um das Laufzeitverhalten zu erhalten.

Ich denke, der Artikel „Using Templates for Reflection in C++“ von Dominic Filion könnte für Sie interessant sein.Es steht in Abschnitt 1.4 von Juwelen der Spielprogrammierung 5.Leider habe ich mein Exemplar nicht dabei, aber suchen Sie danach, weil ich denke, dass es erklärt, wonach Sie fragen.

Bei der Reflexion geht es im Wesentlichen darum, was der Compiler beschlossen hat, als Spuren im Code zu hinterlassen, die der Laufzeitcode abfragen kann.C++ ist dafür bekannt, dass es nicht für das bezahlt, was man nicht nutzt;Da die meisten Leute keine Reflektion verwenden/wollen, vermeidet der C++-Compiler die Kosten, indem er nicht aufzeichnet irgendetwas.

C++ bietet also keine Reflexion, und es ist in der Regel nicht einfach, sie selbst zu „simulieren“, wie in anderen Antworten bereits erwähnt wurde.

Wenn Sie unter „andere Techniken“ keine Sprache mit Reflexion haben, Holen Sie sich ein Tool, das die gewünschten Informationen zur Kompilierungszeit extrahieren kann.

Unser DMS-Software-Reengineering-Toolkit ist eine verallgemeinerte Compiler-Technologie, die durch explizite Sprachdefinitionen parametrisiert wird.Es verfügt über 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 umfassen die Art von Daten, die Sie wahrscheinlich von der „Reflexion“ erwarten.Wenn Ihr Ziel darin besteht, eine Reihe von Feldern oder Methoden aufzuzählen und Tun Etwas mit ihnen, DMS kann verwendet werden, um den Code entsprechend dem, was Sie in den Symboltabellen finden, auf beliebige Weise umzuwandeln.

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

Ich interessiere mich auch für Reflexion für mein Projekt und habe diese Bibliothek gefunden. Ich habe sie noch nicht ausprobiert, aber ich habe andere Tools von diesem Typ ausprobiert und mir gefällt, wie sie funktionieren :-)

Schauen Sie sich Classdesc an http://classdesc.sf.net.Es bietet Reflexion in Form von Klassen-"Deskriptoren", funktioniert mit jedem Standard-C++-Compiler (ja, es funktioniert bekanntermaßen sowohl mit Visual Studio als auch mit GCC) 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.

Als ich Reflexion in C++ wollte, las ich Dieser Artikel und habe das, was ich dort gesehen habe, verbessert.Tut mir leid, keine Dose hat.Ich bin nicht der Eigentümer des Ergebnisses ... aber Sie können sicherlich das nehmen, was ich hatte, und von dort aus fortfahren.

Ich recherchiere derzeit, wenn ich Lust dazu habe, Methoden, um inherit_linearly zu verwenden, um die Definition von reflektierbaren Typen viel einfacher zu machen.Eigentlich bin ich ziemlich weit gekommen, aber ich habe noch einen weiten Weg vor mir.Die Änderungen in C++0x dürften in diesem Bereich sehr hilfreich sein.

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

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

Versuchen Sie, sich dieses Projekt anzusehen http://www.garret.ru/cppreflection/docs/reflect.htmlwird Reflexionen zu C++ hinzugefügt.Den Klassen wurden Metadaten hinzugefügt, die Sie dann verwenden können.

Obwohl Reflektion in C++ nicht standardmäßig unterstützt wird, ist die Implementierung nicht allzu schwierig.Ich bin auf diesen tollen 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 recht einfaches und rudimentäres Reflexionssystem implementieren können.Zugegeben, es ist nicht die gesündeste Lösung, und es gibt noch einige Ecken und Kanten, die es zu klären gilt, aber für meine Bedürfnisse war es ausreichend.

Fazit: Reflexion kann sich auszahlen, wenn sie richtig durchgeführt wird, und sie ist in C++ durchaus machbar.

Ich möchte auf die Existenz des automatischen Selbstbeobachtungs-/Reflexions-Toolkits „IDK“ hinweisen.Es verwendet einen Meta-Compiler wie den von Qt und fügt Metainformationen direkt in Objektdateien ein.Es wird behauptet, dass es einfach zu bedienen sei.Keine externen Abhängigkeiten.Es ermöglicht Ihnen sogar, std::string automatisch wiederzugeben und es dann in Skripten zu verwenden.Bitte sieh dir ... an IDK

Reflection in C++ ist sehr nützlich, in Fällen, in denen Sie für jedes Mitglied eine Methode ausführen müssen (zum Beispiel:Serialisierung, Hashing, Vergleich).Ich habe eine generische Lösung mit sehr einfacher Syntax gefunden:

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 können Sie 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, Vergleiche, Hashing und andere Dinge erstellen, ohne den ursprünglichen Typ zu berühren. Die einzige Anforderung besteht darin, die Methode „EnumerateWith“ für jeden Typ, der nicht aufzählbar ist, pro Enumerator (wie BinaryWriter) zu implementieren. .Normalerweise müssen Sie 10–20 „einfache“ Typen implementieren, um jeden Typ in Ihrem Projekt zu unterstützen.

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

AKTUALISIEREN:Es gibt eine sehr einfache Implementierung des Makros ENUMERATE_MEMBERS (es könnte jedoch etwas erweitert werden, um die Vererbung von einer aufzählbaren Struktur 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 Bibliothek eines Drittanbieters ;)

Mit C++20 haben Sie es Erweiterungsanweisungen, mit dem Sie Aggregattypen iterieren können:

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

Wenn Sie nach einer relativ einfachen C++-Reflexion suchen: Ich habe aus verschiedenen Quellen Makros/Definitionen gesammelt und ihre Funktionsweise auskommentiert.Hier können Sie Header -Dateien herunterladen:

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

Satz von Definitionen und darüber hinausgehender Funktionalität:

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

}

REFLECTABLE define verwendet Klassennamen + Feldnamen mit offsetof - um zu identifizieren, an welcher Stelle im Speicher sich ein bestimmtes Feld befindet.Ich habe versucht, so weit wie möglich die .NET-Terminologie zu übernehmen, aber C++ und C# sind unterschiedlich, also ist es nicht 1 zu 1.Das gesamte C++-Reflexionsmodell befindet sich in TypeInfo Und FieldInfo Klassen.

Ich habe den Pugi-XML-Parser verwendet, um Democode in XML abzurufen und ihn aus XML wiederherzustellen.

Die vom Democode erzeugte Ausgabe sieht also so 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, jede Klassen-/Strukturunterstützung von Drittanbietern über die TypeTraits-Klasse und teilweise Vorlagenspezifikation zu aktivieren – um Ihre eigene TypeTraitsT-Klasse zu definieren, ähnlich wie CString oder int – siehe Beispielcode in

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

Diese Lösung ist für Windows/Visual Studio anwendbar.Es ist möglich, es auf andere Betriebssysteme/Compiler zu portieren, aber das habe ich noch nicht gemacht.(Fragen Sie mich, ob Ihnen die Lösung wirklich gefällt, vielleicht kann ich Ihnen weiterhelfen)

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

Wenn Sie jedoch nach einem Mechanismus suchen, um Klassenteile zu serialisieren oder sogar zu steuern, welche Funktionalität Reflexionsaufrufe erzeugen, können Sie sich die folgende Lösung ansehen:

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

Genauere Informationen finden Sie im YouTube-Video:

C++-Laufzeittypreflexionhttps://youtu.be/TN8tJijkeFE

Ich versuche etwas ausführlicher zu erklären, wie die C++-Reflexion funktionieren wird.

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;

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

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

Beispiele für solche virtuellen Callback-Funktionen finden Sie hier:

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

Siehe Funktionen ReflectCopy, und virtuelle Funktion ::OnAfterSetProperty.

Da das Thema aber sehr fortgeschritten ist, empfehle ich, zuerst das Video anzuschauen.

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

Das Root Reflex-Projekt unterstützt dies.

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

Wenn Sie einen Zeiger auf eine Funktion wie folgt deklarieren:

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

Sie können dieser Funktion wie folgt einen Platz im Speicher zuweisen (erfordert libdl Und 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 verwenden dlopen auf der aufrufenden Binärdatei (argv[0]).

Die einzige Voraussetzung dafür (außer dlopen(), libdl, Und dlfcn.h) bedeutet, die Argumente und den Typ der Funktion zu kennen.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top