Question

J'aimerais pouvoir introspecter une classe C++ pour son nom, son contenu (c'est-à-diremembres et leurs types), etc.Je parle ici de C++ natif, et non de C++ managé, qui a une réflexion.Je réalise que C++ fournit des informations limitées en utilisant RTTI.Quelles bibliothèques supplémentaires (ou autres techniques) pourraient fournir ces informations ?

Était-ce utile?

La solution 17

Réflechir est une bibliothèque de réflexion C++, en réponse à cette question.J'ai examiné les options et j'ai décidé de créer le mien car je n'en trouvais pas un qui réponde à toutes mes cases.

Bien qu'il existe d'excellentes réponses à cette question, je ne veux pas utiliser des tonnes de macros ni compter sur Boost.Boost est une excellente bibliothèque, mais il existe de nombreux 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 en externe, comme encapsuler une bibliothèque C++ qui ne prend pas (encore ?) en charge C++11.C'est un fork de CAMP, utilisant C++11, qui ne nécessite plus Boost.

Autres conseils

Ce que vous devez faire, c'est demander au préprocesseur de générer des données de réflexion sur les champs.Ces données peuvent être stockées sous forme de classes imbriquées.

Tout d’abord, pour rendre son écriture plus facile et plus propre dans le préprocesseur, nous utiliserons une expression typée.Une expression typée est simplement une expression qui met le type entre parenthèses.Alors au lieu d'écrire int x Vous écrirez (int) x.Voici quelques macros pratiques pour vous aider avec les expressions saisies :

#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 un REFLECTABLE macro pour générer les données sur chaque champ (plus le champ lui-même).Cette macro s'appellera ainsi :

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

Donc en utilisant Boost.PP nous parcourons 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)); \
    } \
}; \

Cela génère une constante fields_n c'est le nombre de champs réfléchissants dans la classe.Puis il spécialise le field_data pour chaque champ.Il est également ami avec reflector classe, c'est pour qu'il puisse 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, pour parcourir les champs, nous utilisons le modèle de visiteur.Nous créons une plage MPL de 0 au nombre de champs et accédons aux données du champ à cet index.Ensuite, il transmet les données du 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 rassemblons tout cela.Voici comment on peut définir un Person classe réfléchissante :

struct Person
{
    Person(const char *name, int age)
        :
        name(name),
        age(age)
    {
    }
private:
    REFLECTABLE
    (
        (const char *) name,
        (int) age
    )
};

