OVH Cloud OVH Cloud

operator const float*() const

16 réponses
Avatar
Aurélien Barbier-Accary
Bonjour,

Etant donnée la classe Vec (de QGLViewer) résumée ci-dessous dont pos est une
instance, il est semble-t-il possible d'utiliser le code openGL suivant :
glVertex3fv(pos);
Transmission d'une seule adresse pour 3 flottants plutôt que de transmettre les
3 flottants à la suite.
C'est la surcharge de l'opérateur float* qui le permet :

#if defined (Q_OS_IRIX) || defined (Q_OS_AIX) || defined (Q_OS_HPUX)
# define UNION_NOT_SUPPORTED
#endif
// Q_OS_IRIX, Q_OS_AIX et Q_OS_HPUX sont des macros de Qt (trolltech)

class Vec
{
private:
// ...
#if defined (UNION_NOT_SUPPORTED)
float x_, y_, z_;
#else
union
{
struct { float x_, y_, z_; };
float v_[3];
};
#endif
// ...
public:
// ...
operator const float*() const
{
#ifdef UNION_NOT_SUPPORTED
return &x_;
#else
return v_;
#endif
}
// ...
};

J'ai deux questions :
1) est-ce que tous les compilateurs de toutes les architectures garantissent que
les 3 flottants seront définis en mémoire comme si on utilisait uniquement le
tableau v_ ?
Ceci m'étonne avec les possibilités d'alignement différentes et l'arrivée des
architectures 64 bits...
Que se passe-t-il pour les compilateurs/architectures qui ne supportent pas
l'union ?

2) Ma propre classe de vecteur 3D utilise des double plutôt que des float mais
l'affichage openGL se fait naturellement avec des float.
Est-il possible de proposer un mécanisme similaire dans ce cas ? (3 double et un
opérateur float*) Je ne vois vraiment pas comment mais dans le doute...

Merci d'avance pour vos lumières.

--
Aurélien Barbier-Accary

10 réponses

1 2
Avatar
Sylvain
Aurélien Barbier-Accary wrote on 28/05/2006 17:39:

Etant donnée la classe Vec (de QGLViewer) résumée ci-dessous

class Vec {
private:
#if defined (UNION_NOT_SUPPORTED)
float x_, y_, z_;
#else
union {
struct { float x_, y_, z_; };
float v_[3];
};
#endif


beurk !!

cela peut fonctionner sur un proc 32 bits avec alignement des structures
sur 32 bits (ou proc 64 bits ou align. 64 bits) mais se poser de telles
contraintes est dommage.

public:
operator const float*() const {
#ifdef UNION_NOT_SUPPORTED
return &x_;
#else
return v_;
#endif
}


retourner &x comme la prétendue adresse d'un float[3] est dangereux (ne
fonctionnera pas hors des limites citées plus haut).

J'ai deux questions :
1) est-ce que tous les compilateurs de toutes les architectures
garantissent que les 3 flottants seront définis en mémoire comme si on
utilisait uniquement le tableau v_ ?


donc non.

Que se passe-t-il pour les compilateurs/architectures qui ne supportent
pas l'union ?


ça existe encore ça ?

même avec le support de union le code (si data non alignée sur la taille
native d'un float) fera des aneries sur l'implémentation repose sur un
accès à x_, y_ et z_.

2) Ma propre classe de vecteur 3D utilise des double plutôt que des
float mais l'affichage openGL se fait naturellement avec des float.
Est-il possible de proposer un mécanisme similaire dans ce cas ? (3
double et un opérateur float*) Je ne vois vraiment pas comment mais dans
le doute...


non, pas directement, utilisez plutôt un tableau interne float (mis à
jour de manière synchrone à la modification des doubles ou, plus facile,
mis à jour à la demande) et renvoyer celui-ci, ie:

class VecDouble {: public Vec} {
private:
double z[3];
mutable float f[3];

public:
operator const float*() const {
f[0] = (float) z[0];
f[1] = (float) z[1];
f[2] = (float) z[2];
return f;
}
operator const double*() const {
return z;
}
};

Sylvain.

Avatar
kanze
Aurélien Barbier-Accary wrote:

Etant donnée la classe Vec (de QGLViewer) résumée ci-dessous dont p os
est une instance, il est semble-t-il possible d'utiliser le code
openGL suivant : glVertex3fv(pos); Transmission d'une seule adresse
pour 3 flottants plutôt que de transmettre les 3 flottants à la suite.

C'est la surcharge de l'opérateur float* qui le permet :

