325 Stimmen

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

Ich möchte in der Lage sein, eine C++-Klasse auf ihren Namen, ihren Inhalt (d.h. Mitglieder und ihre Typen) usw. zu untersuchen. Ich spreche hier von nativem C++, nicht von verwaltetem C++, das Reflection hat. Ich weiß, dass C++ mit RTTI einige begrenzte Informationen liefert. Welche zusätzlichen Bibliotheken (oder andere Techniken) könnten diese Informationen liefern?

21 Stimmen

Pech gehabt, ohne Makros und andere Vorverarbeitungen geht es nicht, denn die erforderlichen Metadaten gibt es nicht es sei denn, Sie erstellen sie manuell mit Hilfe von Makro-Vorverarbeitungsmagie.

7 Stimmen

Die Informationen, die Sie von RTTI erhalten können, reichen jedoch nicht aus, um die meisten Dinge zu tun, für die Sie eigentlich eine Reflexion benötigen würden. Sie können zum Beispiel nicht über die Mitgliedsfunktionen einer Klasse iterieren.

331voto

Paul Fultz II Punkte 16880

Sie müssen den Präprozessor veranlassen, Reflexionsdaten über die Felder zu erzeugen. Diese Daten können als verschachtelte Klassen gespeichert werden.

Erstens, um es einfacher und sauberer in den Präprozessor zu schreiben, werden wir einen typisierten Ausdruck verwenden. Ein typisierter Ausdruck ist einfach ein Ausdruck, der den Typ in Klammern setzt. Anstatt also zu schreiben int x werden Sie schreiben (int) x . Hier sind einige praktische Makros, die bei der Eingabe von 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 eine REFLECTABLE Makro, um die Daten zu jedem Feld (und das Feld selbst) zu erzeugen. Dieses Makro wird wie folgt aufgerufen:

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

Also mit Verstärkung.PP iterieren wir über jedes Argument und erzeugen 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 fields_n das ist die Anzahl der reflektierbaren Felder in der Klasse. Dann spezialisiert es die field_data für jedes Feld. Sie ist auch mit den reflector Klasse, damit sie 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 die Felder zu durchlaufen, verwenden wir das Besucher-Muster. Wir erstellen einen MPL-Bereich von 0 bis zur Anzahl der Felder und greifen auf die Felddaten bei diesem Index zu. Dann werden die Felddaten an den vom Benutzer bereitgestellten Besucher weitergegeben:

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 kommt der Moment der Wahrheit, in dem wir alles zusammenfügen. So können wir 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 verallgemeinerte 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 des 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 voila, wir haben soeben Reflection in C++ implementiert, in weniger als 100 Zeilen Code.

150 Stimmen

Ich gratuliere Ihnen, dass Sie zeigen, wie man Reflexion umsetzen kann, anstatt zu sagen, dass es nicht möglich ist. Es sind Antworten wie diese, die S.O. zu einer großartigen Ressource machen.

7 Stimmen

Wenn Sie versuchen, dies unter Visual Studio zu kompilieren, erhalten Sie eine Fehlermeldung, da VS die variadische Makroexpansion nicht richtig verarbeitet. Für VS, versuchen Sie hinzufügen: #define DETAIL_TYPEOF_INT2(tuple) DETAIL_TYPEOF_HEAD tuple y #define DETAIL_TYPEOF_INT(...) DETAIL_TYPEOF_INT2((__VA_ARGS__)) und ändert die Definition von TYPEOF(x) in: #define TYPEOF(x) DETAIL_TYPEOF_INT(DETAIL_TYPEOF_PROBE x,)

1 Stimmen

Ich erhalte die Fehlermeldung 'BOOST_PP_IIF_0' benennt keinen Typ. Können Sie bitte helfen.

113voto

Es gibt zwei Arten von reflection herumschwimmen.

  1. Inspektion durch Iteration über die Mitglieder eines Typs, Aufzählung seiner Methoden und so weiter.

    Dies ist mit C++ nicht möglich.

  2. Prüfung, ob ein Klassentyp (Klasse, Struktur, Union) eine Methode oder einen verschachtelten Typ hat, von einem anderen bestimmten Typ abgeleitet ist.

    Diese Art von Dingen ist mit C++ möglich, indem man template-tricks . Verwenden Sie boost::type_traits für viele Dinge (wie die Prüfung, ob ein Typ integral ist). Um das Vorhandensein einer Mitgliedsfunktion zu prüfen, verwenden Sie Ist es möglich, eine Vorlage zu schreiben, um zu prüfen, ob eine Funktion existiert? . Um zu prüfen, ob ein bestimmter verschachtelter Typ existiert, verwenden Sie einfach SFINAE .