Voici une généralisation print_fields fonction utilisant les données de réflexion pour parcourir 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 du print_fields avec le réfléchissant Person classe:

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 existe deux sortes de reflection nager.

  1. Inspection en itérant sur les membres d'un type, en énumérant ses méthodes, etc.

    Ce n'est pas possible avec C++.
  2. Inspection en vérifiant si un type de classe (classe, 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 utilisant template-tricks.Utiliser boost::type_traits pour beaucoup de choses (comme vérifier si un type est intégral).Pour vérifier l'existence d'une fonction membre, utilisez Est-il possible d'écrire un modèle pour vérifier l'existence d'une fonction ? .Pour vérifier si un certain type imbriqué existe, utilisez plain SFINAE .

Si vous cherchez plutôt des moyens d'accomplir 1), comme regarder le nombre de méthodes d'une classe ou obtenir la représentation sous forme de chaîne d'un identifiant de classe, alors je crains qu'il n'y ait pas de moyen standard C++ de le faire.Vous devez utiliser soit

  • Un méta-compilateur comme le compilateur de méta-objets Qt qui traduit votre code en ajoutant des méta-informations supplémentaires.
  • Un Framework constitué de macros qui permettent d'ajouter les méta-informations requises.Vous devrez indiquer au framework toutes les méthodes, les noms de classe, les classes de base et tout ce dont il a besoin.

Le C++ est conçu dans un souci de rapidité.Si vous souhaitez une inspection de haut niveau, comme C# ou Java, alors je crains de devoir vous dire qu'il n'y a aucun moyen sans un certain effort.

Et j'adorerais avoir un poney, mais les poneys ne sont pas gratuits.:-p

http://en.wikibooks.org/wiki/C%2B%2B_Programming/RTTI c'est ce que vous allez obtenir.La réflexion à laquelle vous pensez - métadonnées entièrement descriptives disponibles au moment de l'exécution - n'existe tout simplement pas pour C++ par défaut.

RTTI n'existe pas pour C++.

C'est tout simplement faux.En fait, le terme même « RTTI » a été inventé par le standard C++.En revanche, RTTI ne va pas très loin dans la mise en œuvre de la réflexion.

Les informations existent, mais pas dans le format dont vous avez besoin, et uniquement si vous exportez vos cours.Cela fonctionne sous Windows, je ne connais pas les autres plateformes.En utilisant les spécificateurs de classe de stockage comme dans, par exemple :

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

Cela oblige le compilateur à créer 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 insérer de macros supplémentaires, etc.dans la classe elle-même.Il permet d'appeler des fonctions comme suit :

MyClass *instance_ptr=new MyClass;
GetClass("MyClass")->GetFunction("Foo")->Invoke(instance_ptr,1.331);

Cela fait effectivement :

instance_ptr->Foo(1.331);

La fonction Invoke(this_pointer,...) a des arguments variables.Évidemment, en appelant une fonction de cette manière, vous contournez des éléments tels que const-safety, etc., ces aspects sont donc implémentés en tant que contrôles d'exécution.

Je suis sûr que la syntaxe pourrait être améliorée et qu'elle ne fonctionne jusqu'à présent que sur Win32 et Win64.Nous l'avons trouvé très utile pour avoir des interfaces GUI automatiques avec 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 suffisamment de demande, nous pourrions peut-être le mettre en forme pour sa sortie.

Vous devez examiner ce que vous essayez de faire et si RTTI répondra à vos exigences.J'ai implémenté ma propre pseudo-réflexion à des fins très spécifiques.Par exemple, je voulais autrefois pouvoir configurer de manière flexible ce qu'une simulation produirait.Cela nécessitait d'ajouter du code passe-partout aux classes qui seraient générées :

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 le BuildMap() méthode pour déterminer quelles méthodes sont disponibles.

Ensuite, 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), c'est donc assez efficace.Je ne recommanderais pas de faire cela à moins que vous en ayez vraiment besoin, mais lorsque vous le faites, vous pouvez faire des choses vraiment sympas.

Qu’essayez-vous de faire avec la réflexion ?
Vous pouvez utiliser le Boost caractères de type et Type de bibliothèques comme forme limitée de réflexion au moment de la compilation.Autrement dit, vous pouvez inspecter et modifier les propriétés de base d'un type transmis à un modèle.

Je recommanderais d'utiliser Qt.

Il existe une licence open source ainsi qu'une licence commerciale.

MODIFIER: CAMP n'est plus entretenu ;deux fourchettes sont disponibles :

  • L'un est aussi appelé CAMP aussi, et est basé sur la même API.
  • Réflechir est une réécriture partielle et doit être préférée car elle ne nécessite pas Boost ;il utilise C++11.

CAMP est une bibliothèque sous licence MIT (anciennement LGPL) qui ajoute une réflexion au langage C++.Cela ne nécessite pas d'étape de prétraitement spécifique dans la compilation, mais la liaison doit être effectuée manuellement.

La bibliothèque Tegesoft actuelle utilise Boost, mais il existe également une fourchette en 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 casse-tête de la maintenance n'en vaut peut-être pas 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 via une délégation semblable au concept de transmission et de transfert de messages d'Objective-C.La façon de procéder 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 des énumérations si vous préférez la rapidité et la gestion des erreurs à la compilation plutôt qu'une 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.Function - auquel je n'avais pas accès à l'époque).Vous pouvez faire la même chose pour vos variables membres tant que vous disposez d’une classe de base commune capable de représenter n’importe quelle valeur.L'ensemble du système était une arnaque éhontée du codage des valeurs clés et de la délégation, avec quelques effets secondaires qui valaient peut-être le temps nécessaire pour que chaque classe utilisant le système fasse correspondre toutes ses méthodes et ses membres avec des appels légaux. :1) N'importe quelle classe peut appeler n'importe quelle méthode sur n'importe quelle autre classe sans avoir à inclure d'en-têtes ou à écrire de fausses classes de base afin que l'interface puisse être prédéfinie pour le compilateur ;et 2) Les getters et setters des variables membres étaient faciles à rendre thread-safe car la modification ou l'accès à leurs valeurs se faisait toujours via 2 méthodes dans la classe de base de tous les objets.

