Hésitation sémantique de valeur ou d'entité

Le problème exposé dans ce sujet a été résolu.

Salut les agrumes ,j'étais entrain de lire un tutoriel sur les pointeurs intelligents et je tombe sur ce code où l'auteur dit qu'il s'agit d'une sémantique de valeur sauf que Rectangle et Circle héritent de Shape et que normalement si c'est bien une sémantique de valeur il ne devrait pas y avoir d'héritage.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
vector<Shape> v;
v.push_back(Rectangle(0, 0, 10, 20));
v.push_back(Circle(0, 0, 10));
for(vector<Shape>::iterator it = v.begin();
it != v.end();
++it)
{
it->draw();
}
vector<Shape> v2 = v;
for(vector<Shape>::iterator it = v2.begin();
it != v2.end();
++it)
{
it->move(2, 2);
}

Je remercie d'avance les personnes qui me permettront de clarifier cela. :)
Bonne journée à tous !

Salut,

A mon avis, la sémantique d'une forme va principalement dépendre de son utilisation. Si elle représente un "objet", une "chose", qui sera par exemple déplacé, affiché… alors ce sera une sémantique d'entité. Si c'est pour une utilisation plutôt mathématique (comparaison de formes, de leur air, etc…), alors elle peut avoir une sémantique de valeur.

Et effectivement, si c'est une sémantique de valeur, l'héritage serait alors inapproprié.

Or dans ce code, je dirais qu'il s'agit plutôt d'une sémantique d'entité (les formes sont affichées, déplacées). Et ce qui pose problème c'est la ligne vector<Shape> v2 = v;. Ce n'est pas vraiment une copie, mais plutôt un clonage.

Pour cette raison, on peut décider d'interdire l'opérateur = avec ce genre de classe, et proposer une fonction clone().

HS: Les range-based for, c'est plus joli.

1
2
for(const auto &shape : v)
    shape.draw();

Lu'!

Un bon moyen de savoir si une classe a une sémantique de valeur est de se demander si la comparaison d'égalité à un sens dessus. Parce que la comparaison d'égalité n'a pas de sens sur des objets à sémantique d'entité (deux objets ayant cette sémantiques étant nécessairement deux objets différents à moins d'être à la même adresse).

Par extension, définir l'égalité sur des objets dans un arbre d'héritage aboutit pour ainsi dire systématiquement à produire des incohérences sur les propriétés d'égalité, d'où le fait que cela ne s'applique pas à des type valeurs.

Quant à la forme de ton code actuellement, tu fais un slicing sur tes objets. Donc tu n'afficheras jamais de Rectangle ou de Cercle avec ton vecteur. Que des Shapes.

Il faudrait voir l'article en question, il y a peut être une confusion/imprécision ?

Il faut faire la distinction entre le concept mathématique (la valeur) et une "instance" particulière (le fait d'appeler tes classes "Circle" et "Rectangle" ne veut pas dire que cela représente un cercle et un rectangle).

Si tu veux par exemple comparer deux surfaces, tu pourrais écrire :

1
2
3
Circle c(0, 0, 10);          // x, y, r
Rectangle r1(0, 0, 10, 20);  // x, y, w, h
bool b = has_same_surface(c, r1);

Si tu crées une seconde variable r2 :

1
2
3
Rectangle r2(0, 0, 10, 20);  // x, y, w, h
bool b = has_same_surface(c, r2);
r1 == r2;

r1 et r2 représente bien la même chose, peu importe que cela correspondent à des objets différents en mémoire.

Maintenant, si on prend des shapes, que l'utilisateur peut cliquer et déplacer à la souris :

1
2
3
4
5
vector<Shape> shapes = {
    Rectangle(0, 0, 10, 20),
    Rectangle(0, 0, 10, 20)
};
shapes.draw();

Même si tes 2 rectangles sont au même endroit, ce sont bien 2 "choses" différentes pour l'utilisateur. L'une est au dessus de l'autre, si l'utilisateur clique en (5, 5), il ne va sélectionner que le rectangle qui est au dessus et il ne déplacera qu'un seul rectangle.

Dans le premier cas, peu importe l'instance en particulier que l'on prend en compte, le résultat est le même (c'est une valeur). Dans le second cas, chaque instance est identifiable et différente des autres (c'est une entité).

Probablement que le mieux est de renommer tes types, par exemple RectangleShape et CircleShape, pour bien montrer qu'il ne s'agit pas des concepts "rectangle" et "cercle", mais d'entités.

+1 -0

Le code que tu cites vient d'une partie introductive, il y a en effet un problème de sémantique que cette partie veut exposer :

Les problèmes sont multiples : On doit désormais s'occuper de désallouer correctement ce qui a été alloué (ce que je n'ai pas fait ici), de plus, on a sans forcément le vouloir une sémantique d'entité, alors qu'initialement on avait une sémantique de valeur. Ainsi, la deuxième partie du code modifie aussi le contenu de v, ce qui n'était pas le cas dans la version précédente.

Il y a une phrase importante :

On veut manipuler des formes mathématiques, des cercles, des rectangles… Dans le monde idéal, on écrirait du code comme ça :

Cela signifie que l'on aimerait bien que ce soit des sémantiques de valeur, dans l'idéal.

Si tu regardes mon code précédent, tu peux remarquer que j'ai un peu triché sur l'exemple avec la sémantique de valeur : je n'ai pas utilisé de tableau. Imagine maintenant avec ce même exemple que je pose la question "si on a une liste de formes, comment trouver celle qui a la plus grande surface ?".

On peut continuer à "tricher" et utiliser 2 tableaux pour circle et rectangle, trouver le plus grand de chaque puis trouver le plus grand entre le plus grand cercle ou le plus grand rectangle.

Mais si tu as plus de forme, tu devras probablement faire un héritage, ajouter du polymorphisme… et passer à une sémantique d'entité.

Et c'est malheureusement ce qui arrive avec le code de Loïc. Il aimerait que ce soit des valeurs, mais par contrainte technique, cela devient des entités.

Un peu plus loin, il écrit :

Les problèmes sont multiples : On doit désormais s'occuper de désallouer correctement ce qui a été alloué (ce que je n'ai pas fait ici), de plus, on a sans forcément le vouloir une sémantique d'entité, alors qu'initialement on avait une sémantique de valeur. Ainsi, la deuxième partie du code modifie aussi le contenu de v, ce qui n'était pas le cas dans la version précédente.

Nous sommes dans le cas où l'on utilise des pointeurs alors que l'on souhaite manipuler des données ayant une sémantique de valeur, uniquement parce que l'on veut du polymorphisme.

Donc tu as bien compris, c'est bien une sémantique d'entité dans ce code, même si conceptuellement, on aimerait que ce soit des valeurs.

+1 -0

Oui, c'est bien une sémantique de valeur qu'il considère1, il aurait en effet pu le dire plus clairement par contre.

C'est le point de départ : on veut manipuler ensemble plusieurs objets différents à sémantique de valeur, comment faire ? Le premier problème, le slicing, est traité en passant par des pointeurs. Ensuite vient les problèmes de la citation de mon message précédent.

Si c'est une introduction aux pointeurs intelligents que tu veux, je ne suis pas convaincu que l'approche de Loic dans cette article soit la meilleur (il choisit de traiter un cas non couvert par les pointeurs du standard comme exemple).

Le tuto publié sur ce site à propos du RAII me semble avoir une meilleur approche du problème.


  1. L'article expose une solution technique, les classes pourraient s'appeler A et B, ça ne change rien. 

+1 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte