31 votes

Aliasing struct et tableau de la manière C ++

C'est un C++ suivi pour une autre question de la mienne

Dans les vieux jours de pré-ISO C, le code suivant aurait surpris personne:

struct Point {
    double x;
    double y;
    double z;
};
double dist(struct Point *p1, struct Point *p2) {
    double d2 = 0;
    double *coord1 = &p1->x;
    double *coord2 = &p2->x;
    int i;
    for (i=0; i<3; i++) {
        double d = coord2[i]  - coord1[i];    // THE problem
        d2 += d * d;
    }
    return sqrt(d2);
}

Malheureusement, cette problématique de la ligne utilise l'arithmétique des pointeurs (p[i] étant , par définition, *(p + i)) en dehors de tout tableau qui est explicitement interdit par la norme. Projet de 4659 pour C++17 dit dans 8,7 [expr.ajouter]:

Si l'expression de P points à l'élément x[i] d'un objet de tableau x avec n éléments, les expressions de P + J et J + P (où J est la valeur de j) point à l' (éventuellement) hypothétique) de l'élément x[i + j] si 0 <= i + j <= n; autrement, le comportement est indéfini.

Et la (non normatif) note 86 rend encore plus explicite:

Un objet qui n'est pas un élément de tableau est considéré comme appartenant à un seul élément de tableau à cet effet. Un pointeur après le dernier élément d'un tableau x de n éléments est considéré comme équivalent à un pointeur vers un hypothétique élément x[n] à cette fin.

L'on a accepté la réponse de la référencées question utilise le fait que le langage C accepte le type de beaucoup les jeux de mots par le biais des syndicats, mais je n'ai jamais pu trouver l'équivalent dans la norme C++. Donc je suppose qu'une union contenant un anonyme struct membre et un tableau entraînerait Undefined Behaviour en C++ - elles sont différentes langues...

Question:

Ce pourrait être une manière conforme à itérer à travers les membres d'une structure, comme s'ils étaient membres d'un tableau en C++? Je suis à la recherche d'une façon de courant (C++17) versions, mais les solutions pour les versions plus anciennes sont aussi les bienvenus.

Avertissement:

Il est évident qu'elle ne s'applique qu'à des éléments de même type, et le rembourrage peut être détecté avec un simple assert comme le montre cette autre question, donc, de rembourrage, l'alignement, et les types mixtes ne sont pas mon problème ici.

28voto

Tobi Points 1264

Utilisez un tableau constexpr de pointeur vers membre:

 #include <math.h>

struct Point {
    double x;
    double y;
    double z;
};

double dist(struct Point *p1, struct Point *p2) {
    constexpr double Point::* coords[3] = {&Point::x, &Point::y, &Point::z};

    double d2 = 0;
    for (int i=0; i<3; i++) {
        double d = p1->*coords[i] - p2->*coords[i];
        d2 += d * d;
    }
    return sqrt(d2);
}
 

17voto

Jaa-c Points 1928

À mon humble avis, le moyen le plus simple consiste à simplement implémenter operator[] . Vous pouvez créer un tableau d'aide comme celui-ci ou simplement créer un commutateur ...

 struct Point
{
    double const& operator[] (std::size_t i) const 
    {
        const std::array coords {&x, &y, &z};
        return *coords[i];
    }

    double& operator[] (std::size_t i) 
    {
        const std::array coords {&x, &y, &z};
        return *coords[i];
    }

    double x;
    double y;
    double z;
};

int main() 
{
    Point p {1, 2, 3};
    std::cout << p[2] - p[1];
    return 0;
}
 

2voto

Yakk Points 31636
struct Point {
  double x;
  double y;
  double z;
  double& operator[]( std::size_t i ) {
    auto self = reinterpret_cast<uintptr_t>( this );
    auto v = self+i*sizeof(double);
    return *reinterpret_cast<double*>(v);
  }
  double const& operator[]( std::size_t i ) const {
    auto self = reinterpret_cast<uintptr_t>( this );
    auto v = self+i*sizeof(double);
    return *reinterpret_cast<double const*>(v);
  }
};

cela repose sur l'existence d'aucun emballage entre l' doubles dans votre `struct. En affirmant que c'est difficile.

Un module struct est une séquence d'octets de la garantie.

Un compilateur doit être capable de compiler [] vers le bas pour les mêmes instructions (ou son absence) comme matière première de la matrice de l'accès ou de l'arithmétique des pointeurs. Il y a peut - être quelques problèmes où cette optimisation arrive "trop tard" pour d'autres optimzations de se produire, afin de vérifier les performances sensible code.

Il est possible que la conversion à l' char* ou std::byte* insted d' uintptr_t serait valable, mais il est une question essentielle si l'arithmétique des pointeurs est autorisé dans ce cas.

0voto

Oliv Points 7148

Vous pouvez utiliser le fait que la conversion d'un pointeur en intptr_t faisant de l'arithmétique, puis la conversion de la valeur en type de pointeur est un comportement défini par implémentation . Je pense que cela fonctionnera sur la plupart des compilateurs:

 template<class T>
T* increment_pointer(T* a){
  return reinterpret_cast<T*>(reinterpret_cast<intptr_t>(a)+sizeof(T));
  }
 

Cette technique est la plus efficace, les optimiseurs semblent ne pas être en mesure de produire de manière optimale si l'on utilise une table de recherche: assemblages-comparaison

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