331 votes

Comment puis-je ajouter une réflexion à une application C ++?

J'aimerais pouvoir introspecter une classe C ++ pour son nom, son contenu (c.-à-d. Les membres et leurs types), etc. Je parle ici du C ++ natif, pas du C ++ géré, qui a une réflexion. Je réalise que C ++ fournit des informations limitées en utilisant RTTI. Quelles autres bibliothèques (ou autres techniques) pourraient fournir cette information?

334voto

Paul Points 4552

Ce que vous devez faire est d'avoir le préprocesseur générer des données de réflexion sur le 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 de l'écrire dans le préprocesseur, nous allons utiliser tapé expression. Typé expression est juste une expression qui met le type entre parenthèses. Donc au lieu d'écrire int x vous écrirez (int) x. Voici quelques macros rapides pour aider à tapé expressions:

#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 relatives à chaque domaine(ainsi que le champ lui-même). Cette macro sera appelée comme ceci:

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

Donc, à l'aide de coup de pouce.PP nous itérer sur chaque argument et de générer 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 qu'il fait est de générer une constante fields_n c'est le nombre de reflectable champs de la classe. Puis il se spécialise l' field_data pour chaque champ. Il a aussi des amis de l' reflector classe, c'est de sorte qu'il peut accéder aux champs, même quand 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 itérer sur les champs, nous utilisons le modèle visiteur. Nous avons créer une MPL comprise entre 0 et le nombre de champs, et d'accéder aux données de terrain à l'index. Elle transmet ensuite les données de terrain sur l'utilisateur fourni par visiteur:

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

Voici maintenant le moment de vérité nous mettre tout cela ensemble. Voici comment on peut définir une Person classe qui est reflectable:

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

Voici une généralisé print_fields de la fonction en 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 de l' print_fields avec le reflectable Person classe:

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

Sorties:

name=Tom
age=82

Et voila, nous venons de mise en œuvre de réflexion en C++, en moins de 100 lignes de code.

113voto

Il existe deux types d' reflection autour de la piscine.

  1. L'Inspection par l'itération sur les membres d'un type, l'énumération de ses méthodes, et ainsi de suite.

    Ce n'est pas possible en C++.
  2. Inspection en vérifiant si une classe de type (class, struct, union) a une méthode ou d'un type imbriqué, est dérivé d'un autre type particulier.

    Ce genre de chose est possible avec C++ à l'aide de template-tricks. Utiliser boost::type_traits pour beaucoup de choses (comme vérifier si un type fait partie intégrante). Pour la vérification de l'existence d'une fonction membre, utilisez http://stackoverflow.com/questions/257288/possible-for-c-template-to-check-for-a-functions-existence#264088 . Pour vérifier si un certain type imbriqué existe, utilisez la plaine SFINAE .

Si vous êtes plutôt à la recherche de moyens pour accomplir 1), comme le fait de regarder la façon dont de nombreuses méthodes d'une classe a, ou comme obtenir la représentation de chaîne d'un id de classe, alors j'ai peur qu'il n'existe pas de Norme C++ moyen de le faire. Vous devez utiliser

  • Une Méta Compilateur comme l'intervalle Qt Compilateur de Méta-Objet qui se traduit par votre code d'ajouter des méta-informations.
  • Un Cadre constisting de macros qui vous permettent d'ajouter la méta-informations. Vous devez dire le cadre de toutes les méthodes, les noms de classe de base des classes et tout ce qu'il faut.

C++ est faite avec de la vitesse à l'esprit. Si vous voulez de haut niveau de l'inspection, comme C# ou en Java, alors je crains que je dois vous dire, il n'est pas sans un certain effort.

59voto

Brad Wilson Points 22910

Et j'aimerais 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 à laquelle vous pensez - les métadonnées entièrement descriptives disponibles au moment de l'exécution - n'existe pas pour C ++ par défaut.

42voto

Roderick Points 425

Les informations existent, mais pas dans le format dont vous avez besoin, et uniquement si vous avez l'exportation de vos classes. Cela fonctionne sous Windows, je ne sais pas sur les autres plates-formes. À l'aide de la catégorie de stockage des prescripteurs, comme par exemple:

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

Cela rend le compilateur construire la définition de la classe de données dans le 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 permet de réfléchir à une classe sans insertion de macros supplémentaires etc. dans la classe elle-même. Il permet des fonctions d'être appelé comme suit:

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

Effectivement cela n':

instance_ptr->Foo(1.331);

Le Invoke(this_pointer,...) de la fonction de la variable d'arguments. De toute évidence, par l'appel d'une fonction dans cette façon, vous êtes en contournant les choses comme const-sécurité et ainsi de suite, de sorte que ces aspects sont mis en œuvre comme l'exécution des contrôles.

Je suis sûr que la syntaxe pourrait être améliorée, et il ne fonctionne que sur Win32 et Win64 jusqu'à présent. Nous avons trouvé vraiment utile pour avoir automatique d'interfaces GUI de classes, la création de propriétés en C++, en streaming et à partir de XML et ainsi de suite, et il n'y a pas besoin de dériver d'une classe de base. Si il y a assez de demande, peut-être qu'on pourrait la heurter en forme pour la libération.

38voto

Konrad Rudolph Points 231505
<blockquote> <p>RTTI n’existe pas pour C++.</p> </blockquote> <p>C’est tout simplement faux. En fait, le terme « RTTI » a été inventé par la norme C++. En revanche, RTTI ne va pas très loin dans la mise en œuvre de la réflexion.</p>

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X