#if defined (Q_OS_IRIX) || defined (Q_OS_AIX) || defined (Q_OS_HPUX)
# define UNION_NOT_SUPPORTED
#endif
// Q_OS_IRIX, Q_OS_AIX et Q_OS_HPUX sont des macros de Qt (trolltech)

class Vec
{
private:
// ...
#if defined (UNION_NOT_SUPPORTED)
float x_, y_, z_;
#else
union
{
struct { float x_, y_, z_; };
float v_[3];
};
#endif
// ...
public:
// ...
operator const float*() const
{
#ifdef UNION_NOT_SUPPORTED
return &x_;
#else
return v_;
#endif
}
// ...
};

J'ai deux questions :

1) est-ce que tous les compilateurs de toutes les
architectures garantissent que les 3 flottants seront définis
en mémoire comme si on utilisait uniquement le tableau v_ ?


Non. Selon la norme, c'est un comportement indéfini. Dans la
pratique, tu ne risques pas grand chose, je crois. Mais je ne
vois pas vraiment l'intérêt. Dans un cas comme ci-dessus, je ne
déclarerais que le tableau, avec des fonctions membres du
genre :

float& x() { return v[ 0 ] ; }

pour en donner les noms, si c'est ça que je veux.

Ceci m'étonne avec les possibilités d'alignement différentes
et l'arrivée des architectures 64 bits...


Même si le compilateur essaie à maintenir un alignement de 8
octets (ce n'est pas le cas des compilateurs 64 bits que je
connais, qui ne respecte que l'alignement nécessité par les
membres), c'est tout à parier que le rembourrage se trouve à la
fin.

Que se passe-t-il pour les compilateurs/architectures qui ne
supportent pas l'union ?


Qui sait. Et qu'est-ce qu'ils entendent par
« UNION_NOT_SUPPORTED » ? Il n'y a aucun problème avec l'union ;
la norme exige qu'elle soit acceptée. Il y a, en revanche, un
problème avec l'utilisation qu'ils en font -- la norme dit qu'on
ne peut accéder qu'au dernier membre écrit. Et à part g++ (et je
ne suis même pas sûr pour g++), je ne connais pas de compilateur
qui offre d'avantage de garanties. Et il faut la garantie, pour
être sûr que le compilateur prend l'alias en compte : si les
fonctions sont inline, tu affectes x_, et puis tu ne lis que
v_[0], il y a de fortes chances que le compilateur détecte que
la valeur affectée à x_ ne soit jamais lu, et qu'il supprime
l'affectation. Dans la pratique, certaines versions du
compilateur C de Microsoft faisaient bien cette optimisation, il
y a déjà plus de 20 ans.

2) Ma propre classe de vecteur 3D utilise des double plutôt
que des float mais l'affichage openGL se fait naturellement
avec des float. Est-il possible de proposer un mécanisme
similaire dans ce cas ? (3 double et un opérateur float*) Je
ne vois vraiment pas comment mais dans le doute...


Bien sûr : avec des membres donnés :

double x ;
double y ;
double z ;
float v[ 3 ] ;

Lors de l'appelle à asFloatArray (je déconseilles fortement la
conversion implicite), tu copies les doubles dans le tableau, et
tu renvoies l'adresse du tableau.

Sans que tes objets contiennent le tableau, c'est plus
difficile. Le plus simple, c'est une fonction membre :

template< typename T >
void copyOut( T* dest ) const
{
dest[ 0 ] = x ;
dest[ 1 ] = y ;
dest[ 2 ] = z ;
}

Ensuite, c'est à l'utilisateur de s'occuper de la mémoire.

Sinon, tu fais une asFloatArray qui renvoie une
std::vector<float>. Attention, en revanche : il s'agit d'un
objet temporaire ; il faut donc faire attention à sa durée de
vie. L'utilisateur va ensuite passé &obj.asFloatArray()[ 0 ] aux
fonctions qui s'attentent à un float*, et ce pointeur ne serait
valid que jusqu'à la fin de l'expression.

Le plus simple, évidemment, c'est d'utiliser de la mémoire
dynamique :

float* asArray() const
{
float* result = new float[ 3 ] ;
result[ 0 ] = x ; result[ 1 ] = y ; result[ 2 ] = z ;
return result ;
}

Mais ne marche qu'avec un glaneur de cellules. Et même alors, le
coût de l'allocation dynamique pourrait être genant.

Enfin, j'imagine que la plupart du temps, l'utilisateur va en
convertir un certain nombre d'éléments à la fois. Dans ce
cas-là, je verais bien un objet fonctionnel de conversion et
std::transform. (Mais il faudrait peut-être un itérateur spécial
pour le cible aussi, étant donné que les tableaux de type C ne
sont pas les types comme les autres.)

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