Wenn Sie eher nach Möglichkeiten suchen, um 1) zu erreichen, wie z.B. zu sehen, wie viele Methoden eine Klasse hat, oder wie man die String-Repräsentation einer Klassenkennung erhält, dann befürchte ich, dass es keine Standard-C++-Möglichkeit gibt, dies zu tun. Sie müssen entweder

  • 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üssen dem Framework alle Methoden, die Klassennamen, Basisklassen und alles, was es braucht, mitteilen.

C++ ist auf Geschwindigkeit ausgelegt. Wenn Sie eine High-Level-Inspektion wünschen, wie sie C# oder Java haben, dann muss ich Ihnen leider sagen, dass es ohne einige Anstrengungen nicht geht.

144 Stimmen

C++ wurde im Hinblick auf Geschwindigkeit entwickelt, aber die Philosophie lautet nicht "so schnell wie möglich", sondern "du zahlst nicht dafür, wenn du es nicht benutzt". Ich glaube, dass es für eine Sprache möglich ist, Introspektion in einer Weise zu implementieren, die zu dieser Philosophie passt, C++ fehlt es einfach.

8 Stimmen

@Joseph: Wie sollte man das machen? Es würde erfordern, dass all diese Metadaten gespeichert werden. Das bedeutet, dass man dafür bezahlen muss, auch wenn man sie nicht nutzt. (Es sei denn, man könnte einzelne Typen als "unterstützend für Reflexion" kennzeichnen, aber dann sind wir schon fast an dem Punkt, an dem wir genauso gut die bestehenden Makrotricks verwenden könnten.

26 Stimmen

@jalf: Nur die Metadaten, die eventuell benötigt werden. Wenn wir nur die Reflexion zur Kompilierzeit betrachten, ist dies trivial. Z.B. eine Funktion zur Kompilierzeit members<T> die eine Liste aller Mitglieder von T zurückgibt. Wenn wir Laufzeitreflexion (d.h. RTTI gemischt mit Reflexion) haben wollten, würde der Compiler immer noch alle reflektierten Basistypen kennen. Es ist sehr wahrscheinlich, dass members<T>(T&) würde für T=std::string niemals instanziiert werden, so dass die RTTI für std::string oder seine abgeleiteten Klassen nicht einbezogen werden muss.

60voto

Damian Dixon Punkte 749

Reflexion wird von C++ nicht von Haus aus unterstützt. Das ist traurig, denn es macht defensive Tests zu einer Qual.

Es gibt verschiedene Ansätze für die Reflexion:

  1. die Debug-Informationen verwenden (nicht portabel).
  2. Ihren Code mit Makros/Schablonen oder einem anderen Quellcode-Ansatz versehen (sieht hässlich aus)
  3. Ändern Sie einen Compiler wie clang/gcc, um eine Datenbank zu erzeugen.
  4. Qt moc-Ansatz verwenden
  5. Boost-Reflexion
  6. Präzise und flache Reflexion

Der erste Link sieht am vielversprechendsten aus (verwendet Mods zu Clang), der zweite diskutiert eine Reihe von Techniken, der dritte ist ein anderer Ansatz, der gcc verwendet:

  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 von potenziellen Fortschritten bei den Überlegungen. Im Folgenden finden Sie weitere Einzelheiten und eine Diskussion über die verschiedenen Techniken und den Stand der Dinge:

  1. Statische Reflexion in aller Kürze
  2. Statische Reflexion
  3. Ein Entwurf für statische Reflexion

Es sieht jedoch nicht vielversprechend aus für einen standardisierten Reflexionsansatz in C++ in der nahen Zukunft, es sei denn, es gibt ein größeres Interesse der Gemeinschaft an der Unterstützung von Reflexion in C++.

Im Folgenden wird der aktuelle Stand auf der Grundlage des Feedbacks aus der letzten C++-Standardsitzung erläutert:

Bearbeiten 13/12/2017

Die Reflexion scheint sich in Richtung C++ 20 oder mehr zu bewegen, wahrscheinlich ein TSR. Die Entwicklung verläuft jedoch langsam.

Bearbeiten 15/09/2018

Ein Entwurf der TS wurde den nationalen Gremien zur Abstimmung vorgelegt.

Der Text ist hier zu finden: https://github.com/cplusplus/reflection-ts

Bearbeiten 11/07/2019

Der Reflexions-TS ist fertiggestellt und wird im Sommer (2019) zur Stellungnahme und Abstimmung vorgelegt.

Der Meta-Template-Programmierungsansatz soll durch einen einfacheren Kompilierzeitcode-Ansatz ersetzt werden (der sich nicht in der TS widerspiegelt).

Bearbeiten 10/02/2020

Es gibt eine Anfrage zur Unterstützung der Reflection TS in Visual Studio hier:

Vortrag des Autors David Sankel über den TS:

Bearbeiten 17 März 2020

Bei der Reflexion sind Fortschritte zu verzeichnen. Einen Bericht der "2020-02 Prague ISO C++ Committee Trip Report" finden Sie hier:

Einzelheiten zu den Überlegungen für C++23 finden Sie hier (einschließlich eines kurzen Abschnitts über Reflexion):

Bearbeiten 4. Juni 2020

Jeff Preshing hat ein neues Framework mit dem Namen "Plywood" veröffentlicht, das einen Mechanismus für die Reflexion zur Laufzeit enthält. Weitere Details finden Sie hier:

Die Werkzeuge und der Ansatz scheinen bisher am ausgefeiltesten und am einfachsten zu benutzen zu sein.

Bearbeiten Juli 12 2020

Clang experimenteller Reflection Fork : https://github.com/lock3/meta/wiki

Interessante Reflection-Bibliothek, die die Clang-Tooling-Bibliothek verwendet, um Informationen für einfache Reflection zu extrahieren, ohne Makros hinzufügen zu müssen: https://github.com/chakaz/reflang

Bearbeiten Feb 24 2021

Einige zusätzliche Clang-Tooling-Ansätze:

Bearbeiten 25. August 2021

Ein ACCU-Vortrag online auf youtube https://www.youtube.com/watch?v=60ECEc-URP8 ist ebenfalls einen Blick wert, da es über aktuelle Vorschläge zum Standard und eine Implementierung auf Basis von Clang berichtet.

Siehe:

1 Stimmen

Der cern-Link ist defekt.

0 Stimmen

Cern-Links sollten jetzt behoben sein. Sie neigen dazu, ziemlich häufig zu brechen, was sehr ärgerlich ist.

0 Stimmen

Bezieht sich diese Antwort nur auf die Reflexion zur Kompilierzeit?

59voto

Brad Wilson Punkte 64944

Und ich würde gerne ein Pony haben, aber Ponys sind nicht umsonst :-p

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

1 Stimmen

Ich unterstütze Brad. C++-Templates können sehr leistungsfähig sein, und es gibt eine Fülle von Erfahrungen mit verschiedenen "Reflection"-Typ-Verhaltensweisen, wie z. B. die Boost-"Any"-Bibliothek, Type Traits, C++ RTTI usw., die viele der Probleme lösen können, für die Reflection gedacht ist. Also Nick, was ist dein Ziel hier?

7 Stimmen

Upvote für die Pony-Bemerkung! Ich würde zweimal hochvoten, da deine Antwort es auch verdient, aber leider bekomme ich nur eine, also gewinnen die Ponys :-)

