OVH Cloud OVH Cloud

sizeof et virtual

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

avant ma question, voici les données :

class cA
{
protected:
double d;
public:
cA(const double& d =0.0) { this->d = d; }
~cA(void) {}
};

class cB
{
protected:
double d;
public:
cB(const double& d =0.0) { this->d = d; }
virtual ~cB(void) {}
};

cout << "sizeof(cA) = " << sizeof(cA) << endl; // => 8
cout << "sizeof(cB) = " << sizeof(cB) << endl; // => 16

est-ce normal ?
peut-on garder une taille de 8 en utilisant des fonctions virtuelles ?

Merci.

Aurélien.

10 réponses

1 2
Avatar
Aurélien Barbier-Accary
Bref ça coûte finalement assez cher ces 8+8+...+8 octets en plus pour chaque
virtual.


+8 pour chaque virtual ? Normalement, on ne paye ce +8 qu'une fois
il me semble.


je me suis mal exprimé, bien sûr qu'on ne paye +8 qu'une fois par objet mais
pour un objet virtuel (pardon pour cet abus de langage) contenant 2 objets
virtuels on paye (logiquement) +24.
Si on répète les appartenances, voila ce que j'entendais par 8+8+...+8

Eventuellement, tu peux non pas stoquer un double mais un
unsigned char t[sizeof double], et ne pas accéder à ta variable,
mais passer par une fonction qui va la recalculer systématiquement.

double getValue(){
double res;
memcpy(&res, t, sizeof(res) );
return res;
}

void setValue(double d){
memcpy(t, &d);
}



oui mais là je vais trop perdre en temps.
Ah la la, on veut toujours le beurre, l'argent ... ;-p
Bon, il me semble que je n'ai pas d'autre choix que de me résigner sur ce point
et de bien repenser la structure de mes objets.

Merci pour tes explications et propositions.

Aurélien.


Avatar
Marc Boyer
Aurélien Barbier-Accary a écrit :
Bref ça coûte finalement assez cher ces 8+8+...+8 octets en plus pour chaque
virtual.


+8 pour chaque virtual ? Normalement, on ne paye ce +8 qu'une fois
il me semble.


je me suis mal exprimé, bien sûr qu'on ne paye +8 qu'une fois par objet mais
pour un objet virtuel (pardon pour cet abus de langage) contenant 2 objets
virtuels on paye (logiquement) +24.


Et oui.

Eventuellement, tu peux non pas stoquer un double mais un
unsigned char t[sizeof double], et ne pas accéder à ta variable,
mais passer par une fonction qui va la recalculer systématiquement.

double getValue(){
double res;
memcpy(&res, t, sizeof(res) );
return res;
}

void setValue(double d){
memcpy(t, &d);
}



oui mais là je vais trop perdre en temps.
Ah la la, on veut toujours le beurre, l'argent ... ;-p


Ben, le compromis mémoire/CPU est un très vieil équilibre en info.

Bon, il me semble que je n'ai pas d'autre choix que de me résigner sur ce point
et de bien repenser la structure de mes objets.


Ben le polymorphisme dynamique, il faut le payer à un moment ou
à un autre. on peut par exemple décider de stocker le type réel
de l'objet dans un type énuméré (8 bits si tu as moins de 256
types), et de se gérer le polymorphisme à la main.
Mais faut être rigoureux pour pas se planter.

Ca peut peut-être se simplifier à grand coups de templates,
mais je ne me lancerais pas dans ça sans une bonne raison.

Marc Boyer
--
À vélo, prendre une rue à contre-sens est moins dangeureux
que prendre un boulevard dans le sens légal. À qui la faute ?



Avatar
Arnaud Debaene
Aurélien Barbier-Accary wrote:
Saurais-tu où je peux trouver une description complète de la
structure d'une classe virtuelle s'il te plait ?
Dans la doc de ton compilateur : il n'y a pas de représentation standard, ca

dépend du compilo.

Arnaud

Avatar
Aurélien Barbier-Accary
Bon, il me semble que je n'ai pas d'autre choix que de me résigner sur ce point
et de bien repenser la structure de mes objets.