Avatar
Aurélien Barbier-Accary

beurk !!


C'est ce que je me disais, merci pour la confirmation.


class VecDouble {: public Vec} {
private:
double z[3];
mutable float f[3];

public:
operator const float*() const {
f[0] = (float) z[0];
f[1] = (float) z[1];
f[2] = (float) z[2];
return f;
}
operator const double*() const {
return z;
}
};



Merci !

--
Aurélien Barbier-Accary

Avatar
Aurélien Barbier-Accary
2) Ma propre classe de vecteur 3D utilise des double plutôt
que des float mais l'affichage openGL se fait naturellement
avec des float. Est-il possible de proposer un mécanisme
similaire dans ce cas ? (3 double et un opérateur float*) Je
ne vois vraiment pas comment mais dans le doute...


Bien sûr : avec des membres donnés :

double x ;
double y ;
double z ;
float v[ 3 ] ;

Lors de l'appelle à asFloatArray (je déconseilles fortement la
conversion implicite), tu copies les doubles dans le tableau, et
tu renvoies l'adresse du tableau.


Merci pour toutes les infos.
Une dernière petite question : pourquoi déconseilles-tu fortement la conversion
implicite ? Quels sont les inconvénients/risques ?

--
Aurélien Barbier-Accary


Avatar
Gilles


beurk !!


C'est ce que je me disais, merci pour la confirmation.


En tant qu'auteur du code incriminé, je suis forcé d'admettre que ce
n'est pas terrible :)

À ma décharge, je n'avais pas pensé aux problème d'alignement, ça
fonctionnait en pratique, et ça permettait un accès direct à x, y et
z que je trouvais plus rapide et concis que v[0], v[1], v[2], ce qui a
de l'importance pour une telle classe de base.

Je vais supprimer l'union et ne garder que le tableau pour être sûr.

Par contre, si j'ai ajouté le cas spécial UNION_NOT_SUPPORTED, c'est
bien que certains compilateurs encore utilisés de nos jours sur des
systèmes historiques hurlent devant tant de modernité.


Avatar
Sylvain
Gilles wrote on 29/05/2006 20:54:

beurk !!
C'est ce que je me disais, merci pour la confirmation.



En tant qu'auteur du code incriminé, je suis forcé d'admettre que ce
n'est pas terrible :)


disons que cela aurait plus propre avec un pragma align. (mais cela ne
corrige pas tout les dangers d'un union face à un compilo qui en fait trop).

À ma décharge, je n'avais pas pensé aux problème d'alignement, ça
fonctionnait en pratique, et ça permettait un accès direct à x, y et
z que je trouvais plus rapide et concis que v[0], v[1], v[2], ce qui a
de l'importance pour une telle classe de base.


si l'essentiel des opérations sont faites par des fonctions membres de
la classe Vec, il sera intéressant en effet de garder que x,y,z afin
d'éviter l'indexage dans ces traitements internes.

Je vais supprimer l'union et ne garder que le tableau pour être sûr.


par contre si les coords sont souvent utilisés par d'autres fonctions
sous forme d'un tableau, celui-ci reste indispensable ...

pourquoi ne pas garder les deux, simplement redéfini selon:

class Vec {
private:
float v[3];
float &x, &y, &z;
public:
Vec() : x(v[0]), y(v[1]), z(v[2]){
x = y = z = 0.0;
}

operator const float*() const {
return v;
}
};

Sylvain.



Avatar
kanze
Gilles wrote:

[...]
Par contre, si j'ai ajouté le cas spécial UNION_NOT_SUPPORTED,
c'est bien que certains compilateurs encore utilisés de nos
jours sur des systèmes historiques hurlent devant tant de
modernité.


Je ne suis pas sûr d'avoir bien compris. Tu veux dire que tu
trouves des compilateurs C++ qui ne supportent pas de union ? Ça
m'étonne énormement, étant donné que union faisait partie du C
avant le premier compilateur C++.

Si le problème, c'est simplement que tu as oublié à donner un
nom à l'élément struct de l'union, c'est normal. Le C++
n'autorise pas de declarations qui ne déclarent rien, à
l'exception des unions anonymes. Qui ont une sémantique un peu
spéciale, en ce qu'elles introduisent les membres dans la portée
où se trouve l'union. Et en fait, aucun des compilateurs à ma
disposition (g++, Sun CC) ne l'accepte en mode conforme. (G++
l'accepte comme une extension si on ne lui démande pas la
conformité. Mais il s'agit bien d'une extension propre à g++,
et il ne faut pas s'attendre à ce qu'il marche ailleurs.)

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

