Comment ajouter de la réflexion à une application C ++?

263

J'aimerais pouvoir introspecter une classe C ++ pour son nom, son contenu (c'est-à-dire les membres et leurs types) etc. Je parle ici de C ++ natif, 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 autres techniques) pourraient fournir ces informations?

pseudo
la source
18
Heureusement, vous ne pouvez pas le faire sans macros et autres prétraitements, car les métadonnées requises n'existent que si vous les créez manuellement via une magie de prétraitement des macros.
jalf
6
Les informations que vous pouvez obtenir de RTTI ne sont pas suffisantes pour faire la plupart des choses pour lesquelles vous souhaitez réellement réfléchir. Vous ne pouvez pas parcourir les fonctions membres d'une classe par exemple.
Joseph Garvin

Réponses:

259

Ce que vous devez faire, c'est que le préprocesseur génère 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 faciliter l'écriture dans le préprocesseur, nous utiliserons l'expression typée. Une expression typée n'est qu'une expression qui met le type entre parenthèses. Donc au lieu d'écrire, int xvous écrirez (int) x. Voici quelques macros pratiques pour vous 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 REFLECTABLEmacro 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)); \
    } \
}; \

Cela génère une constante fields_nqui est le nombre de champs reflétables dans la classe. Ensuite, il spécialise le field_datapour chaque domaine. Il aime aussi la reflectorclasse, 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 de champ à cet index. Il transmet ensuite 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 avons mis tout cela ensemble. Voici comment définir une Personclasse reflétable:

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

Voici une print_fieldsfonction généralisée 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 print_fieldsde la Personclasse réflectable :

int main()
{
    Person p("Tom", 82);
    print_fields(p);
    return 0;
}

Quelles sorties:

name=Tom
age=82

Et voila, nous venons d'implémenter la réflexion en C ++, en moins de 100 lignes de code.

Paul Fultz II
la source
106
Félicitations pour avoir montré comment mettre en œuvre la réflexion, plutôt que de dire que cela ne peut pas être fait. Ce sont des réponses comme celle-ci qui font de SO une excellente ressource.
fearless_fool
4
Notez que si vous essayez de compiler cela sous Visual Studio, vous obtiendrez une erreur car VS ne gère pas correctement l'expansion des macros variadiques. Pour VS, essayez d'ajouter: #define DETAIL_TYPEOF_INT2(tuple) DETAIL_TYPEOF_HEAD tupleet de #define DETAIL_TYPEOF_INT(...) DETAIL_TYPEOF_INT2((__VA_ARGS__)) changer la définition de TYPEOF (x) en:#define TYPEOF(x) DETAIL_TYPEOF_INT(DETAIL_TYPEOF_PROBE x,)
Phenglei Kai
J'obtiens l'erreur «BOOST_PP_IIF_0» ne nomme pas un type. Peux-tu aider s'il te plait.
Ankit Zalani
3
Voir ma propre réponse - stackoverflow.com/a/28399807/2338477 J'ai extrait et reconditionné toutes les définitions, et la bibliothèque de boost n'est pas nécessaire. En tant que code de démonstration, je fournis la sérialisation au format xml et la restauration à partir du xml.
TarmoPikaro
106

Il existe deux types de reflectionbaignade.

  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. Utilisez boost::type_traitspour 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 SFINAE ordinaire .

Si vous cherchez plutôt des moyens d'accomplir 1), comme regarder combien de méthodes possède une classe, ou comme 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 C ++ standard de le faire. Vous devez utiliser soit

  • Un Meta Compiler comme le Qt Meta Object Compiler qui traduit votre code en ajoutant des méta informations supplémentaires.
  • Un Framework constitué de macros vous 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 conçu pour la vitesse. 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.

Johannes Schaub - litb
la source
122
Le C ++ est conçu pour la vitesse, mais la philosophie n'est pas "aussi rapide que possible", mais plutôt "vous ne payez pas si vous ne l'utilisez pas". Je crois qu'il est possible pour un langage d'implémenter l'introspection d'une manière qui correspond à cette philosophie, le C ++ en manque.
Joseph Garvin
8
@Joseph: Comment cela devrait-il être fait? Il faudrait que toutes ces métadonnées soient stockées. Ce qui signifie que vous devez payer pour cela, même si vous ne l'utilisez pas. (À moins que vous ne puissiez marquer les types individuels comme "supportant la réflexion", mais nous sommes presque là où nous pourrions aussi bien utiliser la macro
astuce
25
@jalf: seules les métadonnées qui pourraient être nécessaires. Si nous considérons uniquement la réflexion au moment de la compilation, c'est trivial. Par exemple, une fonction de compilation members<T>qui renvoie une liste de tous les membres de T. Si nous voulions avoir une réflexion à l'exécution (c'est-à-dire RTTI mélangé à la réflexion), le compilateur connaîtrait toujours tous les types de base réfléchis. Il est très probable members<T>(T&)qu'il ne serait jamais instancié pour T = std :: string, donc le RTTI pour std :: string ou ses classes dérivées n'a pas besoin d'être inclus.
MSalters
9
La bibliothèque reflex (mentionnée ci-dessous) ajoute de la réflexion au C ++ sans ralentir le code existant sur: root.cern.ch/drupal/content/reflex
Joseph Lisee
6
@Joe: la réflexion ne ralentit jamais le code existant. Cela agrandit simplement le contenu livré (car vous devez fournir une base de données d'informations de type ...).
mmmmmmmm
56

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

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

Brad Wilson
la source
1
J'appuie Brad. Les modèles C ++ peuvent être plutôt puissants, et il existe une vaste expérience autour de divers comportements de type `` réflexion '', tels que le renforcement de `` n'importe quelle '' bibliothèque, des traits de type, C ++ RTTI, etc., qui peuvent résoudre de nombreux problèmes pour lesquels la réflexion est résolue. Alors Nick, quel est ton objectif ici?
Aaron
7
Upvote pour la remarque des poneys! Je voterais deux fois, car votre réponse le mérite aussi, mais malheureusement je n'en reçois qu'un, donc les poneys gagnent. :-)
Franci Penov
6
Je ne comprends pas vraiment pourquoi c'est une réponse intelligente. J'ai déjà dit que j'aimerais des références aux bibliothèques, etc. pour implémenter cela. La réflexion / introspection est pour différents systèmes pour permettre l'accès au script, la sérialisation, etc.
Nick
3
@ Nick: Il a déjà répondu à cela. Cela ne peut pas être fait, les données n'existent pas et, par conséquent, aucune bibliothèque n'est en mesure de les implémenter pour vous.
jalf
@jalf C'est toujours étrange pour moi de lire des gens dans le monde de la programmation qui disent "ce n'est pas possible" et non "je ne sais pas comment". Bien sûr, les métadonnées n'existent pas mais peuvent être insérées avec des macros
Freddx L.
38

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

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

Cela oblige le compilateur à 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 ma société, 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 permet 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 choses comme const-safety et ainsi de suite, de sorte que ces aspects sont implémentés en tant que contrôles 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 l'avons trouvé très utile pour avoir des interfaces GUI automatiques pour les classes, créer des propriétés en C ++, diffuser vers et à partir de XML et ainsi de suite, et il n'est pas nécessaire de dériver d'une classe de base spécifique. Si la demande est suffisante, nous pourrions peut-être la mettre en forme pour la sortie.

Roderick
la source
1
Je pense que vous voulez dire __declspec(dllexport)et vous pouvez récupérer les informations à partir d'un fichier .map si vous activez la création de telles lors de la construction.
Orwellophile
19

La réflexion n'est pas prise en charge par C ++ hors de la boîte. C'est triste car cela rend les tests défensifs pénibles.

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

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

Le premier lien semble le plus prometteur (utilise des mods pour cliqueter), le second discute 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 maintenant un groupe de travail pour la réflexion C ++. Voir l'actualité du C ++ 14 @ CERN:

Modifier le 13/08/17:

Depuis le message d'origine, 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 statuts:

  1. Réflexion statique en bref
  2. Réflexion statique
  3. Un design pour la réflexion statique

Cependant, cela ne semble pas prometteur sur une approche de réflexions standardisées en C ++ dans un avenir proche, à 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 le 13/12/2017

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

Modifier 15/09/2018

Un projet de ST a été envoyé aux organismes nationaux pour vote.

Le texte peut être trouvé ici: https://github.com/cplusplus/reflection-ts

Modifier le 11/07/2019

La réflexion TS est une fonctionnalité complète et est disponible pour commentaires et vote au cours de l'été (2019).

L'approche de programmation des méta-modèles doit être remplacée par une approche de code temporel de compilation plus simple (non reflétée dans le TS).

Modifier 10/02/2020

Il y a une demande pour prendre en charge la réflexion TS dans Visual Studio ici:

Conférence sur le TS de l'auteur David Sankel:

Modifier 17 mars 2020

Des progrès dans la réflexion sont en cours. Un rapport du '2020-02 Prague ISO C ++ Committee Trip Report' peut être trouvé ici:

Des détails sur ce qui est envisagé pour C ++ 23 peuvent être trouvés ici (comprend une courte section sur la réflexion):

Edit 4 juin 2020

Un nouveau cadre a été publié par Jeff Preshing appelé «Plywood» qui contient un mécanisme de réflexion à l'exécution. Plus de détails ici:

Les outils et l'approche semblent être les plus raffinés et les plus faciles à utiliser jusqu'à présent.

Damian Dixon
la source
1
Le lien cern est rompu.
Mostowski Réduire
les liens cern devraient être corrigés maintenant. Ils ont tendance à se casser assez fréquemment, ce qui est douloureux.
Damian Dixon
Cette réponse concerne-t-elle uniquement la réflexion au moment de la compilation?
einpoklum
@einpoklum les seules solutions actuelles de réflexion sont le temps de compilation, généralement avec du code méta-modèle ou des macro. Le dernier projet de TS semble devoir fonctionner pour l'exécution, mais vous devrez avoir créé toutes les bibliothèques avec le bon compilateur pour que les métadonnées nécessaires soient stockées.
Damian Dixon
@DamianDixon: Ce n'est pas vrai. Il existe plusieurs bibliothèques de réflexion à l'exécution. Maintenant, d'accord, ils sont plutôt maladroits et sont soit opt-in soit nécessitent des nodifications du compilateur, mais ils existent toujours. Si, si je comprends bien votre commentaire, vous n'avez fait référence qu'à la réflexion au moment de la compilation, veuillez modifier votre réponse pour la rendre plus claire.
einpoklum
15

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 une fois pouvoir configurer de manière flexible ce qu'une sortie de simulation produirait. Il a fallu ajouter du code passe-partout 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 BuildMap()méthode pour déterminer les méthodes 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 est traduit en une série d'appels de méthode au moment de l'exécution (lorsque le fichier de configuration est lu), il est donc assez efficace. Je ne recommanderais pas cela sauf si vous en avez vraiment besoin, mais quand vous le faites, vous pouvez faire des trucs vraiment cool.

KeithB
la source
dois aimer ces fonctions qui reviennent toujours vrai;) Je suppose que cela est à l'abri des problèmes de commande d'init statique?
paulm
14

