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

6 réponses

1 2
Avatar
meow
------------Kanze------------
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] ; }
// ...
} ;
<< ------------Kanze------------

Tu préfères ça à une définition de float x,y,z et d'operator[] ?

--Ben


Avatar
kanze
meow wrote:
------------Kanze------------
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] ; }
// ...
} ;
<< ------------Kanze------------

Tu préfères ça à une définition de float x,y,z et d'operator[] ?


L'un ou l'autre. Je crois qu'une des motivations d'avoir le
tableau, c'est pour pourvoir le passer à des fonctions C qui s'y
attend. Si c'est le cas, tu n'as pas le choix. Sinon, c'est
vraiment une question de goût.

--
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
Loïc Joly

Parce que ça multiplie la taille effective de la classe par 2 ou
3 ?


Les compilateurs ne sont-ils pas en droit d'optimiser ça ?

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] ; }
// ...
} ;


Avec le problème que l'on doit mettre des parenthèses partout, ce qui
rend la lecture moins naturelle. Je pense que c'est ce que cherchait à
éviter la proposition précédente. En fait, comment avoir le sucre
syntaxique des propriétés, dans un langage qui ne veut pas définir ce
concept.

--
Loïc

Avatar
Sylvain
kanze wrote on 30/05/2006 08:51:

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


pardon ? veux-tu dire les fonctions membres de Vec ont le droit
d'utiliser x, y, z ? il ne semble pas que quiconque ait dit le contraire
ici.
mais peut-être préconises-tu que les fonctions membres n'utilisent
surtout pas les variables x, y et x mais des getU/setU ou je ne sais
quelle indirection au nom d'une représentation interne.

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


Parce que ça multiplie la taille effective de la classe par 2 ou 3


oui et ?

Potentiellement, aussi (selon le compilateur) ça peut
ralentir les accès d'une façon significative.


ah oui ? tu peux détailler ce que "ça" recouvre ? (qu'est ce qui
ralentirait quoi ?)

selon mon humble avis, un accès à v[i] est résolu en qlq chose comme un
accès à l'adresse @instance + offset constant et donc se traduit par des
chargement immédiat où la notion de calcul d'index a disparu.
un accès à 'x' (où x est visiblement une référence, factuellement un
pointeur) devra charger une adresse à @instance + offset constant puis
utiliser le contenu de cette adresse (un MOV en plus sur Intel, si c'est
àa le "significatif").

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 :

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


je crois que l'on ne veut /pas/ de telles fonctions.

l'interface publique présentée montre clairement que l'utilisation du
"contenu" de la classe Vec se fait par un accès à un float* (float[3]),
d'où la fonction de cast implicite.

je pense de plus que la présence des 'x', 'y' et 'z' n'est justifiée que
par le souhait /dans l'implémentation de cette classe/ de ne pas
utiliser un tableau indéxé - même si comme dit plus haut cela n'induit
aucun cout au runtime (offset fixe) - car il est moins agréable de taper
et relire des v[0/1/2] que des x, y ou z.

la réponse la moins gélatineuse face à cela serait:

dans .h

private float v[3];

dans .cpp

#define x v[0]
#define y v[1]
#define z 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.


x, y, z ne sont pas des noms mais des fonctions, un accès nommé serait:

float{&} operator[] (char name) const {
if (name == 'x')
return v[0];
etc
}

qui serait insolvable s'il coexiste avec

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

Sylvain.



Avatar
kanze
Sylvain wrote:
kanze wrote on 30/05/2006 08:51:

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


pardon ? veux-tu dire les fonctions membres de Vec ont le
droit d'utiliser x, y, z ? il ne semble pas que quiconque ait
dit le contraire ici.


Je veux dire que les fonctions membres de Vec doivent savoir
qu'il n'y a pas de x, y ou z, mais qu'il y a un tableau, v[3].
Et donc se servir du tableau directement.

mais peut-être préconises-tu que les fonctions membres
n'utilisent surtout pas les variables x, y et x mais des
getU/setU ou je ne sais quelle indirection au nom d'une
représentation interne.


Ils s'agit ici d'une utilisation de v[3] en interne. Parce qu'il
y a des cas qui exige un tableau à la C comme représentation
physique.

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


Parce que ça multiplie la taille effective de la classe par
2 ou 3


oui et ?


Ça dépend de l'utilisation de la classe. S'il n'y a que quelque
milliers d'instances, effectivement, qu'importe. S'il y risque
d'en avoir des millions, il faut reflechir. Multiplier
inutilement la taille des objets aussi fondamentaux n'est pas
forcement sans conséquences.

Potentiellement, aussi (selon le compilateur) ça peut
ralentir les accès d'une façon significative.


ah oui ? tu peux détailler ce que "ça" recouvre ? (qu'est ce
qui ralentirait quoi ?)


La plupart des compilateurs implémentent des membres référence
comme des pointeurs. Ensuite, selon la qualité de
l'optimisateur, ce n'est pas dit que le compilateur reconnaît
que la référence x désigne toujours l'élément v[0] ; beaucoup de
compilateurs introduirons effectivement un niveau d'indirection
supplémentaire.