Ben le polymorphisme dynamique, il faut le payer à un moment ou
à un autre. on peut par exemple décider de stocker le type réel
de l'objet dans un type énuméré (8 bits si tu as moins de 256
types), et de se gérer le polymorphisme à la main.
Mais faut être rigoureux pour pas se planter.

Ca peut peut-être se simplifier à grand coups de templates,
mais je ne me lancerais pas dans ça sans une bonne raison.



Merci pour les pistes, il faut que j'y réfléchisse...


Avatar
Arnaud Debaene
Aurélien Barbier-Accary wrote:
Dans mon appli je travaille avec des BlobTrees qui sont des arbres
CSG enrichis, c'est-à-dire qu'un objet (3D) est défini par des
mélanges, unions, intersections et différences de primitiives
géométriques. Ces primitives sont composés d'un squelette géométrique et
d'une peau
implicite (pour les mélanges).
Chaque squelette est composé de points, vecteurs et quelques
grandeurs. Comme les modèles sont animés, en fait il s'agit de
trajectoires,
vecteurs dépendants du temps et grandeurs variables.
Et enfin si je me restreint aux trajectoires j'ai besoin que
certaines soient définies relativement à d'autres (gestion et édition
simplifiée) donc j'utilise beaucoup des compositions de trajectoires,
vecteurs et gandeurs. Si on ajoute à ça le fait que ces modèles sont à
niveaux de détails,
finalement la structure est assez importante mais surtout elle ne
contient que des objets qui sont des instances de classes virtuelles
(de BlobTrees, de primitives, de squelettes, des trajectoires, ...).
Et comme ce qui m'intéresse vraiment est de gérer plusieurs objets et
de faire des transformations entre eux...

Bref ça coûte finalement assez cher ces 8+8+...+8 octets en plus pour
chaque virtual.
Ce qui est un peu curieux, c'est que tous ces objets "primitifs" qui sont

essentiellement des blocs de données, aient tous des méthodes virtuelles.
D'ordinaire, ces sont plutôt les objets de type "identités" qui ont des
méthodes virtuelles, plutôt que les objets de type "valeur". Toutes ces
classes ont des types dérivés? Bon, ceci-dit, je ne connais rien à ton
domaine d'application....

Arnaud

Avatar
Aurélien Barbier-Accary
Aurélien Barbier-Accary wrote:

Saurais-tu où je peux trouver une description complète de la
structure d'une classe virtuelle s'il te plait ?


Dans la doc de ton compilateur : il n'y a pas de représentation standard, ca
dépend du compilo.

Arnaud




Merci, et donc si ça vous intéresse, pour g++ on a l'option de compilation
-fdump-class-hierarchy-options (C++ uniquement) qui permet de "décharger une
représentation de la hiérarchie de chaque classe et de la composition de la
table des fonctions virtuelles dans un fichier. Le nom du fichier est créé en
concaténant .class au nom du fichier source. Si la forme -options est utilisée,
options contrôle les détails du déchargement comme décrits par les options
-fdump-tree." (extrait de http://www.delafond.org/traducmanfr/man/man1/gcc.1.html)


Avatar
Aurélien Barbier-Accary
Ce qui est un peu curieux, c'est que tous ces objets "primitifs" qui sont
essentiellement des blocs de données, aient tous des méthodes virtuelles.
D'ordinaire, ces sont plutôt les objets de type "identités" qui ont des
méthodes virtuelles, plutôt que les objets de type "valeur". Toutes ces
classes ont des types dérivés? Bon, ceci-dit, je ne connais rien à ton
domaine d'application....

Arnaud