Je recommanderais d'utiliser Qt .

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

Jérôme
la source
1
J'ai regardé cela, mais il utilise des macros et le code source a besoin d'une analyse pour générer le code de métadonnées. Je voudrais éviter cette étape supplémentaire. Je préfère utiliser une bibliothèque C ++ ou des macros simples. Merci pour l'idée.
Nick
10
QT, ou une autre bibliothèque implémentant une approche similaire est le meilleur que vous obtiendrez
jalf
5
Payez au moment de la compilation ou payez au moment de l'exécution - de toute façon vous payez!
Martin Beckett
13

Qu'essayez-vous de faire avec la réflexion?
Vous pouvez utiliser les traits de type Boost et les bibliothèques typeof comme une 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.

Ferruccio
la source
13

EDIT : CAMP n'est plus maintenu; deux fourches sont disponibles:

  • L'un est également appelé CAMP et est basé sur la même API.
  • La méditation est une réécriture partielle et doit être préférée car elle ne nécessite pas de boost; il utilise C ++ 11.

CAMP est une bibliothèque sous licence MIT (anciennement LGPL) qui ajoute de la réflexion au langage C ++. Il 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 un fork utilisant C ++ 11 qui ne nécessite plus Boost .

philant
la source
11

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 lié à la maintenance n'en vaut peut-être pas la peine. Mon système a été utilisé pour maintenir les classes d'interface utilisateur complètement séparées de la logique métier par le biais d'une 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 des énumérations si vous préférez la gestion des erreurs de vitesse et de compilation plutôt que la flexibilité totale) pour faire fonctionner des pointeurs (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 avez une classe de base commune capable de représenter n'importe quelle valeur. L'ensemble du système était une arnaque sans faille du codage et de la délégation des valeurs-clés, avec quelques effets secondaires qui valaient peut-être le temps nécessaire pour que chaque classe utilisant le système associe toutes ses méthodes et ses membres à des appels légaux. : 1) Toute 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) Les getters et setters des variables membres étaient faciles à rendre thread-safe car la modification ou l'accès à leurs valeurs se faisait toujours par 2 méthodes dans la classe de base de tous les objets. L'ensemble du système était une arnaque sans faille du codage et de la délégation des valeurs-clés, avec quelques effets secondaires qui valaient peut-être le temps nécessaire pour que chaque classe utilisant le système associe toutes ses méthodes et ses membres à des appels légaux. : 1) Toute 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) Les getters et setters des variables membres étaient faciles à rendre thread-safe car la modification ou l'accès à leurs valeurs se faisait toujours par 2 méthodes dans la classe de base de tous les objets. L'ensemble du système était une arnaque sans faille du codage et de la délégation des valeurs-clés, avec quelques effets secondaires qui valaient peut-être le temps nécessaire pour que chaque classe utilisant le système associe toutes ses méthodes et ses membres à des appels légaux. : 1) Toute 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) Les getters et setters des variables membres étaient faciles à rendre thread-safe car la modification ou l'accès à leurs valeurs se faisait toujours par 2 méthodes dans la classe de base de tous les objets. 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) Les getters et setters des variables membres étaient faciles à rendre thread-safe car la modification ou l'accès à leurs valeurs se faisait toujours par 2 méthodes dans la classe de base de tous les objets. 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) Les getters et setters des variables membres étaient faciles à rendre thread-safe car la modification ou l'accès à leurs valeurs se faisait toujours par 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 sont pas faciles en C ++. Par exemple, je pourrais créer un objet Array qui contenait 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 à la carte en Lisp). Un autre a été l'implémentation de l'observation des valeurs-clés, grâce à laquelle j'ai pu configurer l'interface utilisateur pour répondre immédiatement aux changements 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 les membres définis pour une classe, et sous forme de chaîne non moins.

