Reflection in C++ ist sehr nützlich, in Fällen, in denen Sie benötigen, um einige Methode für jedes Mitglied (zum Beispiel: Serialisierung, Hashing, vergleichen). Ich kam mit generischen Lösung, mit sehr einfachen Syntax:
struct S1
{
ENUMERATE_MEMBERS(str,i);
std::string str;
int i;
};
struct S2
{
ENUMERATE_MEMBERS(s1,i2);
S1 s1;
int i2;
};
Dabei ist ENUMERATE_MEMBERS ein Makro, das später beschrieben wird (UPDATE):
Angenommen, wir haben eine 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)
Mit dem ENUMERATE_MEMBERS-Makro in der struct-Definition können Sie Serialisierung, Vergleich, Hashing und andere Funktionen erstellen, ohne den Originaltyp zu berühren. Die einzige Anforderung ist die Implementierung der "EnumerateWith"-Methode für jeden Typ, der nicht aufzählbar ist, per Enumerator (wie BinaryWriter). Normalerweise müssen Sie 10-20 "einfache" Typen implementieren, um jeden Typ in Ihrem Projekt zu unterstützen.
Dieses Makro sollte Null-Overhead für die Erstellung/Zerstörung von Strukturen zur Laufzeit haben, und der Code von T.EnumerateWith() sollte bei Bedarf generiert werden, was erreicht werden kann, indem man es zu einer Template-Inline-Funktion macht, so dass der einzige Overhead in der ganzen Geschichte darin besteht, ENUMERATE_MEMBERS(m1,m2,m3...) zu jeder Struktur hinzuzufügen, während die Implementierung einer spezifischen Methode pro Mitgliedstyp ein Muss in jeder Lösung ist, so dass ich es nicht als Overhead annehme.
UPDATE: Es ist sehr einfache Implementierung von ENUMERATE_MEMBERS Makro (jedoch könnte es ein wenig erweitert werden, um Vererbung von enumerable struct 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 Zeilen Code benötigen Sie keine Bibliothek eines Drittanbieters ;)
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.