14 Stimmen

Ich verstehe nicht wirklich, warum dies eine kluge Antwort sein soll. Ich habe bereits gesagt, dass ich gerne Verweise auf Bibliotheken usw. hätte, um dies zu implementieren. Die Reflexion/Introspektion ist für verschiedene System, um Skript-Zugriff, Serialisierung usw. zu ermöglichen.

42voto

Roderick Punkte 1093

Die Informationen sind zwar vorhanden, aber nicht in dem Format, das Sie benötigen, und nur, wenn Sie Ihre Klassen exportieren. Dies funktioniert unter Windows, über andere Plattformen weiß ich nichts. Verwenden Sie die Speicherklassenspezifizierer wie z. B. in:

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

Dies bewirkt, dass der Compiler die Klassendefinitionsdaten in die DLL/Exe einbaut. Aber es ist nicht in einem Format, das Sie ohne weiteres für die Reflexion verwenden können.

In meiner Firma haben wir eine Bibliothek entwickelt, die diese Metadaten interpretiert und es 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 ist tatsächlich der Fall:

instance_ptr->Foo(1.331);

Die Funktion Invoke(this_pointer,...) hat variable Argumente. Wenn man eine Funktion auf diese Weise aufruft, umgeht man natürlich Dinge wie const-safety und so weiter, so dass diese Aspekte als Laufzeitprüfungen implementiert sind.

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

1 Stimmen

Ich glaube, Sie meinen __declspec(dllexport) und Sie können die Informationen aus einer .map-Datei abrufen, wenn Sie die Erstellung einer solchen während der Erstellung aktivieren.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X