Inconvénients du système qui pourraient vous décourager de déranger: l'ajout de tous les messages et valeurs-clés est extrêmement fastidieux; c'est plus lent que sans aucune réflexion; vous allez détester voir boost::static_pointer_castet boost::dynamic_pointer_castpartout 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 cordes ne sont pas non plus une surprise amusante ou facile à découvrir.

Quant à la façon d'implémenter quelque chose comme ceci: utilisez simplement des pointeurs partagés et faibles vers une base commune (la mienne était très imaginativement appelée "Object") et dérivez pour tous les types que vous souhaitez utiliser. Je recommanderais d'installer Boost.Function au lieu de le faire comme je l'ai fait, ce qui était avec de la merde personnalisée et une tonne de macros laides pour encapsuler les appels du pointeur de fonction. Étant donné que tout est mappé, l'inspection des objets consiste simplement à parcourir toutes les clés. Étant donné que 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 vous suggère d'utiliser la documentation Cocoa comme modèle.

Michel
la source
Hé, @Michael; avez-vous toujours le code source pour cela, ou vous en êtes-vous débarrassé? J'aimerais y jeter un œil si cela ne vous dérange pas.
RandomDSdevel
Oups, mal orthographié votre nom! Pas étonnant que je n'ai jamais eu de réponse…
RandomDSdevel
10

