2 votes

Utilisation de modèles C++ avec des structures C pour l'introspection ?

Je travaille en C++ pour une entreprise dont tout le reste est écrit en C (l'utilisation du C n'est pas une option pour moi :( ). Ils ont un certain nombre de structures de données qui sont TRÈS similaires (c'est-à-dire qu'ils ont tous des champs tels que "nom", "adresse", etc. Mais, pour une raison quelconque, il n'y a pas de structure commune sur laquelle ils se basent pour tout le reste (ce qui rend les choses infernales). Quoi qu'il en soit, j'ai besoin de faire une analyse de l'ensemble du système de ces structures qui sont en mémoire, et de tout regrouper dans une table. Pas trop mal, mais la table doit inclure des entrées pour tous les champs de toutes les variables, même si elles n'ont pas le champ (la struct b peut avoir le champ "latency", mais pas la struct a - dans la table, l'entrée pour chaque instance de a doit avoir une entrée vide pour "latency".

Ma question est donc la suivante : existe-t-il un moyen de déterminer, au moment de l'exécution, si une structure qui a été transmise à une fonction modèle possède un champ spécifique ? Je ne sais pas si je vais devoir écrire une macro magique qui le fera à ma place, mais je ne sais pas si je vais devoir écrire une macro magique qui le fera à ma place. (Le problème est essentiellement que je ne peux pas utiliser la spécialisation des modèles).

Merci de votre attention ! Si vous avez des questions, n'hésitez pas à les poser !

Voici un extrait de ce que je pensais...

struct A
{
  char name[256];
  int index;
  float percision;
};

struct B
{
  int index;
  char name[256];
  int latency;
};

/* More annoying similar structs... note that all of the above are defined in files that were compiled as C - not C++ */

struct Entry
{
  char name[256];
  int index;
  float percision;
  int latency;
  /* more fields that are specific to only 1 or more structure */
};

template<typename T> struct Entry gatherFrom( T *ptr )
{
  Entry entry;

  strcpy( entry.name, ptr->name, strlen( ptr->name ) );
  entry.index = ptr->index;
  /* Something like this perhaps? */
  entry.percision = type_contains_field( "percision" ) ? ptr->percision : -1;
}

int main()
{
  struct A a;
  struct B b;

  /* initialization.. */

  Entry e  = gatherFrom( a );
  Entry e2 = gatherFrom ( b );

  return 0;
}

3voto

Marcelo Cantos Points 91211

Vous pouvez le faire à la compilation sans toucher au code source des structures originales :

#include <iostream>
#include <limits>
#include <memory.h>

struct A
{
    char name[256];
    int index;
    float percision;
};

struct B
{
    int index;
    char name[256];
    int latency;
};

struct Entry
{
    char name[256];
    int index;
    float percision;
    int latency;
    /* more fields that are specific to only 1 or more structure */
};

inline
std::ostream & operator<<(std::ostream & os, Entry const & e) {
    return os << e.name << "{" << e.index << ", " << e.percision << ", " << e.latency << "}";
}

template <typename T>
inline
void assign(T & dst, T const & src) {
    dst = src;
}

template <size_t N>
inline
void assign(char (&dst)[N], char const (&src)[N]) {
    memcpy(dst, src, N);
}

#define DEFINE_ENTRY_FIELD_COPIER(field)                            \
    template <typename T>                                           \
    inline                                                          \
    decltype(T::field, true) copy_##field(T const * t, Entry & e) { \
        assign(e.field, t->field);                                  \
        return true;                                                \
    }                                                               \
                                                                    \
    inline                                                          \
    bool copy_##field(void const *, Entry &) {                      \
            return false;                                           \
    }

DEFINE_ENTRY_FIELD_COPIER(name)
DEFINE_ENTRY_FIELD_COPIER(index)
DEFINE_ENTRY_FIELD_COPIER(percision)
DEFINE_ENTRY_FIELD_COPIER(latency)

template <typename T>
Entry gatherFrom(T const & t) {
    Entry e = {"", -1, std::numeric_limits<float>::quiet_NaN(), -1};
    copy_name(&t, e);
    copy_index(&t, e);
    copy_percision(&t, e);
    copy_latency(&t, e);
    return e;
}