Cela a également conduit à la possibilité de faire des choses vraiment étranges qui autrement ne seraient pas faciles en C++.Par exemple, je pourrais créer un objet Array contenant des éléments arbitraires de tout type, y compris lui-même, et créer de nouveaux tableaux de manière dynamique en transmettant un message à tous les éléments du tableau et en collectant les valeurs de retour (similaire à map en Lisp).Un autre était la mise en œuvre de l'observation des valeurs-clés, grâce à laquelle j'ai pu configurer l'interface utilisateur pour qu'elle réponde immédiatement aux changements intervenus dans les 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 tous les membres définis pour une classe, et rien de moins sous forme de chaîne.

Inconvénients du système qui pourraient vous décourager de vous embêter :l'ajout de tous les messages et valeurs-clés est extrêmement fastidieux ;c'est plus lent que sans aucune réflexion ;tu vas détester voir boost::static_pointer_cast et boost::dynamic_pointer_cast partout dans votre base de code avec une passion violente ;les limites du système fortement typé sont toujours là, vous les cachez juste 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 à savoir comment implémenter quelque chose comme ceci :utilisez simplement des pointeurs partagés et faibles vers une base commune (la mienne était appelée de manière très imaginative "Objet") et dérivez-les pour tous les types que vous souhaitez utiliser.Je recommanderais d'installer Boost.Function au lieu de le faire comme je l'ai fait, c'est-à-dire avec des conneries personnalisées et une tonne de macros laides pour envelopper les appels de pointeur de fonction.Puisque tout est cartographié, l’inspection des objets consiste simplement à parcourir toutes les clés.Étant donné que mes cours étaient essentiellement aussi proches que possible d'une arnaque directe de Cocoa en utilisant uniquement C++, si vous voulez quelque chose comme ça, je vous suggère d'utiliser la documentation de Cocoa comme modèle.

Les deux solutions de type réflexion que je connais depuis mes années en C++ sont :

1) Utilisez RTTI, qui vous fournira un bootstrap pour construire votre comportement de réflexion, si vous parvenez à faire dériver toutes vos classes d'une classe de base « objet ».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 en arrière-plan 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, consiste à utiliser le SDK DIA.Si je me souviens bien, cela vous permet d'ouvrir des pdbs, qui devraient contenir des métadonnées pour vos types C++.Cela pourrait suffire 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 cependant un peu laides !Il n’y a rien de tel qu’un peu de C++ pour vous faire apprécier le luxe du C#.

Bonne chance.

Il existe une autre nouvelle bibliothèque de réflexion en C++, appelée RTTR (Réflexion du type d'exécution, voir aussi github).

L'interface est similaire à la réflexion en C# et fonctionne sans aucun RTTI.

MODIFIER:Lien rompu mis à jour le 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++ :

Réflexe CERN.Cela semble fonctionner très bien.

La réflexion n'est pas prise en charge par C++ dès le départ.C’est triste car cela rend les tests défensifs pénibles.

Il existe plusieurs approches pour faire de la réflexion :

  1. utilisez les informations de débogage (non portables).
  2. Saupoudrez votre code de macros/modèles ou d'une autre approche source (ça a l'air moche)
  3. Modifiez un compilateur tel que clang/gcc pour produire une base de données.
  4. Utiliser l'approche Qt moc
  5. Boostez la réflexion
  6. Réflexion précise et plate

Le premier lien semble le plus prometteur (utilise des mods pour clang), le second traite d'un certain nombre de techniques, le troisième est une approche différente utilisant gcc :

  1. http://www.donw.org/rfl/

  2. https://bitbucket.org/dwilliamson/clreflect

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

Il existe désormais un groupe de travail pour la réflexion C++.Voir les actualités C++14 au CERN :

Modifier le 13/08/17 :Depuis le message original, il y a eu un certain nombre d'avancées potentielles dans la réflexion.Ce qui suit fournit plus de détails et une discussion sur les différentes techniques et statuts :

  1. La réflexion statique en bref
  2. Réflexion statique
  3. Une conception pour la réflexion statique

Cependant, cela ne semble pas prometteur en ce qui concerne une approche de réflexion standardisée en C++ dans un avenir proche, à moins que la communauté ne s'intéresse beaucoup plus au soutien à la réflexion en C++.

Ce qui suit détaille l'état actuel sur la base des commentaires de la dernière réunion des normes C++ :

Modifier 13/12/2017

La réflexion semble s'orienter vers C++ 20 ou plus probablement un TSR.Le mouvement est cependant lent.

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

Cette question est un peu ancienne maintenant (je ne sais pas pourquoi je continue à poser de vieilles questions aujourd'hui) mais j'y pensais BOOST_FUSION_ADAPT_STRUCT qui introduit la réflexion au moment de la compilation.

C'est à vous de mapper cela à la réflexion au moment de l'exécution bien sûr, et ce ne sera pas trop facile, mais c'est possible dans ce sens, alors que ce ne serait pas l'inverse :)

Je pense vraiment qu'une macro pour encapsuler le BOOST_FUSION_ADAPT_STRUCT on pourrait générer les méthodes nécessaires pour obtenir le comportement d’exécution.

Je pense que vous pourriez trouver intéressant l'article "Utilisation de modèles pour la réflexion en C++" de Dominic Filion.C'est à la section 1.4 de Gemmes de programmation de jeux 5.Malheureusement, je n'ai pas mon exemplaire avec moi, mais recherchez-le car je pense qu'il explique ce que vous demandez.

La réflexion porte essentiellement sur ce que le compilateur a décidé de laisser comme empreintes dans le code que le code d'exécution peut interroger.Le C++ est réputé pour ne pas payer pour ce que vous n’utilisez pas ;parce que la plupart des gens n'utilisent pas/ne veulent pas de réflexion, le compilateur C++ évite le coût en n'enregistrant pas rien.

Ainsi, 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 l'ont noté d'autres réponses.

Sous "autres techniques", si vous ne disposez pas d'un langage avec réflexion, obtenez un outil capable d'extraire les informations souhaitées au moment de la compilation.

Notre Boîte à outils de réingénierie logicielle DMS est une technologie de compilateur généralisée paramétrée par des définitions de langage explicites.Il contient des définitions de langage 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 sur la table des symboles.Ces informations sur la table de symboles incluent le type de données que vous souhaiterez probablement obtenir de la « 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.htmlIl prend en charge 2 manières :obtenir des informations de type à partir des informations de débogage et laisser le programmeur fournir ces informations.

Je me suis également intéressé à la réflexion pour mon projet et j'ai trouvé cette bibliothèque, je ne l'ai pas encore essayée, mais j'ai essayé d'autres outils de ce type et j'aime leur fonctionnement :-)

Consultez la description de la classe 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 qu'avec GCC) et ne nécessite pas d'annotation de code source (bien que certains pragmas existent pour gérer les situations délicates). ).Il est en développement depuis plus d’une décennie et utilisé dans un certain nombre de projets à l’échelle industrielle.

Quand j'ai voulu réfléchir en C++, j'ai lu Cet article et amélioré ce que j'ai vu là-bas.Désolé, pas de canette.Le résultat ne m'appartient pas... 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 Ensure_linearly pour rendre la définition des types réflectables beaucoup plus facile.En fait, je suis allé assez loin dans ce domaine, mais j'ai encore du chemin à parcourir.Les changements apportés à C++0x seront très probablement d’une grande aide dans ce domaine.