Il existe une autre nouvelle bibliothèque de réflexion en C ++, appelée RTTR (Run Time Type Reflection, voir aussi github ).

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

Zack
la source
8

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

1) Utilisez RTTI, qui vous fournira un bootstrap pour construire votre comportement de réflexion, si vous êtes en mesure de faire dériver toutes vos classes à partir d'une classe de base «objet». Cette classe pourrait fournir des méthodes telles que 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 pdbs, qui devraient contenir des métadonnées pour vos types C ++. Il suffira peut-être de faire ce dont vous avez besoin. Cette page montre par exemple comment obtenir tous les types de base d'une classe.

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.

user4385
la source
C'est astucieux et un hack géant, avec le truc DIA SDK que vous avez suggéré là-bas.
2012
7

EDIT: lien cassé mis à jour au 7 février 2017.

Je pense que personne n'a mentionné cela:

Au CERN, ils utilisent un système de réflexion complet pour C ++:

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

Germán Diago
la source
@ j4nbur53 Le lien est rompu car il semble qu'ils aient atteint un jalon: root.cern.ch
Germán Diago
Serait-ce que vous voulez dire ce lien root.cern.ch/root/doc/ROOTUsersGuideHTML/ch07.html Chapter Reflex?
Mostowski Réduire
Essayez ce root.cern.ch/how/how-use-reflex . Reflex fonctionne comme un générateur qui analyse vos fichiers d'en-tête et génère du code / bibliothèque d'introspection c ++, que vous pouvez lier et utiliser une simple API.
Adam Ryczkowski
6

Cette question est un peu ancienne maintenant (je ne sais pas pourquoi je continue de répondre aux anciennes questions aujourd'hui) mais je pensais à BOOST_FUSION_ADAPT_STRUCT qui introduit la réflexion à la compilation.

C'est à vous de mapper cela à la réflexion à l'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 BOOST_FUSION_ADAPT_STRUCTcelle pourrait générer les méthodes nécessaires pour obtenir le comportement d'exécution.

Matthieu M.
la source
2
par minghua (qui a initialement édité le post): J'ai creusé dans cette solution BOOST_FUSION_ADAPT_STRUCT et j'ai finalement trouvé un exemple. Voir cette nouvelle question SO - C ++ itérer dans le champ struct imbriqué avec boost fusion adapt_struct .
Matthieu M.
Génial, Matthieu! Je viens de réaliser avoir vu vos indices ici et là au cours de la dernière année. Je n'ai pas remarqué qu'ils sont liés jusqu'à présent. C'était très inspirant.
minghua
6

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 dans la section 1.4 de Game Programming Gems 5 . Malheureusement, je n'ai pas ma copie avec moi, mais cherchez-la parce que je pense qu'elle explique ce que vous demandez.

Luis
la source
4

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é d'en créer une car je n'en trouvais pas qui cocherait 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 .

pseudo
la source
4

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. 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 / ne veulent pas de réflexion, le compilateur C ++ évite le coût en n'enregistrant 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 n'avez pas de langage avec réflexion, procurez-vous un outil qui peut extraire les informations que vous souhaitez au moment de la compilation.

Notre DMS Software Reengineering Toolkit est une technologie de compilation généralisée paramétrée par des définitions de langage explicites. Il a des définitions de 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 la table des symboles. Ces informations de table de symboles incluent le type de données que vous voudrez probablement de la "réflexion". Si votre objectif est d'énumérer un ensemble de champs ou de méthodes et de 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.

Ira Baxter
la source
3

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 le programmeur fournir ces informations.

Je me suis également intéressé à la réflexion sur 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 :-)