int main() {
    A a = {"Foo", 12, 1.2};
    B b = {23, "Bar", 34};

    std::cout << "a = " << gatherFrom(a) << "\n";
    std::cout << "b = " << gatherFrom(b) << "\n";
}

Le DEFINE_ENTRY_FIELD_COPIER() définit une paire de fonctions surchargées pour chaque champ à extraire. Une surcharge ( copy_##field(T const * t, …) ce qui donne copy_name(T const * t, …) , copy_index(T const * t, …) etc.) définit son type de retour comme suit decltype(T::field, true) qui se résout en type bool si T possède un membre de données appelé name , index , etc. Si T n'a pas un tel champ, la substitution échoue, mais au lieu de provoquer une erreur de compilation, cette première surcharge est simplement traitée comme si elle n'existait pas (c'est ce qu'on appelle une SFINAE ) et l'appel se résout donc à la deuxième surcharge, copy_##field(void const * t, …) qui accepte n'importe quel type pour son premier argument et ne fait rien.

Notes :

  1. Parce que ce code résout les surcharges au moment de la compilation, gatherFrom() est optimal, en ce sens que le code binaire généré pour gatherFrom<A>() par exemple, donnera l'impression que vous l'avez accordé pour A à la main :

    Entry handCraftedGatherFromA(A const & a) {
        Entry e;
        e.latency = -1;
        memcpy(_result.name, a.name, sizeof(a.name));
        e.index = a.index;
        e.percision = a.percision;
        return e;
    }

    Sous g++ 4.8 avec -O3 , gatherFrom<A>() y handCraftedGatherFromA() génèrent un code identique :

    pushq   %rbx
    movl    $256, %edx
    movq    %rsi, %rbx
    movl    $-1, 264(%rdi)
    call    _memcpy
    movss   260(%rbx), %xmm0
    movq    %rax, %rcx
    movl    256(%rbx), %eax
    movss   %xmm0, 260(%rcx)
    movl    %eax, 256(%rcx)
    movq    %rcx, %rax
    popq    %rbx
    ret

    Clang 4.2's gatherFrom<A>() ne fait pas aussi bien, malheureusement ; il initialise de façon redondante toute l'entrée. Tout n'est donc pas rose, je suppose.

    En utilisant NRVO Les deux versions évitent de copier e lors de son renvoi. Cependant, je dois noter que les deux versions permettent d'économiser un code-op ( movq %rcx, %rax ) en utilisant un paramètre de sortie au lieu d'une valeur de retour.

  2. Le copy_…() Les fonctions renvoient un bool résultat indiquant si la copie a eu lieu ou non. Cette fonction n'est pas utilisée actuellement, mais elle pourrait l'être, par exemple, pour définir la fonction int Entry::validFields sous la forme d'un masque de bits indiquant les champs qui ont été remplis.

  3. La macro n'est pas nécessaire ; elle sert uniquement à SEC . L'ingrédient essentiel est l'utilisation de SFINAE.

  4. Le assign() les surcharges ne sont pas non plus nécessaires. Elles permettent simplement d'éviter d'avoir une macro différente et presque identique pour gérer les tableaux de caractères.

  5. Le code ci-dessus s'appuie sur le mot-clé decltype de C++11. Si vous utilisez un compilateur plus ancien, c'est plus compliqué, mais toujours possible. La solution la plus propre que j'ai trouvée est la suivante. Elle est conforme à C++98 et toujours basée sur le principe SFINAE :

    template <typename C, typename F, F (C::*), typename T>
    struct EnableCopy {
        typedef T type;
    };
    
    #define DEFINE_ENTRY_FIELD_COPIER(field, ftype)             \
        template <typename T>                                   \
        inline                                                  \
        typename EnableCopy<T, ftype, &T::field, bool>::type    \
        copy_##field(T const * t, Entry & e) {                  \
            copy_value(e.field, t->field);                      \
            return true;                                        \
        }                                                       \
                                                                \
        inline                                                  \
        bool copy_##field(void const *, Entry &) {              \
            return false;                                       \
        }
    
    DEFINE_ENTRY_FIELD_COPIER(name     , char[256]);
    DEFINE_ENTRY_FIELD_COPIER(index    , int);
    DEFINE_ENTRY_FIELD_COPIER(percision, float);
    DEFINE_ENTRY_FIELD_COPIER(latency  , int);

    Vous devrez également renoncer à la portabilité de la norme C++11. std::numeric_limits<float>::quiet_NaN() et utiliser une astuce ( 0.0f/0.0f semble fonctionner) ou choisissez une autre valeur magique.

