Comment puis-je ajouter une réflexion à une application c++?
J'aimerais pouvoir introspecter une Classe C++ pour son nom, son contenu (c'est-à-dire les membres et leurs types), etc. Je parle de C++ natif ici, pas de C++ géré, qui a une réflexion. Je me rends compte que C++ fournit des informations limitées en utilisant RTTI. Quelles bibliothèques supplémentaires (ou d'autres techniques) pourraient fournir cette information?
30 réponses
Ce que vous devez faire est que le préprocesseur génère des données de réflexion sur les champs. Ces données peuvent être stockées en tant que classes imbriquées.
Tout d'abord, pour le rendre plus facile et plus propre à écrire dans le préprocesseur, nous allons utiliser l'expression typée. Typé expression est juste une expression qui met le type entre parenthèses. Donc au lieu d'écrire int x
, vous écrivez (int) x
. Voici quelques macros pratiques pour aider avec les expressions typées:
#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
Ensuite, nous définissons une macro REFLECTABLE
pour générer les données sur chaque champ (plus le champ lui-même). Cette macro sera appelée comme ceci:
REFLECTABLE
(
(const char *) name,
(int) age
)
Donc en utilisant Boost.PP nous itérons sur chaque argument et générons les données comme ceci:
// 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)); \
} \
}; \
Ce que cela fait est de générer une constante fields_n
qui est le nombre de champs réflectables dans la classe. Ensuite, il se spécialise le field_data
pour chaque champ. Il ami aussi la classe reflector
, c'est ainsi qu'il peut accéder aux champs même lorsqu'ils sont privés:
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;
};
};
Maintenant à itérer sur les champs que nous utilisons le modèle de visiteur. Nous créons une plage MPL allant de 0 au nombre de champs, et accédons aux données de champ à cet index. Ensuite, il transmet les données de champ au visiteur fourni par l'utilisateur:
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));
}
Maintenant, pour le moment de vérité, nous mettons tout cela ensemble. Voici comment nous pouvons définir une classe Person
qui peut être réfléchie:
struct Person
{
Person(const char *name, int age)
:
name(name),
age(age)
{
}
private:
REFLECTABLE
(
(const char *) name,
(int) age
)
};
Voici une fonction print_fields
généralisée utilisant les données de réflexion pour itérer sur les champs:
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());
}
Un exemple d'utilisation le print_fields
avec la classe Person
réflectable:
int main()
{
Person p("Tom", 82);
print_fields(p);
return 0;
}
Quelles sorties:
name=Tom
age=82
Et voilà, nous venons d'implémenter la réflexion en C++, en moins de 100 lignes de code.
Il y a deux sortes de reflection
nageant autour.
- Inspection en itérant sur les membres d'un type, en énumérant ses méthodes et ainsi de suite.
Ce n'est pas possible en C++. - Inspection en vérifiant si un type de classe (class, struct, union) a une méthode ou un type imbriqué, est dérivé d'un autre type particulier.
Ce genre de chose est possible avec C++ en utilisanttemplate-tricks
. Utilisationboost::type_traits
pour beaucoup de choses (comme vérifier si un type fait partie intégrante). Pour vérification de l'existence d'une fonction membre, utilisez Est-il possible d'écrire un modèle pour vérifier la fonction de l'existence? . Pour vérifier si un certain type imbriqué existe, utilisez plain SFINAE .
Si vous cherchez plutôt des moyens d'accomplir 1), comme regarder combien de méthodes une classe A, ou comme obtenir la représentation de chaîne d'un id de classe, alors j'ai peur qu'il n'y ait pas de façon Standard C++ de le faire. Vous devez utiliser soit
- Un Meta compilateur comme le compilateur Qt Meta Object qui traduit votre code en ajoutant des informations méta supplémentaires.
- un Framework constitué de macros permettant d'ajouter les méta-informations requises. Vous devez indiquer au framework toutes les méthodes, les noms de classe, les classes de base et tout ce dont il a besoin.
C++ est fait avec la vitesse à l'esprit. Si vous voulez une inspection de haut niveau, comme C# ou Java, alors je crains de devoir vous dire qu'il n'y a aucun moyen sans effort.
Et j'adorerais un poney, mais les poneys ne sont pas libres. :- p
Http://en.wikibooks.org/wiki/C%2B%2B_Programming/RTTI est ce que vous allez obtenir. La réflexion comme vous pensez à-métadonnées entièrement descriptives disponibles à l'exécution-n'existe tout simplement pas pour C++ par défaut.
L'information existe - mais pas dans le format dont vous avez besoin, et seulement si vous exportez vos classes. Cela fonctionne dans Windows, Je ne connais pas d'autres plates-formes. Utilisation des spécificateurs de classe de stockage comme dans, par exemple:
class __declspec(export) MyClass
{
public:
void Foo(float x);
}
Cela permet au compilateur de construire les données de définition de classe dans la DLL / Exe. Mais ce n'est pas dans un format que vous pouvez facilement utiliser pour la réflexion.
Dans mon entreprise, nous avons construit une bibliothèque qui interprète ces métadonnées, et vous permet de refléter une classe sans insertion de macros supplémentaires, etc. dans la classe elle-même. Il permet aux fonctions d'être appelées comme suit:
MyClass *instance_ptr=new MyClass;
GetClass("MyClass")->GetFunction("Foo")->Invoke(instance_ptr,1.331);
Cela fait effectivement:
instance_ptr->Foo(1.331);
L'Invoke (this_pointer,...) la fonction a des arguments variables. Évidemment, en appelant une fonction de cette façon, vous contournez des choses comme const-safety et ainsi de suite, de sorte que ces aspects sont implémentés en tant que vérifications d'exécution.
Je suis sûr que la syntaxe pourrait être améliorée, et cela ne fonctionne que sur Win32 et Win64 jusqu'à présent. Nous avons trouvé vraiment utile pour avoir des interfaces graphiques automatiques pour les classes, créer des propriétés en C++, diffuser vers et depuis XML, etc., et il n'est pas nécessaire de dériver d'une classe de base spécifique. S'il y a assez de demande, peut-être que nous pourrions le mettre en forme pour la libération.
RTTI n'existe pas pour C++.
C'est tout simplement faux. En fait, le terme même "RTTI" a été inventé par la norme c++. D'autre part, RTTI ne va pas très loin dans la mise en œuvre de la réflexion.
Vous devez regarder ce que vous essayez de faire, et si RTTI répondra à vos besoins. J'ai implémenté ma propre pseudo-réflexion à des fins très spécifiques. Par exemple, je voulais être en mesure de configurer de manière flexible ce qu'une simulation produirait. Il fallait ajouter du code standard aux classes qui seraient sorties:
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;
}
Le premier appel ajoute cet objet au système de filtrage, qui appelle la méthode BuildMap()
pour déterminer quelles méthodes sont disponibles.
, Puis, dans le fichier de configuration, vous pouvez faire quelque chose comme ceci:
FILTER-OUTPUT-OBJECT MyObject
FILTER-OUTPUT-FILENAME file.txt
FILTER-CLAUSE-1 person == 1773
FILTER-CLAUSE-2 time > 2000
Grâce à une magie de modèle impliquant boost
, cela se traduit par une série d'appels de méthode au moment de l'exécution (lorsque le fichier de configuration est lu), donc c'est assez efficace. Je ne recommanderais pas de faire cela sauf si vous en avez vraiment besoin, mais, quand vous le faites, vous pouvez faire des choses vraiment cool.
Qu'essayez-vous de faire avec la réflexion?
Vous pouvez utiliser les bibliothèques Boost Type traits et typeof comme une forme limitée de réflexion à la compilation. Autrement dit, Vous pouvez inspecter et modifier les propriétés de base d'un type transmis à un modèle.
Modifier: le CAMP n'est plus entretenu; deux fourches sont disponibles:
- On est aussi appelé CAMP , et est basé sur la même API.
- Ponder est une réécriture partielle, et doit être préférée car elle ne nécessite pas de Boost; elle utilise C++11.
La bibliothèque actuelle de Tegesoft utilise Boost, mais il y a aussi un fork utilisant C++11 Qui ne nécessite plus Boost.
J'ai fait quelque chose comme ce que vous recherchez une fois, et bien qu'il soit possible d'obtenir un certain niveau de réflexion et d'accéder à des fonctionnalités de niveau supérieur, le mal de tête de maintenance pourrait ne pas en valoir la peine. Mon système a été utilisé pour garder les classes D'interface utilisateur complètement séparées de la logique métier grâce à la délégation semblable au concept D'Objective-C de transmission et de transfert de messages. La façon de le faire est de créer une classe de base capable de mapper des symboles (j'ai utilisé un pool de chaînes mais vous pouvez le faire avec enums si vous préférez la vitesse et la gestion des erreurs à la compilation sur la flexibilité totale) aux pointeurs de fonction (en fait pas des pointeurs de fonction purs, mais quelque chose de similaire à ce que Boost A avec Boost.Fonction-à laquelle je n'avais pas accès à l'époque). Vous pouvez faire la même chose pour vos variables membres tant que vous avez une classe de base commune capable de représenter n'importe quelle valeur. L'ensemble du système était une arnaque sans faille de codage clé-valeur et de délégation, avec quelques effets secondaires qui étaient peut-être cela vaut le temps nécessaire pour que chaque classe qui utilisait le système corresponde à toutes ses méthodes et membres avec des appels légaux: 1) n'importe quelle classe pourrait appeler n'importe quelle méthode sur n'importe quelle autre classe sans avoir à inclure des en-têtes ou écrire de fausses classes de base afin que l'interface puisse être prédéfinie pour le compilateur; et 2) tous les objets.
Cela a également conduit à la possibilité de faire des choses vraiment bizarres qui ne sont pas faciles en C++. Par exemple, je pourrais créer un objet tableau contenant des éléments arbitraires de tout type, y compris lui-même, et créer de nouveaux tableaux dynamiquement en passant un message à tous les éléments du tableau et en collectant les valeurs de retour (similaire à map en Lisp). Une autre était la mise en œuvre de l'observation de la valeur clé, par laquelle j'ai été en mesure de mettre en place l'interface utilisateur pour répondre immédiatement aux changements dans le membres des classes backend au lieu d'interroger constamment les données ou de redessiner inutilement l'affichage.
Peut-être plus intéressant pour vous est le fait que vous pouvez également vider toutes les méthodes et les membres définis pour une classe, et sous forme de chaîne pas moins.
Inconvénients du système qui pourraient vous décourager de vous embêter: ajouter tous les messages et les valeurs clés est extrêmement fastidieux; c'est plus lent que sans aucune réflexion; vous allez détester voir boost::static_pointer_cast
et boost::dynamic_pointer_cast
partout dans votre codebase avec une passion violente; les limites du système fortement typé sont toujours là, vous les cachez vraiment un peu, donc ce n'est pas aussi évident. Les fautes de frappe dans vos chaînes ne sont pas non plus une surprise amusante ou facile à découvrir.
Quant à la façon d'implémenter quelque chose comme ceci: il suffit d'utiliser des pointeurs partagés et faibles vers une base commune (le mien était très imaginativement appelé "objet") et dériver pour tous les types que vous voulez utiliser. Je recommande d'installer Boost.Fonction au lieu de le faire de la manière Je l'ai fait, ce qui était avec de la merde personnalisée et une tonne de macros laides pour envelopper les appels de pointeur de fonction. Puisque tout est mappé, l'inspection des objets est juste une question d'itérer à travers toutes les clés. Puisque mes classes étaient essentiellement aussi proches d'une arnaque directe de Cocoa que possible en utilisant uniquement C++, si vous voulez quelque chose comme ça, je suggérerais d'utiliser la documentation Cocoa comme plan.
Les deux solutions de type réflexion que je connais de mes jours c++ sont:
1) Utilisez RTTI, qui vous fournira un bootstrap pour construire votre comportement de type réflexion, si vous êtes capable de faire dériver toutes vos classes à partir d'une classe de base 'object'. Cette classe pourrait fournir des méthodes comme GetMethod, GetBaseClass etc. En ce qui concerne le fonctionnement de ces méthodes, vous devrez ajouter manuellement des macros pour décorer vos types, qui, dans les coulisses, créent des métadonnées dans le type pour fournir des réponses GetMethods etc.
2) une autre option, si vous avez accès aux objets du compilateur est d'utiliser le SDK DIA . Si je me souviens bien, cela vous permet d'ouvrir des PDB, qui devraient contenir des métadonnées pour vos types C++. Il pourrait être suffisant pour faire ce dont vous avez besoin. Cette page montre comment vous pouvez obtenir tous les types de base d'une classe par exemple.
Ces deux solutions sont un peu laides! Il n'y a rien comme un peu de C++ pour vous faire apprécier le luxe de C#.
Bon Chance.
EDIT: mise à jour du lien brisé à partir du 7 février 2017.
Je pense que personne n'a mentionné ceci:
Au CERN, ils utilisent un système de réflexion complet pour C++:
Le CERN Réflexe. Il semble très bien fonctionner.
Cette question est un peu ancienne maintenant (je ne sais pas pourquoi je continue à poser de vieilles questions aujourd'hui) mais je pensais à BOOST_FUSION_ADAPT_STRUCT qui introduit la réflexion à la compilation.
C'est à vous de mapper ceci à la réflexion d'exécution bien sûr, et ce ne sera pas trop facile, mais c'est possible dans cette direction, alors que ce ne serait pas dans le sens inverse:)
Je pense vraiment qu'une macro pour encapsuler les BOOST_FUSION_ADAPT_STRUCT
on pouvait générer les méthodes nécessaires pour obtenir l'exécution comportement.
Je pense que vous pourriez trouver intéressant L'article "utilisation de modèles pour la réflexion en C++" par Dominic Filion. Il est dans la section 1.4 de jeu de programmation Gems 5 . Malheureusement je n'ai pas mon exemplaire avec moi, mais parce que je pense qu'il explique ce que vous demandez.
La réflexion n'est pas prise en charge par C++. C'est triste parce que cela rend les tests défensifs une douleur.
Il existe plusieurs approches pour faire de la réflexion:
- Utilisez les informations de débogage (non portable).
- saupoudrez votre code avec des macros/templates ou une autre approche source (semble moche)
- modifiez un compilateur tel que clang / gcc pour produire une base de données.
- utiliser l'approche Qt moc
- Boost Reflète
- Précis et Plat La réflexion
Le premier lien semble le plus prometteur (utilise des mod pour clang), le second discute d'un certain nombre de techniques, le troisième est une approche différente en utilisant gcc:
Il y a maintenant un groupe de travail pour la réflexion C++. Voir les nouvelles pour C++14 @ CERN:
Édition 13/08/17: Depuis le poste original, il y a eu un certain nombre de progrès potentiels sur la réflexion. Ce qui suit fournit plus de détails et une discussion sur les différentes techniques et le statut:
Cependant, il ne semble pas prometteur sur une approche de réflexion standardisée en C++ dans un proche avenir à moins que la communauté ne s'intéresse beaucoup plus à la réflexion en C++.
Ce qui suit détaille l'état actuel en fonction des commentaires de la dernière réunion des normes C++:
Modifier 13/12/2017
La réflexion semble se diriger vers C++ 20 ou plus probablement un TSR. Le mouvement est cependant lent.
- Miroir
- proposition de norme miroir
- Miroir de papier
- Herb Sutter-méta programmation incluant la réflexion
Modifier 15/09/2018
Un projet de TS a été envoyé aux instances nationales pour vote.
, Le texte peut être trouvé ici: https://github.com/cplusplus/reflection-ts
Ponder est une bibliothèque de réflexion C++, en réponse à cette question. J'ai considéré les options et j'ai décidé de faire la mienne car je ne pouvais pas en trouver une qui cochait toutes mes cases.
Bien qu'il y ait d'excellentes réponses à cette question, Je ne veux pas utiliser des tonnes de macros, ou compter sur Boost. Boost est une grande bibliothèque, mais il y a beaucoup de petits projets C++0x sur mesure qui sont plus simples et ont des temps de compilation plus rapides. Il y a aussi des avantages à pouvoir décorer une classe extérieurement, comme envelopper une bibliothèque C++ qui ne le fait pas (encore?) soutien C++11. C'est fork de CAMP, en utilisant C++11, que ne nécessite plus de Boost.
La réflexion concerne essentiellement ce que le compilateur a décidé de laisser comme empreintes dans le code que le code d'exécution peut interroger. C++ est célèbre pour ne pas payer pour ce que vous n'utilisez pas; parce que la plupart des gens n'utilisent pas / veulent la réflexion, le compilateur C++ évite le coût en n'enregistrant rien .
Donc, C++ ne fournit pas de réflexion, et il n'est pas facile de le "simuler" vous-même en règle générale comme d'autres réponses l'ont noté.
Sous "autres techniques", si vous n'avez pas langage avec réflexion, Obtenez un outil qui peut extraire les informations que vous voulez au moment de la compilation.
Notre DMS Software Reengineering Toolkit est une technologie de compilateur généralisée paramétrée par des définitions langauge explicites. Il a des définitions langauge pour C, C++, Java, COBOL, PHP,...
Pour les versions C, C++, Java et COBOL, il fournit un accès complet aux arbres d'analyse et aux informations de table de symboles. Cette information de table de symbole inclut le genre de les données que vous êtes susceptible de vouloir de "réflexion". Si votre objectif est d'énumérer un ensemble de champs ou de méthodes et Faire quelque chose avec eux, DMS peut être utilisé pour transformer le code en fonction de ce que vous trouvez dans les tables de symboles de manière arbitraire.
Vous pouvez trouver une autre bibliothèque ici: http://www.garret.ru/cppreflection/docs/reflect.html Il prend en charge 2 façons: obtenir des informations de type à partir des informations de débogage et laisser programmeur pour fournir ces informations.
Je me suis aussi intéressé à la réflexion pour mon projet et j'ai trouvé cette bibliothèque, Je ne l'ai pas encore essayé, mais j'ai essayé d'autres outils de ce gars et j'aime comment ils fonctionnent: -)
Consulter Classdesc http://classdesc.sf.net. Il fournit une réflexion sous la forme de "descripteurs" de classe, fonctionne avec n'importe quel compilateur C++ standard (Oui, il est connu pour fonctionner avec Visual Studio ainsi que GCC), et ne nécessite pas d'annotation de code source (bien que certains pragmas existent pour gérer des situations délicates). Il a été en développement pendant plus d'une décennie, et utilisé dans un certain nombre de projets à l'échelle industrielle.
Quand je voulais une réflexion en C++ j'ai Lu Cet article et amélioré ce que j'ai vu là-bas. Désolé, non est. Je n'ai pas le résultat...mais vous pouvez certainement obtenir ce que j'avais et partir de là.
Je recherche actuellement, Quand j'en ai envie, des méthodes pour utiliser inherit_linearly pour faciliter la définition des types réflectables. Je suis allé assez loin en fait, mais j'ai encore du chemin à parcourir. Les changements dans C++0x sont très susceptibles d'avoir beaucoup d'aide dans ce zone.
Essayer de regarder ce projet http://www.garret.ru/cppreflection/docs/reflect.html est ajouté réflexions à C++. Il a ajouté des méta-données aux classes que vous pouvez ensuite utiliser.
Même si la réflexion n'est pas prise en charge en c++, elle n'est pas trop difficile à implémenter. J'ai rencontré ce grand article: http://replicaisland.blogspot.co.il/2010/11/building-reflective-object-system-in-c.html
L'article explique en détail comment vous pouvez implémenter un système de réflexion assez simple et rudimentaire. certes, ce n'est pas la solution la plus saine, et il reste des bords rugueux à régler, mais pour mes besoins, c'était suffisant.
La réflexion de la ligne de fond peut être payante si elle est effectuée correctement, et c'est complètement faisable en C++.
Je voudrais annoncer l'existence de la boîte à outils d'introspection/réflexion automatique "IDK". Il utilise un méta-compilateur comme Qt et ajoute des méta-informations directement dans les fichiers objet. Il est prétendu être facile à utiliser. Pas de dépendances externes. Il vous permet même de refléter automatiquement std:: string, puis de l'utiliser dans des scripts. Veuillez regarder IDK
La réflexion en c++ est très utile, dans les cas où vous devez exécuter une méthode pour chaque membre (par exemple: sérialisation, hachage, comparaison). Je suis venu avec une solution générique, avec une syntaxe très simple:
struct S1
{
ENUMERATE_MEMBERS(str,i);
std::string str;
int i;
};
struct S2
{
ENUMERATE_MEMBERS(s1,i2);
S1 s1;
int i2;
};
Où ENUMERATE_MEMBERS est une macro, qui est décrite plus loin (mise à jour):
Supposons que nous ayons défini la fonction de sérialisation pour int et std:: string comme ceci:
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());
}
Et nous avons une fonction générique près de la "macro secrète";)
template<typename TWriter, typename T>
auto EnumerateWith(TWriter && writer, T && val) -> is_enumerable_t<T>
{
val.EnumerateWith(write); //method generated by ENUMERATE_MEMBERS macro
}
Maintenant vous pouvez écrire
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)
Donc, ayant la macro ENUMERATE_MEMBERS dans la définition de la structure, vous pouvez construire la sérialisation, la comparaison, le hachage et d'autres éléments sans toucher au type d'origine, la seule exigence est d'implémenter la méthode "EnumerateWith" pour chaque type, qui n'est pas énumérable, par énumérateur (comme BinaryWriter). Habituellement, vous devrez implémenter 10-20 types "simples" pour prendre en charge n'importe quel type dans votre projet.
Cette macro devrait avoir une surcharge nulle pour la création/destruction de la structure au moment de l'exécution, et le code de T. EnumerateWith () doit être généré à la demande, ce qui peut être réalisé en le rendant fonction template-inline, donc la seule surcharge dans toute l'histoire est d'ajouter ENUMERATE_MEMBERS (m1,m2,m3...) à chaque structure, tout en implémentant une méthode spécifique par type de membre est un must dans n'importe quelle solution, donc je ne l'assume pas comme une surcharge.
Mise à jour: Il y a une implémentation très simple de la macro ENUMERATE_MEMBERS (mais elle pourrait être un peu étendue pour supporter l'héritage de enumerable struct)
#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);
}
Et vous n'avez pas besoin d'une bibliothèque tierce pour ces 15 lignes de code;)
Mise à Jour 24.2.2017
Auparavant, j'ai analysé le support pour l'utilisation de #define et comme il est recommandé dans certains articles web-j'ai frappé à travers les définitions dans visual C++ ne fonctionnaient pas identique à define utilisé dans gcc (par exemple, sur internet, cela est souvent appelé "walkaround MSVC"). En plus de ne pas être capable de comprendre facilement ce qui se passe derrière les machines d'expansion define / macro-il est plutôt difficile de déboguer chaque expansion macro.
Il existe plusieurs façons de contourner les complexités de l'expansion de define, une approche consiste à activer l'indicateur du compilateur "/P" (pré-processus pour déposer uniquement) - après cela, vous pouvez comparer la façon dont votre define est ouvert. (Auparavant, j'ai également utilisé activement l'opérateur stringfy (#))
J'ai rassemblé toutes les définitions utiles de plusieurs forums, les ai utilisées et commenté ce qui se passe derrière les machines, vous pouvez trouver un fichier d'en-tête entier ici maintenant:
Https://sourceforge.net/p/testcppreflect/code/HEAD/tree/MacroHelpers.h
Je pensais qu'il était plutôt trivial d'utiliser ces macros pour activer la réflexion C++, mais cela nécessite un peu plus de magie pour faire la réflexion.
Je me suis souvenu d'un exemple de code de travail, et le mettre comme projet sourceforge, peut être téléchargé ici:
Https://sourceforge.net/p/testcppreflect/code/HEAD/tree/
Le code de démonstration ressemble à ce:
#include "CppReflect.h"
using namespace std;
class Person
{
public:
REFLECTABLE( Person,
(CString) name,
(int) age
)
};
class People
{
public:
REFLECTABLE( People,
(CString) groupName,
(vector<Person>) people
)
};
void main(void)
{
People ppl;
ppl.groupName = "Group1";
Person p;
p.name = L"Roger";
p.age = 37;
ppl.people.push_back(p);
p.name = L"Alice";
p.age = 27;
ppl.people.push_back( p );
p.name = L"Cindy";
p.age = 17;
ppl.people.push_back( p );
CStringA xml = ToXML( &ppl );
CStringW errors;
People ppl2;
FromXml( &ppl2, xml, errors );
CStringA xml2 = ToXML( &ppl2 );
printf( xml2 );
}
REFLECTABLE define utilise class name + field name Avec offsetof - pour identifier à quel endroit dans la mémoire un champ particulier est situé. J'ai essayé de ramasser la terminologie.net autant que possible, mais C++ et C# sont différents, donc ce n'est pas 1 à 1. Tout le modèle de réflexion C++ réside dans les classes TypeInfo et FieldInfo pour le timebeing, il est possible d'étendre le support aussi à la méthode, mais j'ai décidé de garder les choses simples pour l'instant.
J'ai utilisé Pugi xml parser pour récupérer code de démonstration en xml et le restaurer à partir de xml.
, Donc la sortie produite par le code de démonstration ressemble à ceci:
<?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>
Il est également possible d'activer tout support de classe / structure 3-ème partie via la classe TypeTraits, et la spécification de Modèle partiel-pour définir votre propre classe TypeTraitsT
, de la même manière que CString
ou int
- Voir exemple de code dans
Https://sourceforge.net/p/testcppreflect/code/HEAD/tree/TypeTraits.h#l65
template <>
class TypeTraitsT<CString> : public TypeTraits
{
public:
virtual CStringW ToString( void* pField )
{
CString* s = (CString*)pField;
return *s;
}
virtual void FromString( void* pField, const wchar_t* value )
{
CString* s = (CString*)pField;
*s = value;
}
};
template <>
class TypeTraitsT<int> : public TypeTraits
{
public:
virtual CStringW ToString( void* pField )
{
int* p = (int*) pField;
return std::to_string(*p).c_str();
}
virtual void FromString( void* pField, const wchar_t* value )
{
int* p = (int*)pField;
*p = _wtoi(value);
}
};
Je suppose que seul inconvénient de mon propre l'implémentation est l'utilisation de __if_exists
- qui pourrait être une extension spécifique au compilateur Microsoft. Si quelqu'un sait comment marcher autour, faites le moi savoir.
Le manque de réflexion intégrée en c++ est la seule raison pour laquelle le C++ moderne n'est pas utilisé pour le développement web (et manque D'ORM et d'autres frameworks)
Vous pouvez essayer http://www.extreme.indiana.edu/reflcpp/
Un moyen simple consiste à utiliser l'opérateur dynamic_cast<>()
qui, lorsqu'il est affecté à un mauvais type, renvoie NULL, de sorte que vous pouvez facilement passer à une classe concrète de base, en vérifiant la valeur du pointeur, s'il N'est pas NULL, le cast a été fait, et vous avez le type de l'objet.
Mais ce n'est qu'une solution simple, et elle ne fournit que le type des objets, vous ne pouvez pas demander quelles méthodes il a, comme en Java. Si vous avez besoin d'une solution avancée, il y a quelques frameworks à choisir.