alariq
la source
3

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

Russell Standish
la source
1
Bienvenue dans Stack Overflow. Bien que cette réponse soit sur le sujet, il est important de souligner que vous êtes l'auteur de ce logiciel, pour clarifier ce n'est pas une recommandation impartiale :-)
Matthew Strawbridge
2

Lorsque j'ai voulu réfléchir en C ++, j'ai lu cet article et amélioré ce que j'y ai vu. Désolé, non. Je ne suis pas propriétaire du 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 à utiliser inherit_linearly pour rendre la définition des types reflétables beaucoup plus facile. Je suis allé assez loin en fait, mais j'ai encore du chemin à faire. Les changements dans C ++ 0x sont très susceptibles d'être d'une grande aide dans ce domaine.

Edward Strange
la source
2

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

Recherchez des macros ou créez les vôtres. Qt peut également aider à la réflexion (si elle peut être utilisée).

Bohdan
la source
2

même si la réflexion n'est pas prise en charge dès le départ 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 mettre en œuvre un système de réflexion assez simple et rudimentaire. accordé sa solution pas la plus saine, et il y a des bords rugueux à régler mais pour mes besoins c'était suffisant.

la ligne de fond - la réflexion peut être payante si elle est effectuée correctement, et elle est complètement réalisable en c ++.

Naore Azenkut
la source
2

Je voudrais annoncer l'existence de la boîte à outils automatique d'introspection / réflexion "IDK". Il utilise un méta-compilateur comme Qt et ajoute des méta-informations directement dans les fichiers objets. Il serait 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

Eugene G
la source
2

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

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

ensemble de définitions, plus des fonctionnalités par-dessus:

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

}

REFLECTABLEdefine utilise le nom de la classe + le nom du champ avec offsetof- pour identifier à quel endroit de la mémoire se trouve un champ particulier. J'ai essayé de prendre la terminologie .NET pour la mesure du possible, mais C ++ et C # sont différentes, il est donc pas 1 à 1. modèle de réflexion ++ entier C réside dans TypeInfoet FieldInfoclasses.

J'ai utilisé l'analyseur pugi xml pour récupérer le code de démonstration dans xml et le restaurer à partir de xml.

Ainsi, 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 toute prise en charge de classe / structure tierce via la classe TypeTraits et la spécification partielle de modèle - pour définir votre propre classe TypeTraitsT, de manière similaire à 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 OS / compilateurs, mais ce n'est pas le cas. (Demandez-moi si vous aimez vraiment la solution, je pourrais peut-être vous aider)

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

Cependant, si vous recherchez un mécanisme pour sérialiser des parties de classe ou même pour contrôler les fonctionnalités que les appels de réflexion produisent, vous pouvez jeter un œil à la solution suivante:

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

Des informations plus détaillées peuvent être trouvées sur la vidéo youtube:

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

J'essaie d'expliquer un peu plus en détail le fonctionnement de 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 aboutit en fait à un appel de fonction à l'aide 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é C ++ et les pointeurs d'instance de classe, sous forme de chemin d'accès, et sur la base de ces informations, vous pouvez générer xml, json ou même sérialiser celui-ci sur Internet.

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

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

Voir les fonctions ReflectCopyet la fonction virtuelle::OnAfterSetProperty .

Mais puisque le sujet est vraiment avancé - je recommande de vérifier d'abord la vidéo.

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

TarmoPikaro
la source
2

La bibliothèque Random Access Reflection permet une réflexion assez facile et intuitive - toutes les informations de champ / type sont conçues pour être disponibles dans des tableaux ou pour se sentir comme un accès au tableau. Il est écrit pour C ++ 17 et fonctionne avec Visual Studios, g ++ et Clang. La bibliothèque est uniquement en-tête, ce qui signifie que vous n'avez qu'à copier "Reflect.h" dans votre projet pour l'utiliser.

Les structures ou classes réfléchies ont besoin de la macro REFLECT, où vous fournissez le nom de la classe que vous réfléchissez et les noms des champs.