et bien oui car :
- un BlobTree est une SurfaceImplicite particulière
- le mélange, l'union, ... sont des opérateurs
- les sphères, cylindres généralisés, ... sont des primitives
- les opérateurs et les primitives sont des noeuds de BlobTree
- les primitives contiennent des trajectoires, des vecteurs et des grandeurs
- parmi les trajectoires il y a les polynomiales, les physiques (forces), les
interpolées, ...
- et même pour les scalaires il y a les nombres, les "équations" (fonctions
d'une valeur variable pour laquelle on veut maintenir le lien), ...

A chaque fois je travaille avec des pointeurs sur les classes mères et j'utilise
des fonctions virtuelles pour chacune de ces classes.
D'où mon (léger) problème de mémoire, rançon des gains offerts pour la
modélisation et l'édition.

Avatar
kanze
Aurélien Barbier-Accary wrote:
Bref ça coûte finalement assez cher ces 8+8+...+8 octets en
plus pour chaque virtual.


+8 pour chaque virtual ? Normalement, on ne paye ce +8
qu'une fois il me semble.


je me suis mal exprimé, bien sûr qu'on ne paye +8 qu'une fois
par objet mais pour un objet virtuel (pardon pour cet abus de
langage) contenant 2 objets virtuels on paye (logiquement)
+24.


Attention : un objet ne peut jamais contenir un objet
polymorphique. Il peut tout au plus contenir un pointeur à un
tel objets.

Si on répète les appartenances, voila ce que j'entendais par
8+8+...+8


Mais si les objets « contenus » sont en fait des pointeurs... où
sont les objets réelement. S'ils sont alloué dynamiquement (ce
qui est en général le cas), je crois que tu trouveras que le
coût supplémentaire (le « overhead ») en mémoire de l'allocation
dynamique est au moins aussi grand que le coût du pointeur pour
le polymorphisme.

Eventuellement, tu peux non pas stoquer un double mais un
unsigned char t[sizeof double], et ne pas accéder à ta variable,
mais passer par une fonction qui va la recalculer systématiquement.

double getValue(){
double res;
memcpy(&res, t, sizeof(res) );
return res;
}

void setValue(double d){
memcpy(t, &d);
}



oui mais là je vais trop perdre en temps.


Est-ce que la solution la plus simple ne serait-elle pas de ne
stocker que des float dans ces objets-ci, quitte à faire les
calculs (et garder les valeurs intermédiaire) en double ?
Typiquement, un float n'a qu'un alignement de 4, comme un
pointeur (je suppose une machine 32 bits).

Ensuite, si tu as une classe qui ne contient qu'un seul float,
tu pourrait fournir un operator new spécifique à la classe, qui
se base sur un pool d'objets -- les gestionnaires des blocs de
taille fixe peuvent être beaucoup plus efficace, aussi bien en
termes d'utilisation de la mémoire qu'en termes de vitesse, que
des gestionnaires de la mémoire généralisés.

Ah la la, on veut toujours le beurre, l'argent ... ;-p

Bon, il me semble que je n'ai pas d'autre choix que de me
résigner sur ce point et de bien repenser la structure de mes
objets.


Si c'est réelement un problème, il existe des solutions.

--
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:
Bref ça coûte finalement assez cher ces 8+8+...+8 octets en
plus pour chaque virtual.


+8 pour chaque virtual ? Normalement, on ne paye ce +8
qu'une fois il me semble.


je me suis mal exprimé, bien sûr qu'on ne paye +8 qu'une fois
par objet mais pour un objet virtuel (pardon pour cet abus de
langage) contenant 2 objets virtuels on paye (logiquement)
+24.


Attention : un objet ne peut jamais contenir un objet
polymorphique. Il peut tout au plus contenir un pointeur à un
tel objets.

Si on répète les appartenances, voila ce que j'entendais par
8+8+...+8


Mais si les objets « contenus » sont en fait des pointeurs... où
sont les objets réelement. S'ils sont alloué dynamiquement (ce
qui est en général le cas), je crois que tu trouveras que le
coût supplémentaire (le « overhead ») en mémoire de l'allocation
dynamique est au moins aussi grand que le coût du pointeur pour
le polymorphisme.

Eventuellement, tu peux non pas stoquer un double mais un
unsigned char t[sizeof double], et ne pas accéder à ta variable,
mais passer par une fonction qui va la recalculer systématiquement.

double getValue(){
double res;
memcpy(&res, t, sizeof(res) );
return res;
}

