Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

Problèmes d'héritage

32 réponses
Avatar
No_Name
Bonjour,

Je travaille sur une application en C++ pour laquelle j'ai plusieurs
niveaux d'héritage :

A = Classe mère
Classe B = Hérite de A
Classe C = Hérite de B

Je voudrais pouvoir facilement transformer des objets de type B en
objets de type C et inversément.

Pour passer de C à B, j'imagine qu'un simple cast suffira, sachant
qu'en faisant cela, je perdrai les données qui font la sécificité de C
(puisque le but est de me retrouver avec un objet de type B).

Par contre, je ne vois pas bien comment je peux passer de B à C ?
J'envisageais de doter la classe C d'une fonction permettant de créer
un objet de type C à partir d'un objet de type B, en initialisant les
données supplémentaires à des valeurs par défaut, mais je ne suis pas
complètement sur de cette solution.

Et les fonctions de création d'un objet de type B en objet de type C
doivent elle effectivement être des méthodes de la classe C ?

Merci de vos conseils et suggestions.

10 réponses

1 2 3 4
Avatar
Fabien LE LEZ
On Tue, 27 Jan 2009 14:31:26 +0100, No_Name :

Parce que le new() que j'utilise pour créer mon objet me retourne un
pointeur ...



Mais typiquement, ce que new renvoie, on le stocke dans un pointeur
intelligent, pour éviter les fuites mémoire.

(Je dis "typiquement", car certains objets se détruisent tout seuls.)

Et question subsidiaire : pourquoi new est-il utilisé ici ?
Au moins dans le cadre de ce thread, ça ne fait que compliquer le
problème.
Cherche d'abord à bien comprendre les mécanismes de passage entre B et
C avec des objets normaux, et ensuite seulement, tu pourras ajouter,
si nécessaire, les complications liées à l'allocation dynamique.
Avatar
No_Name
Fabien LE LEZ a utilisé son clavier pour écrire :
On Tue, 27 Jan 2009 14:31:26 +0100, No_Name :

Parce que le new() que j'utilise pour créer mon objet me retourne un
pointeur ...



Mais typiquement, ce que new renvoie, on le stocke dans un pointeur
intelligent, pour éviter les fuites mémoire.

(Je dis "typiquement", car certains objets se détruisent tout seuls.)

Et question subsidiaire : pourquoi new est-il utilisé ici ?
Au moins dans le cadre de ce thread, ça ne fait que compliquer le
problème.
Cherche d'abord à bien comprendre les mécanismes de passage entre B et
C avec des objets normaux, et ensuite seulement, tu pourras ajouter,
si nécessaire, les complications liées à l'allocation dynamique.



En effet, mais la question que je posais s'inscrivait dans un véritable
projet. Il ne s'agit pas d'un exercice de style, et il me faut bien
adapter les réponses qui m'ont été faites à la réalité de mon code.

Merci encore pour vos réponses qui m'ont permis de régler le problème.
Avatar
No_Name
Michael DOUBEZ vient de nous annoncer :
No_Name wrote:
Le but de tout cela est de permettre un "undo", c'est à dire l'annulation
des choix du client.

Disons que le client fait un premier choix. On va donc générer en mémoire
un objet de type B correspondant à son choix.

Ensuite il sélectionne une option. C'est là que je suis amené à passer d'un
objet de type B à un objet de type C, correspondant à cette option.

Puis le client change d'idée et annule son choix. On doit alors revenir à
un objet de type B, d'où l'option est retirée.

Voilà la raison des manipulations auxquelles je me livre ...

Voyez vous éventuellement une autre façon (un autre design ?) de réaliser
le même type de traitements et d'offrir le même genre de possibilités ?



Ca dépends à quoi sers le choix du client et quel est le workflow. Faire
correspondre un type à un choix du client me parait pas très évolutif. En
plus ça mélange des opération business (appuyer un bouton) et design
(hierarchie de classe).

En général, un undo/redo est implémenté en terme de "memento" ou de
"command". Le fait que tu parles d'option me laisse penser qu'il s'agit d'un
mécanisme de "décoration" ou de "stratégie".
Si les combinaison d'option sont complexes, il y aura peut être lieu d'avoir
un système de "builder".

Voilà, c'est très générique mais ça me fait 5 points GOF :)



Merci de vos suggestions auxquelles je tâcherai de réfléchir.
Pour compléter ma description, je dirais qu'il s'agit de modéliser
différentes versions d'un produit (avec ses options) vendu par le biais
d'un terminal de paiement.

Mon code devra donc s'exécuter dans un environnement de type Windows
CE, et je cherche également à limiter l'occupation mémoire, donc le
nombre de classes et d'objets à créer.
Avatar
No_Name
Sylvain SF avait écrit le 1/27/2009 :
No_Name a écrit :

Par contre, pour le passage de C vers B, j'ai testé :

B *cq(C);



??

ou bien : B *cq = static_cast<B *>(cqc); (avec cqc pointant vers un objet
de type C)

mais à chaque fois, d'après le débuggeur, j'obtiens le même résultat : cq
pointe vers un objet bizarre, qui est un objet de type C auquel s'ajoutent
les propriétés d'un objet de type B.



c'est quoi "bizarre" ?

C hérite de B et Z, l'héritage multiple implique une composition
des tables des méthodes virtuelles de B et de C; caster un C* en B*
retourne ici un pointeur sur la zone où "commence" B dans cette
composition (cela peut être strictement l'adresse de B).

ce cast de *pointeur* est à 99% inutile car partout où ce 'cq'
sera utilisé le pointeur d'origine 'cqc' pourra l'être.
ta démarche est dès lors non claire.

Alors que mon but et d'obtenir un objet de type B à partir d'un
objet de type C, en perdant bien entendu les propriétés
supplémentaires qui font que C est une spécialisation de B.



ce que ne fait pas du tout un cas de pointeur, une adresse est
une adresse est une adresse.
si tu veux *créer* un objet B à partir de d'un C, il faut bien
le *créer*, soit B b = *cqc; ( ou B b(*cqc); )

Sylvain.



En effet, c'est bien cette dernière solution que j'ai retenue.

Merci.
Avatar
Sylvain SF
No_Name a écrit :

Par contre, pour le passage de C vers B, j'ai testé :

B *cq(C);



??

ou bien : B *cq = static_cast<B *>(cqc); (avec cqc pointant vers un
objet de type C)

mais à chaque fois, d'après le débuggeur, j'obtiens le même résultat :
cq pointe vers un objet bizarre, qui est un objet de type C auquel
s'ajoutent les propriétés d'un objet de type B.



c'est quoi "bizarre" ?

C hérite de B et Z, l'héritage multiple implique une composition
des tables des méthodes virtuelles de B et de C; caster un C* en B*
retourne ici un pointeur sur la zone où "commence" B dans cette
composition (cela peut être strictement l'adresse de B).

ce cast de *pointeur* est à 99% inutile car partout où ce 'cq'
sera utilisé le pointeur d'origine 'cqc' pourra l'être.
ta démarche est dès lors non claire.

Alors que mon but et d'obtenir un objet de type B à partir d'un
objet de type C, en perdant bien entendu les propriétés
supplémentaires qui font que C est une spécialisation de B.



ce que ne fait pas du tout un cas de pointeur, une adresse est
une adresse est une adresse.
si tu veux *créer* un objet B à partir de d'un C, il faut bien
le *créer*, soit B b = *cqc; ( ou B b(*cqc); )

Sylvain.
Avatar
Michael DOUBEZ
No_Name wrote:
Le but de tout cela est de permettre un "undo", c'est à dire l'annulation des choix du client.

Disons que le client fait un premier choix. On va donc générer en mémoire un objet de type B correspondant à son choix.

Ensuite il sélectionne une option. C'est là que je suis amené à passer d'un objet de type B à un objet de type C, correspondant à cette option.

Puis le client change d'idée et annule son choix. On doit alors revenir à un objet de type B, d'où l'option est retirée.

Voilà la raison des manipulations auxquelles je me livre ...

Voyez vous éventuellement une autre façon (un autre design ?) de
réaliser le même type de traitements et d'offrir le même genre de
possibilités ?



Ca dépends à quoi sers le choix du client et quel est le workflow. Faire
correspondre un type à un choix du client me parait pas très évolutif.
En plus ça mélange des opération business (appuyer un bouton) et design
(hierarchie de classe).

En général, un undo/redo est implémenté en terme de "memento" ou de
"command". Le fait que tu parles d'option me laisse penser qu'il s'agit
d'un mécanisme de "décoration" ou de "stratégie".
Si les combinaison d'option sont complexes, il y aura peut être lieu
d'avoir un système de "builder".

Voilà, c'est très générique mais ça me fait 5 points GOF :)

--
Michael
Avatar
pjb
No_Name writes:

Fabien LE LEZ a utilisé son clavier pour écrire :
On Tue, 27 Jan 2009 14:31:26 +0100, No_Name :

Parce que le new() que j'utilise pour créer mon objet me retourne
un pointeur ...



Mais typiquement, ce que new renvoie, on le stocke dans un pointeur
intelligent, pour éviter les fuites mémoire.

(Je dis "typiquement", car certains objets se détruisent tout seuls.)

Et question subsidiaire : pourquoi new est-il utilisé ici ?
Au moins dans le cadre de ce thread, ça ne fait que compliquer le
problème.
Cherche d'abord à bien comprendre les mécanismes de passage entre B et
C avec des objets normaux, et ensuite seulement, tu pourras ajouter,
si nécessaire, les complications liées à l'allocation dynamique.



En effet, mais la question que je posais s'inscrivait dans un
véritable projet. Il ne s'agit pas d'un exercice de style, et il me
faut bien adapter les réponses qui m'ont été faites à la réalité de
mon code.

Merci encore pour vos réponses qui m'ont permis de régler le problème.



Le problème est que si le code utilise new pour créer des objets, il
s'attend certainement à avoir une sémantique d'identité, pas de
valeur.

Sinon, il n'y aurait que des C c; B b; et des copies de valeurs: b=c;


--
__Pascal Bourguignon__
Avatar
pjb
No_Name writes:
Le but de tout cela est de permettre un "undo", c'est à dire
l'annulation des choix du client.

Disons que le client fait un premier choix. On va donc générer en
mémoire un objet de type B correspondant à son choix.

Ensuite il sélectionne une option. C'est là que je suis amené à passer
d'un objet de type B à un objet de type C, correspondant à cette
option.

Puis le client change d'idée et annule son choix. On doit alors
revenir à un objet de type B, d'où l'option est retirée.

Voilà la raison des manipulations auxquelles je me livre ...




Et ça ne serait pas plus simple de modéliser ce que tu dis plutôt?

+---------+ * +--------+
| Choix |<>----------------------| Option |
+---------+ +--------+
| commun | |
+---------+ __________/__________
| |
+---------+ +---------+
| OptionA | | OptionB |
+---------+ +---------+
| specifA | | specifB |
+---------+ +---------+

Alors il est trés simple de changer d'options ou même d'en voir plusieurs...


--
__Pascal Bourguignon__
Avatar
James Kanze
On Jan 27, 11:28 am, Fabien LE LEZ wrote:
On Tue, 27 Jan 2009 11:32:11 +0100, Michael DOUBEZ
:



>En supposant que B ait un constructeur par copie.



Y a-t-il des cas où une classe n'a pas de constructeur par
copie ?



Ça dépend de la définition d'« avoir ». Il y a bien toujours
une déclaration de la fonction dans toute classe, mais pas
forcément une définition. (J'aurais tendance à associer
« avoir » avec la définition ; c'est la définition qui crée,
en quelque sort, la fonction, et on ne peut pas avoir quelque
chose qui n'est pas. Mais c'est une question de langage. La
norme ne parle jamais d'« avoir » dans ce cas.)

--
James Kanze (GABI Software) email:
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
James Kanze
On Jan 27, 3:11 pm, Fabien LE LEZ wrote:
On Tue, 27 Jan 2009 14:31:26 +0100, No_Name :



>Parce que le new() que j'utilise pour créer mon objet me
>retourne un pointeur ...



Mais typiquement, ce que new renvoie, on le stocke dans un
pointeur intelligent, pour éviter les fuites mémoire.



Il n'y a pas de « typique ». Tout dépend de l'objet, et
pourquoi on l'a alloué dynamiquement, et dans la plupart des
applications, l'utilisation des pointeurs intelligents doit être
plutôt l'exception.

(Je dis "typiquement", car certains objets se détruisent tout seuls.)



Et d'autres sont « gérés » par une entité externe unique (base
de données, etc.). Et en dehors des ces deux cas, c'est plutôt
exceptionnel d'allouer dynamiquement, au moins au niveau de
l'application. (Il y aurait bien des allocations dynamiques à
l'intérieur des std::vector et des std::map, mais en tant
qu'utilisateur, je ne les connais pas.)

Et question subsidiaire : pourquoi new est-il utilisé ici ?



Je crois en somme que tout la question est là.

Il y a aussi la question de ce que doit représenter ces objets.
Il y a un point fondamental, et il m'étonne que personne (dans
les réponses que j'ai vues jusqu'ici) ne l'a cité : il est
absolument impossible de changer le type d'un objet en C++. Le
type est établi par le constructeur, et reste ensuite
invariable. Il faut donc savoir ce qu'il veut faire réelement
pour pouvoir suggérer une solution alternative : si les objets
n'ont pas d'identité, créer un nouvel objet avec le nouveau type
pourrait être la bonne solution. Mais si les objets n'ont pas
d'identité, je ne vois pas ce que l'allocation dynamique a à
faire là dedans ; on n'alloue dynamiquement que si l'objet a
une identité et une durée de vie qui ne correspond pas à une
durée de vie standard (ou si on ne connaît pas le type ou la
taille lors de la compilation).

Si l'objet a une identité, c'est plus complex ; il faudrait
probablement un espèce de proxie qui sait présenter l'interface
d'une B ou d'une C, selon le cas, et qui implémente l'une ou
l'autre au moyen du modèle de stratégie.

--
James Kanze (GABI Software) email:
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 3 4