J'essaie de faire un petit programme en c++ dans lequel je crée une graine de la classe Mousse, qui hérite de la classe Arbre. Je souhaite ensuite la planter dans une plantation de la classe Plantation.
Mousse graine; //création de la graine
Plantation plantation; // création de la plantation
plantation.recevoirGraine(graine); // assigner la graine à la plantation
plantation.arroser(); // appel à la fonction arroser de la plantation, qui fait appel à la fonction recevoirEau de la graine
graine.afficherInfo(); // afficher les infos de la graine avec la fonction afficherInfo de graine
plantation.afficherInfoP(); afficher les infos de la graine avec la fonction afficherInfoP de plantation, qui fait appel à la fonction afficherInfo de Arbre
graine.afficherInfo(); // afficher les infos de graine avec la fonction afficherInfo de graine
Les fonctions recevoirGraine, arroser et afficherInfoP de la classe Plantation sont celles-ci:
void Plantation::recevoirGraine(Arbre agraine)
{
if(occupe())
{
cout<<"Cette parcelle est déjà occupée."<<endl;
}
else
{
m_graine = &agraine;
}
}
void Plantation::afficherInfoP()
{
if(occupe())
{
m_graine->afficherInfo(); //Cela fait appel à la Arbre::afficherInfo() plutôt que de faire appel à Niche::afficherInfo()
}
else
{
cout<<"Parcelle vide"<<endl;
cout<<"Taille: "<<endl;
}
}
void Plantation::arroser()
{
if(occupe())
{
m_graine->recevoirEau();
}
else
{
cout<<"Il n'y a rien à arroser sur cette parcelle."<<endl;
}
}
et la fonctions afficherInfo de la classe Mousse est:
Il y a deux problèmes que je ne comprend pas. Lorsque je fais:
plantation.afficherInfoP();
La fonction afficherInfoP fait appel à la fonction afficherInfo de la classe Arbre et non de la classe Mousse, alors que mon objet est de la classe Mousse et que les fonctions sont en virtual.
L'objet graine n'est pas mis à jour, et tout se passe comme si la graine n'avait pas été arrosé. Mais l'objet plantation, (qui utilise le afficherInfo de la classe Arbre plutôt que celui de la classe Mousse) renvoit bien des informations qui ont été modifiées par la fonction arroser.
Savez-vous d'où cela peut venir?
Merci beaucoup!
- Edité par HélèneRousseau1 19 février 2024 à 19:24:23
Pour expliquer, imagine l'héritage comme des boites. Ta classe Mousse qui dérive de ta classe Arbre, c'est comme si tu avais une "boite" Mousse qui contient une "boite" Arbre.
Le problème avec cette ligne de code, c'est que tu passe par valeur une "boite" Arbre. Donc c'est comme si tu prenais ta "boite" Mousse de graine et que tu copies uniquement la "boite" Arbre qu'elle contient. Tu perds toutes les informations relatives a ta "boite" Mousse.
Passer par un pointeur (ou une référence -- mais tu as commencé avec des pointeurs, continue avec ça) permet de passer un lien vers ta "boite" Mousse et donc ne pas perdre les informations d'une classe dérivée.
void Plantation::recevoirGraine(Arbre* agraine)
{
if(occupe())
{
cout<<"Cette parcelle est déjà occupée."<<endl;
}
else
{
m_graine = agraine;
}
}
plantation.recevoirGraine(&graine); // assigner la graine à la plantation
Dans tous les cas, c'est "en argument". Mais il existe plusieurs façons de passer un argument, par exemple :
void foo(int i); // passage par valeur
void foo(const int& i); // passage par référence constante
void foo(int& i); // passage par référence
void foo(int* i); // passage par pointeur
etc.
Le passage par valeur (la première ligne -- et ce que tu avais écrit) fait une copie de Arbre et c'est ça qui pose problème. Si tu avais passé une copie de Mousse par exemple, ça aurait marché aussi (mais cela pose d'autres problèmes de faire des copies -- mais laissons de coté pour le moment).
Les autres lignes permettent de faire une indirection sur l'objet et donc de conserver toutes les informations. C'est ce qu'il y a de mieux à faire.
Tout ça pour dire que c'est pas juste "passer en argument" qui pose problème, mais la façon de passer l'argument.
Déjà, la classe mousse qui hérite de arbre, ca me semble vachement suspect
Parce que cela voudrait dire que toute mousse est un arbre avec -- peut-être -- "quelque chsoe de plus".
Et ca, ca a vraiment du mal à passer...
Qu'à la limite, tu fasses hériter des classes comme Chene, Sapin, Hetre, Noyer ou Pommier, cela pourrait passer, car ce sont effectivement tous des arbres, mais dire qu'une mousse peut être un arbre, définittvement, non... Tu te ferais passer pour un imbécile face à n'importe quel étudiant en agornomie
L'objet graine n'est pas mis à jour, et tout se passe comme si la graine n'avait pas été arrosé. Mais l'objet plantation, (qui utilise le afficherInfo de la classe Arbre plutôt que celui de la classe Mousse) renvoit bien des informations qui ont été modifiées par la fonction arroser.
Encore une fois, c'est normal... Parce que, encore une fois, tu as transmis la graine ... par valeur, ce qui a engendré une copie de la graine (enfin, de la composante Arbre de la graine). La graine qui se trouve dans la plantation n'a donc absolument rien à voir avec la graine que tu as transmis à la plantation, ce qui explique que, si tu demande à la graine qui est hors de la plantation d'afficher ses informations, ben, elle ne profite absolument pas des soins apportés par la plantation, car ... la plantation s'occupe d'une autre graine
Et pour finir, il y a énormément d'erreur dans ton code, cependant, comme il est incomplet et qu'il ne nous permet absolument pas de nous faire une idée de la manière dont tu l'utilise effectivement, il nous sera très difficile de te conseiller quant aux corrections à apporter.
Peut-être devrais tu mettre ton code complet sur un serveur public git (github,ou autre), que l'on puisse avoir l'ensemble du code et s'arranger pour le faire compiler
Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
Un truc important pour gagner ENORMEMENT de temps en développant, c'est de faire en sorte de configurer le projet pour que le compilateur détecte, en plus des erreurs, des raisons d'avoir des soucis, et nous mette des avertissements. Le compilateur est votre ami, il a le courage de vous dire franchement que vous faites un truc qui n'a pas l'air bien, même si c'est "légal".
Sur le code qui suit, version simplifié de celui de dessus, avec l'option -Wall de gcc on a l'avertissement
$ g++ -Wall code.cc
code.cc: In member function ‘void Plantation::recoitGraine(Arbre)’:
code.cc:16:14: warning: storing the address of local variable ‘a’ in ‘*this.Plantation::m_graine’ [-Wdangling-pointer=]
16 | m_graine = &a;
| ~~~~~~~~~^~~~
code.cc:15:39: note: ‘a’ declared here
15 | void Plantation::recoitGraine(Arbre a) {
| ~~~~~~^
traduction et explication :
le paramètre de recoitGraine est passé par valeur
quand on rentre dans la fonction, une copie temporaire de cette valeur est faite, mise dans la variable locale a, qui sera détruite - la variable locale et la copie avec - en sortant de la fonction.
l'affectation m_graine = &a; met l'adresse de la copie dans la variable membre (= le champ, l'attribut) m_graine.
quand on ressort de la fonction la copie étant détruite, le pointeur désigne un emplacement qui sera utilisé très vite pour autre chose : il pointe "dans la nature" (dangling pointer)
et du coup, c'est à peu près certain que ça va faire planter le programme, quelque part. Souvent sur une opération qui apparemment n'a rien à voir. Le genre de truc mystérieux (*) où on s'arrache les cheveux pendant des heures.
(*) Pas si mystérieux : si on fait des trucs avec le pointeur, ça bousille le contenu d'une donnée qui occupe l'emplacement mémoire où était la copie temporaire. Et inversement. Mais bon, savoir sur qui ça tombe, c'est une autre paire de manches.
Moralité :
toujours mettre les options de compilation qui provoquent un maximum d'avertissements.
avec g++, j'utilise -Wall et -Wextra. Ca dépend du compilateur utilisé. Voir configuration du projet.
dans un programme de débutant, tous ces avertissements doivent être pris en compte et éliminés. En corrigeant le code, pas en supprimant les options.
dans un environnement professionnel ça peut arriver d'avoir du code tordu avec des cas qui déclenchent des avertissements alors qu'on fait des trucs honnêtes malgré tout. Du moins on estime que. Et/ou on préfère ne pas se mettre à corriger le code qui est vieux et mal écrit, avec autant de chances de le casser que de le réparer. Don't fix it if it ain't broken. Et des heures de boulot consommées (time is money) pour la bonne cause, au lieu de faire des trucs qu'on pourrait facturer au client.
EDIT: zut j'avais oublié de mettre le code, que j'ai rebidouillé après pour essayer un truc. Le voili le voila
#include <iostream>
class Arbre {
};
class Plantation
{
private:
Arbre *m_graine;
public:
void recoitGraine(Arbre a); // NO GOOD
};
void Plantation::recoitGraine(Arbre a) { // NO GOOD
m_graine = & a;
}
int main() {
Arbre a;
Plantation p;
p.recoitGraine(a);
}
A faire : passer le paramètre par référence (Arbre &a)
PS, après, si la plantation n'avait qu'un type d'arbre défini une fois pour toute, la variable membre m_graine pourrait être une référence initialisée dans le constructeur.
ce qui évitera d'avoir à se trimballer un pointeur, mais ne résoudra pas le fond du problème qui fache, quid d'une plantation construite à partir d'un arbre (peut être temporaire) qu'on a détruit ensuite ? Crac boum badaboum. La gestion des durées de vie des objets, faut pas compter sur C++. Voir des trucs plus safe, comme Rust.
- Edité par michelbillaud 19 février 2024 à 23:51:23
Pointeur d'une classe vers une autre
× Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
× Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
Discord NaN. Mon site.
Discord NaN. Mon site.