selon mon humble avis, un accès à v[i] est résolu en qlq chose
comme un accès à l'adresse @instance + offset constant et donc
se traduit par des chargement immédiat où la notion de calcul
d'index a disparu. un accès à 'x' (où x est visiblement une
référence, factuellement un pointeur) devra charger une
adresse à @instance + offset constant puis utiliser le contenu
de cette adresse (un MOV en plus sur Intel, si c'est àa le
"significatif").


Un MOV de plus, c'est signifiant SI tout ce qu'on fait
autrement, c'est un MOV -- deux MOV (de mémoire) à la place
d'un, c'est multiplier le temps par deux.

Ensuite, évidemment, il dépend de ce qu'on fait avec ces
variables.

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 :

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


je crois que l'on ne veut /pas/ de telles fonctions.


Pourquoi ?

l'interface publique présentée montre clairement que
l'utilisation du "contenu" de la classe Vec se fait par un
accès à un float* (float[3]), d'où la fonction de cast
implicite.


Si on n'en a pas besoin, alors, pourquoi toute cette discussion.
Les fonctions membres représentent un tout petit bout de code,
capable de traiter la représentation utilisée, quelqu'elle soit.
Et si on n'a besoin que d'une représentation au niveau de
l'interface aussi, il n'y a pas de problème.

je pense de plus que la présence des 'x', 'y' et 'z' n'est
justifiée que par le souhait /dans l'implémentation de cette
classe/ de ne pas utiliser un tableau indéxé - même si comme
dit plus haut cela n'induit aucun cout au runtime (offset
fixe) - car il est moins agréable de taper et relire des
v[0/1/2] que des x, y ou z.


Certes, mais le rôle des fonctions membres n'est-il pas
précisement de s'occuper de la représentation interne. Ou est-ce
que tu le trouves normal que la classe reserve l'interface
«@agréable » pour elle-même, et force les utilisateurs
d'utiliser celle moins agréable.

la réponse la moins gélatineuse face à cela serait:

dans .h

private float v[3];

dans .cpp

#define x v[0]
#define y v[1]
#define z v[2]


C'est une solution. N'empèche que les macros à noms aussi
généraux ne me plaît pas trop. Imagine la surprise du pauvre
programmeur de maintenance qui déclare une variable locale x.

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.


x, y, z ne sont pas des noms mais des fonctions, un accès nommé serai t:

float{&} operator[] (char name) const {
if (name == 'x')
return v[0];
etc
}


Tu n'est pas sérieux, j'espère. Parce que si tu fais ça, sans
fournir directement un operator[](int) aussi, alors [0] ou [1]
va appeler cette fonction. Et si tu fournis un operator[](int),
tu as une ambiguïté dès que l'utilisateur se sert d'un type
non-signé (par exemple).

qui serait insolvable s'il coexiste avec

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


Ça veut dire quoi, « insolvable », ici. Si j'écris unVec[x],
c'est l'opérateur [] qui sert ; s'il existe, on ne cherche pas
d'autres conversions.

--
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
Sylvain
kanze wrote on 31/05/2006 09:14:

float x() const { return v[0] ; }
je crois que l'on ne veut /pas/ de telles fonctions.

Pourquoi ?



parce que l'interface montrée excluait l'accès à un simple float.

Certes, mais le rôle des fonctions membres n'est-il pas
précisement de s'occuper de la représentation interne. Ou est-ce
que tu le trouves normal que la classe reserve l'interface
«@agréable » pour elle-même, et force les utilisateurs
d'utiliser celle moins agréable.


cela ne me gènerait pas, la notion d'"agréable" est subjective et liée à
l'attente (variable) - si les routines clientes attendent des float*,
il ne leur sera pas agréable de recevoir une référence fournissant 3
accesseurs distincts (x(), y(), z()).

float{&} operator[] (char name) const {
if (name == 'x')
return v[0];
etc
}
Tu n'est pas sérieux, j'espère.



pas vraiment non, c'était juste pour te taquiner un peu vis à vis des
"propriétés nommées" ;)
(une telle fonction qui introduit un "default case" (name <> 'x,y,z')
qu'elle ne peut pas gérer est évidemment à éviter).

qui serait insolvable s'il coexiste avec
operator const float*() const {
return v;
}


Ça veut dire quoi, « insolvable », ici. Si j'écris unVec[x],
c'est l'opérateur [] qui sert ; s'il existe, on ne cherche pas
d'autres conversions.



si "x" est une variable ou une valeur immédiate de type char (ie
unVec['x'] ou char c = 'y', unVec[c]) cela passe, mais si c'est une
valeur numérique (immédiate ou variable) ""moins typée"", le compilo a
plus de chance de trouver cela ambigu.

M$, notamment, ne sait pas choisir entre ((const float*) unVec)[2] et
unVec.operator[](2); même échec avec int indx = 'y'; unVec[indx];

ma remarque était aussi là pour appuyer ton commentaire concernant les
"effets de bords" d'opérateur de conversion implicite.

Sylvain.



1 2