IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

 C++ Discussion :

Comment modifier les objets passés en paramètres ?


Sujet :

C++

  1. #1
    Membre du Club
    Homme Profil pro
    Inscrit en
    Mai 2012
    Messages
    7
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Mai 2012
    Messages : 7
    Par défaut Comment modifier les objets passés en paramètres ?
    Bonsoir,
    J'ai écrit ces trois fonctions pour tester et je voudrais savoir si elles sont bonnes et le cas contraire, quelles sont les erreurs commises. Le but est de modifier l'objet p passé en paramètre en créant un nouvel objet et en l'assignant à p.
    Je vous remercie par avance de votre aide qui me permettra d'avancer.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    void f(Personnage** p) {
    	Personnage* jeanbis = new Personnage("jeanbis");
    	delete *p; // le delete est bon ?
    	jeanbis->setVie(-1);
    	*p = jeanbis;
    }
     
    /**
     * Cette fonction est fausse, right ?
     */
    void h(Personnage& p) {
    	Personnage hervebis("hervebis");
    	hervebis.setVie(-2);
    	p = hervebis;
    }
     
    /**
     * La même que celle au dessus mais par allocation dynamique
     */
    void hbis(Personnage& p) {
    	Personnage* pouletbis = new Personnage("pouletbis");
    	pouletbis->setVie(-3);
    	p = *pouletbis;
    	delete pouletbis; // le delete est bon ?
    }
     
    /**
     * bidon, pour afficher
     */
    void affiche(Personnage const& p) {
    	p.toString();
    }
     
    int main() {
    	Personnage* jean = new Personnage("jean");
    	Personnage herve("herve");
    	Personnage poulet("poulet");
    	f(&jean);
    	h(herve);
    	hbis(poulet);
    	cout << "jean :: 0 est devenu ";
    	affiche(*jean);
    	cout << "herve :: 0 est devenu ";
    	affiche(herve);
    	cout << "poulet :: 0 est devenu ";
    	affiche(poulet);
    	delete jean;
    }
    Résultat obtenu :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    jean :: 0 est devenu jeanbis :: -1
    herve :: 0 est devenu hervebis :: -2
    poulet :: 0 est devenu pouletbis :: -3

  2. #2
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 635
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 635
    Par défaut
    Salut,


    Je vais commencer par répondre aux questions que tu poses en commentaire
    • oui, delete est bon et au bon endroit dans f()
    • non, h n'est pas mauvaise, et est tout à fait valide, du moins, tant que Personnage a sémantique de valeur (*)
    • oui, delete est bon et au bon endroit dans hbis()

    Du seul point de vue du langage, aucune des fonctions que tu présentes n'est fondamentalement fausse ni meme invalide (comprends : elle passent toutes à la compilation), et toutes vont meme jusqu'à éviter les fuites mémoire, du moins, tant que ta classe Personnage a sémantique de valeur et non sémantique d'entité (*)

    Par contre, toutes les fonctions présentent le même problème : elles ont comme effet très désagréable de remplacer l'objet qu'elle prennent en paramètre par un objet qu'elles créent elles-meme

    Et la pire de toute, tant du point de vue de l'esthétique que du point de vue de la facilité d'utilisation est sans doute la première car si l'utilisation de pointeur n'est déjà pas forcément aisée (il faut toujours penser à les déréférencer correctement ), que dire de l'utilisation de pointeurs de pointeurs

    Qu'il te suffise, par exemple, de rajouter à ta classe personnage une fonction qui renvoie le nom du personnage et de le faire afficher avant et après l'appel des fonctions et tu verra que "jean" est devenu "jeanbis", que "herve" est devenu "herevebis" et que poulet est devenu "pouletbis"

    Cela signifie que, si ta classe personnage dispose d'autres attributs et que ces attributs ont été modifiés avant que tu n'appelles ces fonctions, tu perdras irrémédiablement les valeurs correspondant à ces autres attributs, et ca, ce serait particulièrement dommageable dans un programme réel

    Je présumes d'ailleurs que, à choisir, tu préférerais que jean continue à s'appeler jean et qu'il en aille de meme pour hervé et pour poulet

    Dés lors, étant donné que tu transmets, de toutes manières, ton objet en paramètre et qu'il n'est pas transmis sous forme constante, pourquoi ne pas, tout simplement, agir directement sur le paramètre lui-même
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    void f(Personnage** p) {
    	Personnage* jeanbis = new Personnage("jeanbis");
    	delete *p; // le delete est bon ?
    	jeanbis->setVie(-1);
    	*p = jeanbis;
    }
    serait avantageusement remplacée par
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    void f(Personnage* p) {
    	p->setVie(-1);
    }
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    void h(Personnage& p) {
    	Personnage hervebis("hervebis");
    	hervebis.setVie(-2);
    	p = hervebis;
    }
    serait remplacée par
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    void h(Personnage& p) {
           p.setVie(-2);
    }
    et
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    void hbis(Personnage& p) {
    	Personnage* pouletbis = new Personnage("pouletbis");
    	pouletbis->setVie(-3);
    	p = *pouletbis;
    	delete pouletbis; // le delete est bon ?
    }
    deviendrait
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    void hbis(Personnage& p) {
    	pouletbis.setVie(-3);
    }
    D'ailleurs, étant donné que seule la valeur change entre h et hbis, on pourrait très bien envisager de factoriser cela en une seule et meme fonction, en passant également la valeur en argument.

    Les deux fonctions pourraient donc être remplacée par une seule fonction proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    void hter(Personnage & p, int valeur)
    {
        p.setVie(valeur);
    }
    qui serait appelée sous une forme proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    int main() {
    	Personnage herve("herve");
    	Personnage poulet("poulet");
    	hter(herve, 2);
    	hter(poulet, 3);
    	std::cout << "herve :: 0 est devenu ";
    	affiche(herve);
    	std::cout << "poulet :: 0 est devenu ";
    	affiche(poulet);
    }
    Ceci dit, le code mets en évidence un aspect qui me dérange fortement : la présence du mutateur "setVie"

    D'abord, parce que je te vois mal, dans un programme réel, décider "arbitrairement" du nombre de vies dont dispose un personnage à un autre moment que lors de sa création (et, à ce moment là, cela devrait etre pris en charge par le constructeur).

    Une fois que l'objet a été créé, setVie aura l'énorme inconvénient de t'obliger à calculer le nombre à transmettre en paramètre en fonction du nombre de vies qu'il reste effectivement au personnage, et donc... de devoir interroger le personnage sur son nombre de vies actuel

    Dans un code réel, tu serais donc obligé de créer une fonction proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    void h(Personnage & p)
    {
       p.setVie(p.getVie() - /* ou +, selon le cas */ XXX);
    }
    Or, le propre du paradigme orienté objet (en dehors de son fondement meme qui est de permettre la substituabilité), c'est que l'on doit penser nos classes non plus sur base des données qu'elles contiennent mais bien sur base des services que l'on en attend.

    La modification du nombre de vie de ton personnage devrait donc prendre la forme de deux comportements que tu es susceptible d'en attendre, par exemple:
    • gagneVie(/* nombre de vie éventuel à rajouter */) qui augmenterait le nombre de vies de 1 (ou du nombre de vie à rajouter)
    • perdVie(/* nombre de vie éventuel à retirer */ qui diminuerait le nombre de vies de 1 (ou du nombre de vie à retirer)
    Cela intervient dans le respect de ce que l'on appelle la loi demeter qui, appliquée à l'informatique, dit que si une classe (ou une fonction) A (comme ta fonction h) manipule un objet de type B (comme le paramètre de type Personnage) et que B utilise en interne un objet de type C (ici, une variable de type numérique correspondant au nombre de vie dont dispose le personnage en question ), la classe (ou la fonction) A n'a absolument pas besoin de savoir comment B gère l'information C en interne pour pouvoir travailler sur B.

    Autant le dire tout de suite, pour ta classe Personnage et son nombre de vie, je coupe peut etre les cheveux en quatre, mais prenons une classe un peu plus complexe : une éventuelle classe Voiture.

    On se doute qu'une voiture utilise un réservoir d'essence, qui présente certaines caractéristiques (dont, entre autres, la capacité maximale et le niveau de remplissage).

    En tant qu'utilisateur de la classe Voiture, tu ne devrais pas avoir à écrire un code proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    void foo(Voiture & v)
    {
        v.getReservoir().setQuantite(45); // défini la quantité d'essence du réservoir à 45 litres
    }
    parce que, non seulement tu devrais avoir connaissance de la classe Voiture (ce qui est normal, vu que tu l'utilise), mais tu devrais, en plus, avoir connaissance de la classe Reservoir, à lors que, en tant qu'utilisateur (non mécanicien ) tu n'as absolument aucun besoin justifié de connaitre la classe Réservoir.

    L'idée est donc de dire que l'un des comportements de la voiture est de pouvoir accepter un surplus d'essence, voir d'accepter suffisamment d'essence pour faire le plein.

    On se doutera bien, parce qu'on n'est pas idiot, que la voiture utilisera en interne une classe Réservoir (et encore, rien n'empêche qu'elle ne dispose pas, tout simplement d'un entier représentant la capacité maximale et d'un autre représentant le niveau de remplissage ), mais on pourra simplement ordonner à la voiture "rajoutes XXX litres d'essence ou "fais le plein et indique moi la quantité que tu as rajoutée".

    De cette manière, quelle que soit la représentation interne du système qui permet à la voiture de savoir de combien de litre d'essence elle dispose (que ce soit une classe à part ou plusieurs entiers représentant certaines valeurs), on pourra utiliser notre voiture sans nous poser de question, et sans devoir changer tout notre code si, un jour, nous venions à décider de changer la représentation interne qui permet à la voiture de savoir de combien de litre d'essence elle dispose.

    C'est ce que l'on appelle l'encapsulation

    (*) Bien qu'il traite également d'un sujet qui te passera sans doute au dessus de la tete (la sémantique de mouvement n'est sans doute pas un sujet très orienté pour les débutants), la lecture de cet excellent billet d'Emmanuel te permettra sans doute de comprendre ce que la sémantique de valeur et la sémantique d'entité

    PS : je passe, en moyenne, plusieurs fois par jour sur le forum, il ne sert à rien de m'envoyer un MP pour demander que je fasse un tour sur une discussion ou sur une autre
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  3. #3
    Membre du Club
    Homme Profil pro
    Inscrit en
    Mai 2012
    Messages
    7
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Mai 2012
    Messages : 7
    Par défaut
    Merci pour cette réponse complète et ces explications qui m'ont fait comprendre certaines choses, mais ont soulevé de nouveaux points incompris.

    Il est vrai que ces fonctions sont vraiment inutiles, le fait de remplacer un objet par un autre est idiot, mais c'était pour voir si c'était bien ou mal fait.

    Quand tu dis :
    Du seul point de vue du langage, aucune des fonctions que tu présentes n'est fondamentalement fausse ni meme invalide (comprends : elle passent toutes à la compilation), et toutes vont meme jusqu'à éviter les fuites mémoire, du moins, tant que ta classe Personnage a sémantique de valeur et non sémantique d'entité (*)
    Déjà, je n'ai pas avancé jusqu'à ce point dans mon apprentissage, je viens de voir ce que c'était dans la faq c++ du site.
    Cependant je ne comprends pas ce que tu dis par là. Ma classe Personnage a-t'elle une sémantique de valeur ? Quels seraient les problèmes avec ces fonctions si c'était une sémantique d'entité ?
    Pour l'instant dans ma classe Personnage je n'ai pas redéfini les opérateurs, n'a-t'elle pas une sémantique d'entité (deux personnages ne pouvant être égaux) ?

    Quand tu parles de
    effet très désagréable de remplacer l'objet qu'elle prennent en paramètre par un objet qu'elles créent elles-meme
    je rajoute que c'était pour tester. Mais une application me vient à l'esprit.
    Par exemple imaginons qu'un Personnage ait un apôtre qui le suit. Arrivé à un checkpoint l'apôtre se fait harakiri et un nouvel apôtre le remplace (cf. delApotreAndCreateApotreBecauseOfCheckpoint)
    Voici mon code. Je l'ai testé, compilé, çà a l'air de marcher, et avec valgrind il n'y a pas de fuites.
    Y a-t'il des erreurs ? Est-ce mal fait ?
    Nota : j'ai bien pris en compte ta remarque sur l'encapsulation, j'y penserai désormais.

    Personnage.h
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    #ifndef PERSONNAGE_H_
    #define PERSONNAGE_H_
     
    #include <string>
     
    class Personnage {
        friend std::ostream& operator<<(std::ostream& output, const Personnage& p);
     
    public:
    	Personnage(std::string name);
    	virtual ~Personnage();
    	Personnage* getApotre();
    	void setApotre();
    	void setApotre(Personnage* p);
    	void addVie(int x);
     
    private:
    	int m_vie;
    	std::string m_name;
    	Personnage *m_apotre;
    };
     
    #endif /* PERSONNAGE_H_ */
    Personnage.cpp
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    #include "Personnage.h"
    #include <iostream>
    using namespace std;
     
    ostream& operator<<(ostream& output, const Personnage& p) {
    	output << "[" << p.m_name << "(" << p.m_vie << ")";
    	if (p.m_apotre != NULL) {
    		output << ", apotre:" << *(p.m_apotre);
    	}
    	output << "]";
    	return output;
    }
     
    Personnage::Personnage(string name) {
    	m_vie = 0;
    	m_name = name;
    	m_apotre = NULL;
    }
     
    Personnage::~Personnage() {
    	cout << "Suppresion de " << *this << endl;
    }
     
    Personnage* Personnage::getApotre() {
    	return m_apotre;
    }
     
    void Personnage::setApotre(Personnage* p) {
    	m_apotre = p;
    }
     
    void Personnage::setApotre() {
    	m_apotre = new Personnage("Juda");
    }
     
    void Personnage::addVie(int x) {
    	m_vie += x;
    }
    Main.cpp
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    #include <iostream>
    #include <string>
    #include "Personnage.h"
    using namespace std;
     
    void delApotre(Personnage& p) {
    	delete p.getApotre();
    	p.setApotre(NULL);
    }
     
    void delApotreAndCreateApotreBecauseOfCheckpoint(Personnage& p) {
    	p.addVie(10);
    	Personnage* yahve = new Personnage("Yahve");
    	yahve->addVie(1);
    	delete p.getApotre();
    	p.setApotre(yahve);
    /* c'est comme çà qu'on fait pour supprimer un objet créé avec new et en créer un nouveau ? */
    }
     
    int main() {
    	Personnage jesus("Jesus");
    	jesus.setApotre();
    	cout << jesus << endl;
    	delApotreAndCreateApotreBecauseOfCheckpoint(jesus);
    	cout << jesus << endl;
    	delApotre(jesus);
    	cout << jesus << endl;
    }
    Résultat obtenu :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    [Jesus(0), apotre:[Juda(0)]]
    Suppresion de [Juda(0)]
    [Jesus(10), apotre:[Yahve(1)]]
    Suppresion de [Yahve(1)]
    [Jesus(10)]
    Suppresion de [Jesus(10)]


    EDIT : j'avais oublié de regarder l'article d'emmanuel, je vais le lire.

    EDIT2: D'ailleurs j'ai utilisé un pointeur pour l'apôtre m_apotre, au début j'avais mis :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    Personnage m_apotre;
    // Field 'm_apotre' has incomplete type. Pourquoi ? Est-ce du à la récursion ou autre ?

  4. #4
    Expert éminent
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 394
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 394
    Par défaut
    La différence entre sémantique de valeur et sémantique d'entité, si je me souviens bien, est ce qui fait "l'identité" d'un objet, ainsi que ce qui fait que deux objets soient considérés identiques.

    En gros, une copie est-elle considérée comme étant "un autre objet" que l'original?
    • Une "personne" aura généralement une sémantique d'entité: deux personnes ayant le même nom restent deux personnes différentes.
    • Une chaîne de caractères (surtout s'il est impossible de la modifier) aura généralement une sémantique de valeur: c'est son contenu qui est important, deux chaînes identiques comptent comme "la même chaîne".


    Edit: "Incomplete type" fait généralement référence à un type qui n'a eu qu'une déclaration d'une ligne au lieu d'une définition complète. Mais là, c'est plutôt lié au fait que tu tentes d'inclure un objet dans lui-même, ce qui est impossible: Il y aurait une infinité de personnages contenant chacun un personnage contenant chacun un personnage etc. comme des poupées russes!
    SVP, pas de questions techniques par MP. Surtout si je ne vous ai jamais parlé avant.

    "Aw, come on, who would be so stupid as to insert a cast to make an error go away without actually fixing the error?"
    Apparently everyone.
    -- Raymond Chen.
    Traduction obligatoire: "Oh, voyons, qui serait assez stupide pour mettre un cast pour faire disparaitre un message d'erreur sans vraiment corriger l'erreur?" - Apparemment, tout le monde. -- Raymond Chen.

Discussions similaires

  1. comment modifier les paramétres régionaux
    Par chekkal dans le forum Débuter
    Réponses: 17
    Dernier message: 11/11/2013, 09h05
  2. Comment modifier les paramètres vidéos ?
    Par hellotk dans le forum Général JavaScript
    Réponses: 3
    Dernier message: 04/04/2011, 23h39
  3. Réponses: 1
    Dernier message: 14/08/2009, 08h50
  4. Comment modifier les paramètres de ce merveilleux éditeur wysywyg tout simple ?
    Par Alexandrebox dans le forum Général JavaScript
    Réponses: 21
    Dernier message: 07/05/2009, 17h02
  5. Réponses: 0
    Dernier message: 22/11/2007, 12h42

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo