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