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

Langage C++ Discussion :

Une histoire de pointeurs


Sujet :

Langage C++

  1. #1
    Membre du Club
    Homme Profil pro
    Étudiant
    Inscrit en
    Novembre 2011
    Messages
    129
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Novembre 2011
    Messages : 129
    Points : 68
    Points
    68
    Par défaut Une histoire de pointeurs
    Bonjour à tous !

    Alors voilà, j'ai une question d'ordre technique. D'habitude, je ne suis pas gêné par l'utilisation des pointeurs, mais là, j'ai été étonné de constater que ce que je connaissais pouvait être remis en doute et j'aimerais bien être fixé sur quelques points.

    J'explique ma question avec un exemple qui devrait bien illustrer le problème :

    Imaginons 3 objets : Un programme, une Lecteur de données, et une Base de données.

    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
     
    class Program
    {
    	Program::Program() 
    	{
    		myReader = NULL;
    		myBD = NULL;
    	}
     
    	// Lecture de la BD
    	void Program::Read()
    	{
    		myReader = new Reader(myBD);
    	}
     
    	private : 
    		BD* myBD;
    		Reader* myReader;
    }
     
    class Reader
    {
    	public :
    		Reader::Reader(BD* bd)
    		{
    			// Initialise la Base de données si elle est nulle
    			if(bd == NULL)
    				bd = new BD();
     
    			// Récupère le pointeur
    			_bd = bd;
    		}
     
    	private :
    		BD* _bd;
    }
    Je crée une instance de programme. puis j'appelle la fonction Read() une première fois.
    Dans l'idée que je me faisais, l'objet BD pointé est modifié, et un second appel de la fonction Read() ne devrait pas le réinstancier... Même si le Reader est réalloué, la BD elle, n'est pas remise à NULL.
    Mais visiblement, je me trompe. Dans mon programme, l'équivalent de mon objet BD est toujours à NULL au second appel de Read...

    Comment se fait-il ?

    Une idée pour palier le problème ? J'ai pensé à utiliser une référence, mais je suis plus à l'aide (d'habitude) avec les pointeurs, justement parce que j'ai l'habitude de déclarer mes objets une seule et une seule fois pour ne pas surcharger la mémoire.

  2. #2
    Membre éclairé Avatar de MythOnirie
    Homme Profil pro
    Développeur décisionnel
    Inscrit en
    Juin 2012
    Messages
    376
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    Localisation : France, Puy de Dôme (Auvergne)

    Informations professionnelles :
    Activité : Développeur décisionnel

    Informations forums :
    Inscription : Juin 2012
    Messages : 376
    Points : 795
    Points
    795
    Par défaut
    Tu envoi au constructeur de Reader le pointeur par copie. Il faudrait le passer par adresse afin que celui-ci puisse entre modifié dans ton objet Program.

  3. #3
    Membre actif
    Profil pro
    Inscrit en
    Avril 2008
    Messages
    159
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2008
    Messages : 159
    Points : 224
    Points
    224
    Par défaut
    Hello,

    Si tu utilises des pointeurs tu devrais te pencher sur le RAII http://cpp.developpez.com/faq/cpp/?p...POINTEURS_raii
    Car dans ton exemple, comme vas-tu gérer correctement la libération de ta mémoire ? Comment peux-tu savoir qui est responsable d'effectuer les `delete` ?

    Sinon, le problème vient en effet du fait que lorsque tu passes un pointeur à une fonction, ce pointeur (donc la variable contenant l'adresse pointée) est copié. Tu peux donc agir avec effets de bords sur la valeur pointée en déréférençant ton pointeur, mais toute manipulation sur le pointeur lui-même (ici changement d'adresse pointée) sera limité à la portée de ta fonction.

    Je pense qu'en pensant RAII ton problème disparaîtra de lui même, et tu gagneras en simplicité de gestion de la mémoire. (car dans le code présenté, je ne sais pas si c'est voulu mais il y a des fuites, et les doubles delete vont être difficiles à éviter).

  4. #4
    En attente de confirmation mail

    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2004
    Messages
    1 391
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 33
    Localisation : France, Doubs (Franche Comté)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Points : 3 311
    Points
    3 311
    Par défaut
    Bonjour,

    Tu touches là à ce qui fait, entre autre, la différence entre pointeur et référence. Les références ont une seule sémantique, celle de référence, une référence n'est qu'une nouvelle variable pour un objet déjà existant. Alors que le pointeur, en plus de cette sémantique de référence (il permet aussi de se référer à un objet), a une sémantique de valeur, le pointeur est avant tout un valeur (l'adresse) qu'on utilise pour se référer à un objet.

    Cette différence est importante, quand on parle de "modifier une référence", bien que syntaxiquement fausse, la phrase n'a pas d'ambiguité : ce que l'on modifie est l'objet qu'elle réfère. Dans le cas d'un pointeur l'ambiguité existe, est-ce la valeur du pointeur ou l'objet pointé ? Syntaxiquement cette différence se traduit par l'utilisation, ou non, de l'opérateur *.

    Un second point important est de se souvenir qu'en C++, on a deux modes de passage des paramètres (du moins je ne vais en considérer que deux pour simplifier) : par valeur et par référence. Dans le cas d'un passage par valeur, la fonction travaillera sur une copie de la valeur et n'aura donc aucune incidence sur la valeur d'origine, alors que dans le cas d'un passage par référence, elle pourra modifier celle-ci (ie l'objet référé).

    Maintenant observons ton constructeur de Reader :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
    Reader::Reader(BD* bd)
    {
    	// Initialise la Base de données si elle est nulle
    	if(bd == NULL)
    		bd = new BD();
     
    	// Récupère le pointeur
    	_bd = bd;
    }
    Tu effectues ici un passage par valeur d'un pointeur, ainsi la sémantique des pointeurs (à la fois référence et valeur) t'ammene à une dualité :
    • La valeur du pointeur (l'adresse : bd) est passé par valeur, ainsi tu ne pourras pas modifier la valeur "original" du pointeur.
    • L'objet référé par le pointeur (*bd) est passé par référence, ainsi tu peux le modifier.

    Or dans la suite tu as cette syntaxe :
    Mais comme bd est passé par valeur, la modification que tu effectues ici n'a pas d'incidence sur la valeur originale. Ainsi si l'on reprend ton appel à ce constructeur :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    void Program::Read()
    {
    		myReader = new Reader(myBD);
    }
    On en déduit que le pointeur qui est passé (myBD) n'est jamais modifié (par cet appel) et vaut donc NULL, ainsi à chaque appel du constructeur de Reader, la même branche conditionnelle est choisie.

    Venons en à une solution. Tu veux donc modifier la valeur du pointeur. On a vu que pour modifier un objet il fallait utiliser un passage par référence :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
    Reader::Reader(BD*& bd)
    {
    	// Initialise la Base de données si elle est nulle
    	if(bd == NULL)
    		bd = new BD();
     
    	// Récupère le pointeur
    	_bd = bd;
    }
    Deux petites remarques :
    • Tu devrais prendre l'habitude d'utiliser la liste d'initialisation pour tes constructeurs :
      Code : Sélectionner tout - Visualiser dans une fenêtre à part
      1
      2
      3
      4
      5
       
      Program::Program() : myReader(nullptr), myBD(nullptr)
      { }
      Reader::Reader(BD*& bd) : _bd( bd ? bd : bd = new BD() )
      { }
      J'ai utiliser nullptr (C++11) à la place de NULL (ou 0). Et pour le constructeur de Reader, je comprends qu'on puisse le trouver plus lisible avec un if dans le corps de la fonction. (Par contre mettre == NULL est superflux, AMA)
    • Renseignes toi sur les pointeurs intelligents. Si tes classes Program et Reader on la responsabilité de la durée de vie des données qu'elles réfèrent, alors utiliser un std::unique_ptr/boost::scoped_ptr pourrait être une solution. Ca pourrait t'éviter des fuites mémoires par exemple.

  5. #5
    Rédacteur/Modérateur


    Homme Profil pro
    Network game programmer
    Inscrit en
    Juin 2010
    Messages
    7 115
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : Canada

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 115
    Points : 32 967
    Points
    32 967
    Billets dans le blog
    4
    Par défaut
    Bonjour,

    les paramètres sont par défaut toujours envoyés par copie.
    Les pointeurs ne font pas exception.
    Le cas du pointeur diffère où, le pointeur est une copie, mais la donnée pointée est la même.

    Comme ceci:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    void f(int* _bd) { std::cout<<_bd<<std::endl;}
    void f2(int*& _bd) { std::cout<<_bd<<std::endl;}
    void main() {
    int* my_bd;
    std::cout<<my_bd<<std::endl; // p-e 0x85
    f(my_bd); // pas 0x85
    f2(my_bd); // 0x85
    }
    Pensez à consulter la FAQ ou les cours et tutoriels de la section C++.
    Un peu de programmation réseau ?
    Aucune aide via MP ne sera dispensée. Merci d'utiliser les forums prévus à cet effet.

  6. #6
    Membre du Club
    Homme Profil pro
    Étudiant
    Inscrit en
    Novembre 2011
    Messages
    129
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Novembre 2011
    Messages : 129
    Points : 68
    Points
    68
    Par défaut
    Bonjour à tous et merci pour vos réponses.

    Mon problème tenait du fait que j'ignorais totalement que le passage par valeur, concernait également les pointeurs. J'ai parfaitement compris le problème et ca m'a vraiment appris quelque chose et par ailleurs, cela explique la majorité de mes problèmes dans la réalisation de mon programme, notamment en ce qui concerne les destructions d'objet.

    J'ai utiliser nullptr (C++11) à la place de NULL (ou 0). Et pour le constructeur de Reader, je comprends qu'on puisse le trouver plus lisible avec un if dans le corps de la fonction. (Par contre mettre == NULL est superflux, AMA)
    Qu'est-ce donc que nullptr ? Je ne me tiens pas au courant des updates de C++, bien que j'aimerais avoir le temps. Pourquoi plus nullptr que NULL ? Par ailleurs, pourquoi est-ce superflux de faire un test d'égalité sur NULL ? On m'a appris comme ca lorsque j'ai appris le C++ et ca ne m'a jamais posé problème (tout du moins, je crois...) Pour moi, ca me permet de tester si un pointeur pointe justement sur NULL ou sur une adresse mémoire. Cependant ca implique de remettre le pointeur à NULL à chaque destruction de l'objet pointé pour rester cohérent.

  7. #7
    Rédacteur/Modérateur


    Homme Profil pro
    Network game programmer
    Inscrit en
    Juin 2010
    Messages
    7 115
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : Canada

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 115
    Points : 32 967
    Points
    32 967
    Billets dans le blog
    4
    Par défaut
    nullptr est al version C++11 de NULL
    tester un pointeur == NULL est superflu, un pointeur est automatiquement transtypé en bool
    if (ptr != NULL) === if(ptr)

    si, digestion time^^
    Pensez à consulter la FAQ ou les cours et tutoriels de la section C++.
    Un peu de programmation réseau ?
    Aucune aide via MP ne sera dispensée. Merci d'utiliser les forums prévus à cet effet.

  8. #8
    Membre éclairé Avatar de MythOnirie
    Homme Profil pro
    Développeur décisionnel
    Inscrit en
    Juin 2012
    Messages
    376
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    Localisation : France, Puy de Dôme (Auvergne)

    Informations professionnelles :
    Activité : Développeur décisionnel

    Informations forums :
    Inscription : Juin 2012
    Messages : 376
    Points : 795
    Points
    795
    Par défaut
    Citation Envoyé par Bousk Voir le message
    nullptr est al version C++11 de NULL
    tester un pointeur == NULL est superflu, un pointeur est automatiquement transtypé en bool
    if (ptr == NULL) === if(ptr)
    Ça ne serait pas plutôt :
    if (ptr == NULL) <==> if (!ptr)

  9. #9
    Membre du Club
    Homme Profil pro
    Étudiant
    Inscrit en
    Novembre 2011
    Messages
    129
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Novembre 2011
    Messages : 129
    Points : 68
    Points
    68
    Par défaut
    Bon encore merci à tous, je vais essayé d'utiliser nullptr et de me familiariser avec, je suis aussi content d'avoir appris ca.

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

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

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 369
    Points : 41 519
    Points
    41 519
    Par défaut
    Citation Envoyé par Bousk Voir le message
    nullptr est al version C++11 de NULL
    tester un pointeur == NULL est superflu, un pointeur est automatiquement transtypé en bool if (ptr != NULL) === if(ptr)
    Dans le temps je pensais comme toi, mais je me suis rendu compte que c'était plus explicite; comparer à NULL (ou nullptr) rend le code plus facile à lire et comprendre du premier coup d’œil.
    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.

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Encore une histoire de pointeur
    Par Supersami2000 dans le forum C
    Réponses: 5
    Dernier message: 11/07/2008, 13h42
  2. Réponses: 8
    Dernier message: 10/03/2006, 17h28
  3. Une histoire de lien...
    Par sloshy dans le forum Balisage (X)HTML et validation W3C
    Réponses: 7
    Dernier message: 25/08/2005, 23h13
  4. [JAR][debutant] encore une histoire de classpath
    Par blaz dans le forum Général Java
    Réponses: 6
    Dernier message: 27/07/2005, 12h28
  5. fuite de memoire dans une liste de pointeur sur composant
    Par Nicolos_A dans le forum Composants VCL
    Réponses: 2
    Dernier message: 16/12/2004, 08h46

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