void setValue(double d){
memcpy(t, &d);
}



oui mais là je vais trop perdre en temps.


Est-ce que la solution la plus simple ne serait-elle pas de ne
stocker que des float dans ces objets-ci, quitte à faire les
calculs (et garder les valeurs intermédiaire) en double ?
Typiquement, un float n'a qu'un alignement de 4, comme un
pointeur (je suppose une machine 32 bits).

Ensuite, si tu as une classe qui ne contient qu'un seul float,
tu pourrait fournir un operator new spécifique à la classe, qui
se base sur un pool d'objets -- les gestionnaires des blocs de
taille fixe peuvent être beaucoup plus efficace, aussi bien en
termes d'utilisation de la mémoire qu'en termes de vitesse, que
des gestionnaires de la mémoire généralisés.

Ah la la, on veut toujours le beurre, l'argent ... ;-p

Bon, il me semble que je n'ai pas d'autre choix que de me
résigner sur ce point et de bien repenser la structure de mes
objets.


Si c'est réelement un problème, il existe des solutions.

--
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
Marc Boyer wrote:

Aurélien Barbier-Accary a
écrit : >Bref ça coûte finalement assez cher ces 8+8+...+8
octets en plus pour chaque >virtual.

+8 pour chaque virtual ? Normalement, on ne paye ce +8
qu'une fois il me semble.


je me suis mal exprimé, bien sûr qu'on ne paye +8 qu'une
fois par objet mais pour un objet virtuel (pardon pour cet
abus de langage) contenant 2 objets virtuels on paye
(logiquement) +24.


Et oui.

Eventuellement, tu peux non pas stoquer un double mais un
unsigned char t[sizeof double], et ne pas accéder à ta
variable, mais passer par une fonction qui va la recalculer
systématiquement.

double getValue(){
double res;
memcpy(&res, t, sizeof(res) );
return res;
}

void setValue(double d){
memcpy(t, &d);
}


oui mais là je vais trop perdre en temps. Ah la la, on veut
toujours le beurre, l'argent ... ;-p


Ben, le compromis mémoire/CPU est un très vieil équilibre en
info.

Bon, il me semble que je n'ai pas d'autre choix que de me
résigner sur ce point et de bien repenser la structure de
mes objets.


Ben le polymorphisme dynamique, il faut le payer à un moment
ou à un autre. on peut par exemple décider de stocker le type
réel de l'objet dans un type énuméré (8 bits si tu as moins de
256 types), et de se gérer le polymorphisme à la main.


Sauf que ça ne change rien au problème de l'alignement. Si tu as
une struct { char type ; double value ; }, il y a de fortes
chances que le sizeof en soit 16, toujours.

Ce qui pourrait être intéressant, c'est si on arrive à s'en
tirer avec un seul type, du genre :

struct Data {
enum Type { ... } type ;
union {
T1 donneesType1 ;
T2 donneesType2 ;
// ...
} ;
} ;

Du coup, je peux faire std::vector< Data >, non seulement
std::vector< Data* >, avec chaque élément alloué dynamiquement.
Si on considère que le surcoûte d'une allocation dynamique est
de 8 ou de 16 octets, et que dans le forme avec Data*, il y a
des pointeurs en plus, on vient très vite à un gain d'entre 12
et 20 octets par élément. De l'autre côté, on a une localisation
accrue, ce qui pourrait améliorer les temps d'exécution d'une
façon notable.

Selon l'application, il serait même peut-être possible que
chaque tableau ne contient qu'un seul type. Dans ce cas-ci, on
pourrait associer le type au tableau, et non à chaque élément.

Mais faut être rigoureux pour pas se planter.

Ca peut peut-être se simplifier à grand coups de templates,
mais je ne me lancerais pas dans ça sans une bonne raison.


Tout dépend. Il y a des cas où l'union avec discriminateur fait
plus de sens que l'héritage. Mais c'est vrai que mon choix se
ferait d'abord sur les bases d'une bonne conception, et que j'en
écarterais que si les besoins de l'application (vitesse,
mémoire) l'exigent.

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



1 2