Avatar
kanze
Sylvain wrote:
Gilles wrote on 29/05/2006 20:54:

beurk !!
C'est ce que je me disais, merci pour la confirmation.



En tant qu'auteur du code incriminé, je suis forcé
d'admettre que ce n'est pas terrible :)


disons que cela aurait plus propre avec un pragma align. (mais
cela ne corrige pas tout les dangers d'un union face à un
compilo qui en fait trop).


Disons que c'est un comportement indéfini -- certains
compilateurs (dont g++, je crois) peuvent le définir, mais ça ne
serait jamais portable.

À ma décharge, je n'avais pas pensé aux problème
d'alignement, ça fonctionnait en pratique, et ça permettait
un accès direct à x, y et z que je trouvais plus rapide et
concis que v[0], v[1], v[2], ce qui a de l'importance pour
une telle classe de base.


si l'essentiel des opérations sont faites par des fonctions
membres de la classe Vec, il sera intéressant en effet de
garder que x,y,z afin d'éviter l'indexage dans ces traitements
internes.


Pourquoi ? En général, je trouve assez normal que les fonctions
membre aient à faire avec la représentation interne de la
classe.

Je vais supprimer l'union et ne garder que le tableau pour
être sûr.


par contre si les coords sont souvent utilisés par d'autres
fonctions sous forme d'un tableau, celui-ci reste
indispensable ...

pourquoi ne pas garder les deux, simplement redéfini selon:

class Vec {
private:
float v[3];
float &x, &y, &z;
public:
Vec() : x(v[0]), y(v[1]), z(v[2]){
x = y = z = 0.0;
}

operator const float*() const {
return v;
}
};


Parce que ça multiplie la taille effective de la classe par 2 ou
3 ? Potentiellement, aussi (selon le compilateur) ça peut
rallentir les accès d'une façon significative. Si on veut
donner les noms x, y et z (ce qui me semble utile, même dans
l'interface publique), la façon la plus simple, c'est des
fonctions inline :

class Vec
{
private:
float v[3] ;
public:
float x() const { return v[0] ; }
float y() const { return v[1] ; }
float z() const { return v[2] ; }
// ...
} ;

Dans certaines applications, je verrais bien une petite classe
qui ne faisait que ça -- définissait un tableau de trois
éléments, en permettant l'accès aussi bien comme celui d'un
tableau que par les noms x, y et z.

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34




Avatar
kanze
Aurélien Barbier-Accary wrote:
2) Ma propre classe de vecteur 3D utilise des double plutôt
que des float mais l'affichage openGL se fait naturellement
avec des float. Est-il possible de proposer un mécanisme
similaire dans ce cas ? (3 double et un opérateur float*) Je
ne vois vraiment pas comment mais dans le doute...


Bien sûr : avec des membres donnés :

double x ;
double y ;
double z ;
float v[ 3 ] ;

Lors de l'appelle à asFloatArray (je déconseilles fortement la
conversion implicite), tu copies les doubles dans le tableau, et
tu renvoies l'adresse du tableau.


Une dernière petite question : pourquoi déconseilles-tu
fortement la conversion implicite ? Quels sont les
inconvénients/risques ?


C'est de l'obfuscation. Celui qui lit ne voit pas tout ce qui se
passe. Parfois (dans le cas des proxys, par exemple), c'est le
but. Mais ici ? En général, sauf dans les cas particuliers comme
les proxys, moins il y a de conversions implicites, mieux on se
porte.

Aussi, évidemment, plus il y a de conversions implicites, plus
il y a de risques que les appels de fonction soient ambigus,
voire qu'on finit par appeler une fonction différente que celle
qu'on aurait voulue.

À titre d'exemple : tu n'as pas fourni un opérator << pour ta
classe. Quelqu'un écrit par erreur :

Vec v ;
// ...
std::cout << v << std::endl ;

Avec la conversion implicite, pas de probème -- ça marche. Et
affiche l'adresse du tableau, ce qui n'est certainement pas ce
qu'a voulu l'auteur.

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34



Avatar
Aurélien Barbier-Accary
À titre d'exemple : tu n'as pas fourni un opérator << pour ta
classe. Quelqu'un écrit par erreur :

Vec v ;
// ...
std::cout << v << std::endl ;

Avec la conversion implicite, pas de probème -- ça marche. Et
affiche l'adresse du tableau, ce qui n'est certainement pas ce
qu'a voulu l'auteur.



Très bien.
Merci beaucoup pour ces explications limpides.
Bonne journée.

--
Aurélien Barbier-Accary

1 2