Il semble que C++ ne dispose toujours pas de cette fonctionnalité.Et C++11 réflexion reportée aussi ((

Recherchez des macros ou créez-en vos propres.Qt peut également aider à la réflexion (s'il peut être utilisé).

Essayez de regarder ce projet http://www.garret.ru/cppreflection/docs/reflect.htmlest ajouté des réflexions au C++.Il a ajouté des métadonnées aux classes que vous pouvez ensuite utiliser.

même si la réflexion n’est pas prise en charge directement en C++, elle n’est pas trop difficile à mettre en œuvre.J'ai rencontré cet excellent article :http://replicaisland.blogspot.co.il/2010/11/building-reflective-object-system-in-c.html

l'article explique en détail comment 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 aspérités à régler, mais pour mes besoins, c'était suffisant.

En fin de compte, la réflexion peut être payante si elle est effectuée correctement, et elle est tout à fait réalisable 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 celui de Qt et ajoute des méta-informations directement dans les fichiers objets.On prétend qu'il est facile à utiliser.Aucune dépendance externe.Il vous permet même de refléter automatiquement std::string puis de l'utiliser dans des scripts.Regardez s'il vous plaît JE NE SAIS PAS

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, décrite plus tard (UPDATE) :

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 proche 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 tu peux é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)

Ainsi, en ayant la macro ENUMERATE_MEMBERS dans la définition de structure, vous pouvez créer une sérialisation, une comparaison, un 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 ne devrait avoir aucune surcharge pour la création/destruction de structure au moment de l'exécution, et le code de T.EnumerateWith() doit être généré à la demande, ce qui peut être obtenu en en faisant une fonction de modèle en ligne, donc la seule surcharge dans toute l'histoire consiste à ajouter ENUMERATE_MEMBERS(m1,m2,m3...) à chaque structure, tandis que l'implémentation d'une méthode spécifique par type de membre est indispensable dans toute solution, donc je ne suppose pas que cela soit une surcharge.

MISE À JOUR:Il existe une implémentation très simple de la macro ENUMERATE_MEMBERS (cependant, elle pourrait être un peu étendue pour prendre en charge l'héritage d'une structure énumérable)

#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 besoin d'aucune bibliothèque tierce pour ces 15 lignes de code ;)

Avec C++20, vous avez déclarations d'expansion, qui permet d'itérer sur les types d'agrégats :

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

Si vous recherchez une réflexion C++ relativement simple, j'ai collecté des macros/définitions à partir de diverses sources et je les ai commentées sur leur fonctionnement.Vous pouvez télécharger des fichiers d'en-tête à partir d'ici:

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

ensemble de définitions, plus des fonctionnalités en plus :

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

L'exemple d'application réside également dans le référentiel git, ici :https://github.com/tapika/TestCppReflect/

Je vais le copier en partie ici avec explication :

#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 définir utilise le nom de la classe + le nom du champ avec offsetof - identifier à quel endroit de la mémoire se trouve un champ particulier.J'ai essayé de reprendre la terminologie .NET autant que possible, mais C++ et C# sont différents, donc ce n'est pas 1 pour 1.Le modèle de réflexion C++ entier réside dans TypeInfo et FieldInfo Des classes.

J'ai utilisé l'analyseur Pugi XML pour récupérer le code de démonstration en XML et le restaurer à partir de XML.

La sortie produite par le code de démonstration ressemble donc à 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 la prise en charge de toute classe/structure tierce 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 l'exemple de code dans

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

Cette solution est applicable pour Windows / Visual studio.Il est possible de le porter sur d'autres systèmes d'exploitation/compilateurs, mais je ne l'ai pas fait.(Demandez-moi si vous aimez vraiment la solution, je pourrai peut-être vous aider)

Cette solution est applicable à la sérialisation unique d'une classe avec plusieurs sous-classes.

Si vous recherchez toutefois un mécanisme pour sérialiser les parties de classe ou même pour contrôler les fonctionnalités produites par les appels de réflexion, vous pouvez consulter la solution suivante :

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

Des informations plus détaillées peuvent être trouvées dans la vidéo YouTube :

Réflexion de type d'exécution C++https://youtu.be/TN8tJijkeFE

J'essaie d'expliquer un peu plus en détail comment fonctionnera la réflexion C++.

Un exemple de code ressemblera par exemple à ceci :

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;

Mais chaque étape ici se traduit en fait par un appel de fonction utilisant des propriétés C ++ avec __declspec(property(get =, put ... ).

qui reçoit des informations complètes sur les types de données C++, les noms de propriétés C++ et les pointeurs d'instance de classe, sous forme de chemin, et sur la base de ces informations, vous pouvez générer du XML, du JSON ou même sérialiser celui-ci sur Internet.

Des exemples de telles fonctions de rappel virtuel peuvent être trouvés ici :

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

Voir les fonctions ReflectCopy, et fonction virtuelle ::OnAfterSetProperty.

Mais comme le sujet est vraiment avancé, je vous recommande de vérifier d'abord la vidéo.

Si vous avez des idées d'amélioration, n'hésitez pas à me contacter.

Le projet Root Reflex prend en charge cela.

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

Si vous déclarez un pointeur vers une fonction comme ceci :

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

Vous pouvez attribuer une place en mémoire à cette fonction comme ceci (nécessite libdl et 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);
}

Pour charger un symbole local en utilisant l'indirection, vous pouvez utiliser dlopen sur le binaire appelant (argv[0]).

La seule exigence pour cela (autre que dlopen(), libdl, et dlfcn.h) consiste à connaître les arguments et le type de la fonction.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top