class FuelTank {
    public:
        float capacity;
        float currentLevel;
        float tickMarks[2];

    REFLECT(() FuelTank, () capacity, () currentLevel, () tickMarks)
};

C'est tout ce qu'il y a, aucun code supplémentaire n'est nécessaire pour configurer la réflexion. Vous pouvez éventuellement fournir des superclasses (dans la parenthèse du premier argument) et des annotations de champ (dans la parenthèse précédant le champ que vous souhaitez annoter) pour pouvoir parcourir les superclasses ou ajouter des informations de compilation supplémentaires à un champ (comme Json: :Ignorer).

La boucle dans les champs peut être aussi simple que ...

for ( size_t i=0; i<FuelTank::Class::TotalFields; i++ )
    std::cout << FuelTank::Class::Fields[i].name << std::endl;

Vous pouvez parcourir une instance d'objet pour accéder aux valeurs de champ (que vous pouvez lire ou modifier) ​​et aux informations de type de champ ...

FuelTank::Class::ForEachField(fuelTank, [&](auto & field, auto & value) {
    using Type = typename std::remove_reference<decltype(value)>::type;
    std::cout << TypeToStr<Type>() << " " << field.name << ": " << value << std::endl;
});

Une bibliothèque JSON est construite au-dessus de RandomAccessReflection qui identifie automatiquement les représentations de sortie JSON appropriées pour la lecture ou l'écriture, et peut parcourir récursivement tous les champs réfléchis, ainsi que les tableaux et les conteneurs STL.

struct MyOtherObject { int myOtherInt; REFLECT(() MyOtherObject, () myOtherInt) };
struct MyObject
{
    int myInt;
    std::string myString;
    MyOtherObject myOtherObject;
    std::vector<int> myIntCollection;

    REFLECT(() MyObject, () myInt, () myString, (Reflected) myOtherObject, () myIntCollection)
};

int main()
{
    MyObject myObject = {};
    std::cout << "Enter MyObject:" << std::endl;
    std::cin >> Json::in(myObject);
    std::cout << std::endl << std::endl << "You entered:" << std::endl;
    std::cout << Json::pretty(myObject);
}

Ce qui précède pourrait être exécuté comme ça ...

Enter MyObject:
{
  "myInt": 1337, "myString": "stringy", "myIntCollection": [2,4,6],
  "myOtherObject": {
    "myOtherInt": 9001
  }
}


You entered:
{
  "myInt": 1337,
  "myString": "stringy",
  "myOtherObject": {
    "myOtherInt": 9001
  },
  "myIntCollection": [ 2, 4, 6 ]
}

Voir également...

jjf28
la source
1

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, comparer). 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 (UPDATE):

Supposons que nous ayons défini une 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
}

Vous pouvez maintenant é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, avec la macro ENUMERATE_MEMBERS dans la définition de la structure, vous pouvez créer la sérialisation, comparer, hacher 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 doit avoir zéro surcharge pour structurer la création / destruction 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 faisant fonctionner en tant que modèle en ligne, de sorte que la seule surcharge dans toute l'histoire consiste à 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 toute solution, donc je ne le suppose pas comme une surcharge.

MISE À JOUR: Il existe une implémentation très simple de la macro ENUMERATE_MEMBERS (mais elle pourrait être un peu étendue pour prendre en charge l'héritage de la 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;)

jenkas
la source
1

Vous pouvez obtenir des fonctionnalités de réflexion statique intéressantes pour les structures avec BOOST_HANA_DEFINE_STRUCT à partir de la bibliothèque Boost :: Hana.
Hana est assez polyvalent, non seulement pour le cas d'utilisation que vous avez en tête, mais pour beaucoup de métaprogrammation de modèles.

nnolte
la source
0

Si vous déclarez un pointeur sur une fonction comme celle-ci:

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

Vous pouvez attribuer une place en mémoire à cette fonction comme ceci (nécessite libdlet 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 dlopensur le binaire appelant ( argv[0]).

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

SS Anne
la source