2voto

datenwolf Points 85093

tout le reste écrit en C (l'utilisation du C n'est pas une option pour moi :( ).

J'aimerais tout d'abord citer ce que Linus Torvalds avait à dire à ce sujet :


From: Linus Torvalds <torvalds <at> linux-foundation.org>
Subject: Re: [RFC] Convert builin-mailinfo.c to use The Better String Library.
Newsgroups: gmane.comp.version-control.git
Date: 2007-09-06 17:50:28 GMT (2 years, 14 weeks, 16 hours and 36 minutes ago)

C++ is a horrible language. It's made more horrible by the fact that a lot 
of substandard programmers use it, to the point where it's much much 
easier to generate total and utter crap with it. Quite frankly, even if 
the choice of C were to do *nothing* but keep the C++ programmers out, 
that in itself would be a huge reason to use C.

http://harmful.cat-v.org/software/c++/linus


Ils ont un certain nombre de structures de données qui sont TRÈS similaires (c'est-à-dire qu'ils ont tous des champs tels que "nom", "adresse", etc. Mais, pour une raison quelconque, il n'y a pas de structure commune sur laquelle ils se basent pour tout le reste (ce qui rend tout travail infernal).

Il se peut qu'ils aient eu de très bonnes raisons de le faire. Rassembler des champs communs dans une structure de base unique (classe) peut sembler une excellente idée. Mais cela rend les choses vraiment difficiles si vous voulez appliquer des changements majeurs à l'une des structures (remplacer certains champs, changer les types, etc.) tout en laissant le reste intact. La POO n'est certainement pas la seule et unique façon de faire les choses.

Ma question est donc la suivante : existe-t-il un moyen de déterminer, au moment de l'exécution, si une structure qui a été transmise à une fonction modèle possède un champ spécifique ?

Non, ce n'est pas possible. Ni en C, ni en C++, car toutes les informations sur les types sont supprimées lors de la création du binaire. Il n'y a ni réflexion ni introspection en C ou en C++. Techniquement, les informations de débogage émises par le compilateur sont les suivantes fait fournissent ces informations, mais il n'existe pas de fonction intégrée au langage pour y accéder. En outre, ce type d'informations de débogage repose sur une analyse effectuée à la compilation, et non à l'exécution. Le C++ dispose de RTTI, mais ce n'est qu'un système très grossier qui permet d'identifier la classe d'une instance. Il n'est d'aucune utilité pour les membres d'une classe ou d'une structure.

Mais pourquoi faire cela au moment de l'exécution ?

Quoi qu'il en soit, j'ai besoin de faire une analyse à l'échelle du système de ces structs qui sont en mémoire, et de tout regrouper dans un tableau.

Vous devriez être heureux de devoir analyser le C et non le C++. Parce que le C est vraiment, vraiment facile à analyser (contrairement au C++ qui est terriblement difficile à analyser, principalement à cause de ces satanés templates). En particulier les structures. J'écrirais juste un petit et simple script, qui extrait toutes les définitions de structures des sources C. Cependant, comme les structures sont de taille constante, elles contiennent souvent des pointeurs vers des données allouées dynamiquement. Et à moins que vous ne vouliez patcher votre allocateur, je pense que la façon la plus simple d'analyser cela est de se connecter à un débogueur et d'enregistrer l'utilisation de la mémoire de chaque objet unique dont le pointeur est assigné à un membre de la structure.

1voto

MSalters Points 74024

Oui, ce n'est pas difficile du tout. Il suffit de mettre un A et un Entry dans un seul objet, et de faire de l'objet Entry un citoyen de seconde zone :

void setDefaultValues(Entry*); // You should be able to provide these.
struct Entry {
  int x;
  int y;
};
struct Indirect : public Entry { };
template<typename T> struct EntryOr : public T, Indirect
{
  setDefaultValues(this);
};

// From C code
struct A {
  int x;
}

int main()
{
  EntryOr<A> foo;
  foo.x = 5; // A::x
  std::cout << foo.x << foo.y; // Prints A::x and Entry::y
}

(Lien)

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