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 :

Dereference un pointeur retourne par une fonction


Sujet :

C++

  1. #1
    Membre régulier Avatar de elvivo
    Inscrit en
    Mai 2002
    Messages
    105
    Détails du profil
    Informations forums :
    Inscription : Mai 2002
    Messages : 105
    Points : 104
    Points
    104
    Par défaut Dereference un pointeur retourne par une fonction
    Bonjour,

    Je me pose une question au sujet de la liberation de la memoire lors du dereferencement d'un pointeur retourne par une fonction.

    J'ai la fonction suivante:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    int* testFct()
    {
        int* a = new int;
       *a = 42;
       return a;
    }
    Le main est:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    int main()
    {
        int b = 0;
     
        b = *testFct();
     
        return 0;
    }
    Mon probleme est: A quel moment je fais le delete?
    Il me semble que la memoire a ete allouee mais dans la mesure ou je derefence le pointeur lors de l'appel de la fonction, je "perds" le pointeur.
    J'espere aue j'ai ete clair
    D'avance merci!
    In code we trust !!

  2. #2
    Membre habitué

    Inscrit en
    Mai 2005
    Messages
    132
    Détails du profil
    Informations forums :
    Inscription : Mai 2005
    Messages : 132
    Points : 171
    Points
    171
    Par défaut
    Salut,

    bien sur, tu as ete clair, voire meme pour etrangers :-))

    Dans ton example ce n'est pas possible. Tu dois le faire comme ca :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    int main()
    {
        int *b = NULL;
     
        b = testFct();
     
        return 0;
    }
    En somme ... tu dois te souvenir le pointeur. La valeur est toujour accessible, mais en plus tu peux faire delete.

    La recommandation - il faut TOUJOUR transmettre le pointeur :-))))))

    Fredy "KRUGER"

  3. #3
    Rédacteur

    Avatar de Davidbrcz
    Homme Profil pro
    Ing Supaéro - Doctorant ONERA
    Inscrit en
    Juin 2006
    Messages
    2 307
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 32
    Localisation : Suisse

    Informations professionnelles :
    Activité : Ing Supaéro - Doctorant ONERA

    Informations forums :
    Inscription : Juin 2006
    Messages : 2 307
    Points : 4 732
    Points
    4 732
    Par défaut
    Ou ne pas renvoyer de pointeur brut, c'est bien aussi surtout sur des types primitifs.
    "Never use brute force in fighting an exponential." (Andrei Alexandrescu)

    Mes articles dont Conseils divers sur le C++
    Une très bonne doc sur le C++ (en) Why linux is better (fr)

  4. #4
    Membre régulier Avatar de elvivo
    Inscrit en
    Mai 2002
    Messages
    105
    Détails du profil
    Informations forums :
    Inscription : Mai 2002
    Messages : 105
    Points : 104
    Points
    104
    Par défaut
    @Davidbrcz: Oui c'etait juste un exemple. Dans la pratique je retournerai pas un pointeur sur un simple int sauf si j'ai une bonne raison

    Merci a tous les deux!!
    J'ai ma reponse.
    A force d'utiliser des langages avec un garbage collector j'ai oublie les bons reflexes
    In code we trust !!

  5. #5
    Rédacteur

    Avatar de Davidbrcz
    Homme Profil pro
    Ing Supaéro - Doctorant ONERA
    Inscrit en
    Juin 2006
    Messages
    2 307
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 32
    Localisation : Suisse

    Informations professionnelles :
    Activité : Ing Supaéro - Doctorant ONERA

    Informations forums :
    Inscription : Juin 2006
    Messages : 2 307
    Points : 4 732
    Points
    4 732
    Par défaut
    Bah tu peux renvoyer des pointeurs intelligents (boost::shared_ptr ou équivalent) mais pas de pointeurs bruts.
    "Never use brute force in fighting an exponential." (Andrei Alexandrescu)

    Mes articles dont Conseils divers sur le C++
    Une très bonne doc sur le C++ (en) Why linux is better (fr)

  6. #6
    Membre éprouvé
    Profil pro
    Inscrit en
    Mai 2006
    Messages
    780
    Détails du profil
    Informations personnelles :
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations forums :
    Inscription : Mai 2006
    Messages : 780
    Points : 1 176
    Points
    1 176
    Par défaut
    Oui le bon réflexe serait de ne pas utiliser de pointeurs bruts.:

    Règle 1) Ne pas utiliser des pointeurs
    Règle 2) Utiliser des références
    Règle 3) Si vraiment besoin de pointeurs, ne pas utiliser de pointeurs bruts

  7. #7
    Membre éprouvé
    Inscrit en
    Avril 2005
    Messages
    1 110
    Détails du profil
    Informations forums :
    Inscription : Avril 2005
    Messages : 1 110
    Points : 937
    Points
    937
    Par défaut
    Comment faire pour généraliser la règle 2) ?
    Est-il possible de toujours se passer de (tout) pointeur quitte revoir la conception ?
    En bref, comment corriger le code suivant en conservant la référence et en évitant le memory leak:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    int & testFct()
    {
        int* a = new int;
       *a = 42;
       return *a;
    }
     
    int main()
    {
        int b = 0;
         b = testFct();
         return 0;
    }

  8. #8
    Membre chevronné
    Avatar de Goten
    Profil pro
    Inscrit en
    Juillet 2008
    Messages
    1 580
    Détails du profil
    Informations personnelles :
    Âge : 33
    Localisation : France

    Informations forums :
    Inscription : Juillet 2008
    Messages : 1 580
    Points : 2 205
    Points
    2 205
    Par défaut
    C'est qu'un int ça se renvoie par copie ça :p.
    Mais je suppose que tu voulais généraliser, ben y'a pas de méthode miracle.. passage en argument par référence? Ca peut être une solution, mais c'est pas ma préférée.
    Sinon retourner par copie dans le cas d'un compilo qui fait du (N)RVO c'est ce qu'il y'a de plus efficace. Ou alors binder ta rvalue à une const ref... (mais c'est casse gueule si quelqu'un s'en sert autrement)
    "Hardcoded types are to generic code what magic constants are to regular code." --A. Alexandrescu

  9. #9
    Inactif  


    Homme Profil pro
    Inscrit en
    Novembre 2008
    Messages
    5 288
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 48
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Secteur : Santé

    Informations forums :
    Inscription : Novembre 2008
    Messages : 5 288
    Points : 15 620
    Points
    15 620
    Par défaut
    bonjour camboui

    Je vais être moins catégorique que nikko34 : vouloir éviter à tout prix l'utilisation des pointeurs n'est pas forcement un bonne chose.

    Pourquoi une référence plutôt qu'un pointeur : simplement parce que la référence garantie que l'objet existe. Donc pas besoin de vérifier l'allocation et pas de risque d'avoir un pointeur non null qui pointe vers rien (et provoquera une erreur à l'exécution).

    Utiliser systématiquement un pointeur intelligent ? oui, sauf bonne raison de ne pas le faire

    Une règle (recommandation ?) déjà lu quelque part : la classe qui crée un objet se charge aussi de la détruire.

    Pour répondre à ta question (il faut bien y arriver un moment donné), lorsque la classe qui crée un objet continue d'existant pendant toute la vie de l'objet crée (par exemple un gestionnaire de fenêtre qui crée une fenêtre) alors tu peux retourner une référence. Si la classe est temporaire (ou que sa durée de vie n'est pas garantie), tu dois renvoyer un pointeur.

    Dans ton code initial :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    int* testFct()
    {
       int* a = new int; // tu crées un objet dynamique en mémoire et tu conserves l'adresse de cet objet dans "a"
       *a = 42; // tu affectes la valeur "42" à l'objet dynamique
       return a; // tu retourne l'adresse de l'objet dynamique
    }
    
    int main()
    {
        int b = 0; // tu crées un objet statique
        b = *testFct(); // tu déréférences ton pointeur (tu vas chercher directement l'objet dynamique pointé) puis tu copies cet objet dans "b"
        return 0;
    }
    Ici, l'erreur provient du fait que tu recopies l'objet dynamique et ne conserve pas l'adresse de l'objet dynamique créé. Tu as donc 2 objets en mémoire : "b" qui est statique et accessible en utilisant la variable "b", et "a" qui est dynamique et qui n'est plus accessible (et ne peut donc être détruit : fuite mémoire).


    Plein de méthodes pour éviter la fuite mémoire :
    Avec un pointeur intelligent
    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
    typedef boost::shared_ptr<int> int_ptr;
     
    int_ptr testFct()
    {
       int_ptr a = new int_ptr();
       *a = 42;
       return a;
    }
     
    int main()
    {
        int b = 0;
        b = *testFct();
        return 0;
    }
    Le pointeur intelligent "sait" qu'il existe plus de pointeur vers l'objet dynamique et le détruit automatiquement.


    Retour par référence d'un objet interne à la fonction
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    int& testFct()
    {
        int a = 42;
        return a; // retourne "a" par référence mais "a" est détruit en sortie de fonction : erreur !
    }
    Retour par copie de l'objet créé dans la fonction
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    int testFct()
    {
        int a = 42;
        return a; // retourne une copie de "a" : ok !
    }
    Une classe créant un objet dynamique et se chargeant de le détruire :
    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
    class A
    {
        A() { m_a=NULL; }
        ~A() { delete m_a; }
     
        int* testFct()
        {
            if(NULL==m_a) m_a = new int(42) else *m_a=42;
            return m_a;
        }
     
        private: int* m_a;
    }
     
    int main()
    {
        A a();
        int b = 0;
        b = *testFct();
        return 0;
    }

    Classe utilisant un objet interne et renvoyant une référence (mieux que l'exemple de dessus) :
    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
    class A
    {
        int& testFct()
        {
            m_a = 42; // plus besoin de test si NULL puisque existence garantie.
            return m_a;
        }
     
        private: int m_a;
    }
     
    int main()
    {
        A a();
        int b = 0;
        b = testFct();
        return 0;
    }
    Une classe créant plusieurs objets dynamiques et se chargeant de les détruire :
    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
    class A
    {
        typedef std::vector<int*> int_vector;
        typedef typename int_vector::iterator int_it;
     
        ~A() 
        {
            for(int_it it=m_vec.begin(); it!=vec.end(); ++it) delete (*it);
        }
     
        int* testFct()
        {
            int* a = new int(42);
            m_vec.push_back(a);
            return a;
        }
     
        private: std::vector<int*> m_vec;
    }
     
    int main()
    {
        A a();
        int b = 0;
        b = *testFct();
        return 0;
    }
    J'espère avoir fait le tour et ne pas avoir dit trop de bêtises (d'autres se chargeront de me corriger )

  10. #10
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 612
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 612
    Points : 30 612
    Points
    30 612
    Par défaut
    Salut,

    Il faut être prudent avec la règle 2...

    Elle est vraie lorsque l'on parle de paramètres, ou lorsque l'on parle d'une fonction membre qui renvoie un membre de la classe /structure (bien que l'on préférera alors renvoyer une référence constante, pour éviter tout risque de modification "inoportune").

    Par contre, il ne faut jamais renvoyer une référence sur un objet créé localement à une fonction, car l'objet sera détruit au moment où l'on quittera la portée de la fonction dans laquelle il est créé.

    Le retour par valeur est donc la meilleure des solutions, sauf cas particuliers
    Citation Envoyé par gbdivers Voir le message
    bonjour camboui

    Je vais être moins catégorique que nikko34 : vouloir éviter à tout prix l'utilisation des pointeurs n'est pas forcement un bonne chose.
    En fait, il ne faut pas vouloir les éviter à tout prix, mais il faut les éviter "autant que faire se peut"...

    Typiquement, il n'y a que de très rares cas dans lesquels des pointeurs seront réellement utiles, et toute la beauté de l'art réside dans le fait d'identifier avec certitude ces quelques cas
    Pourquoi une référence plutôt qu'un pointeur : simplement parce que la référence garantie que l'objet existe. Donc pas besoin de vérifier l'allocation et pas de risque d'avoir un pointeur non null qui pointe vers rien (et provoquera une erreur à l'exécution).
    c'est la raison technique la plus couremment citée...

    Mais au delà, on peut également citer :

    - le fait que l'on garde, avec les références, une syntaxe similaire à celle que l'on utiliserait si on avait affaire à un objet (plus besoin de se poser la question de savoir si on doit utiliser la fleche ou l'étoile pour accéder à un membre / une fonction membre)

    - le fait que la constance est correctement tansmise avec les références, alors qu'il existe quatre possibilités de constance différentes avec les pointeurs (et que l'on risque donc de s'emmeler les pinceaux)

    - Le fait que, si l'on commence à utiliser l'operateur address of, on risque facilement de se retrouver avec des pointeurs de pointeurs de pointeurs de ... et qu'il est très facile d'y perdre son latin

    - Le fait que l'utilisation des pointeurs est souvent associée, dans la compréhension "populaire", au recours à l'allocation dynamique, et donc à tous les problèmes qu'elle peut engendrer.
    Une règle (recommandation ?) déjà lu quelque part : la classe qui crée un objet se charge aussi de la détruire.
    heuuu... oui, mais pas forcément...

    Le problème auquel nous serons confrontés en respectant cette recommandation est que nous obtiendrons un couplage fort entre le contenant (la classe qui s'occupe de la création et de la destruction de l'objet) et le contenu (l'objet créé en ayant recours à l'allocation dynamique).

    Dans bien des cas (en gros, chaque fois que le contenu sera polymorphe), il sera préférable d'avoir recours à une fabrique qui aura la responsabilité de créer un objet, et de laisser la responsabilité de la destruction au contenant.

    Ce sera alors au contenant de s'occuper de la durée de vie du contenu, et il est possible de mettre en place (via les assertions ou les exceptions) un système qui renvoie une référence (constante ou non) sur le contenu uniquement s'il existe réellement
    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

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

Discussions similaires

  1. Chaîne retournée par une fonction et encodage
    Par dj-julio dans le forum Langage
    Réponses: 4
    Dernier message: 26/03/2008, 22h36
  2. Réponses: 2
    Dernier message: 24/12/2007, 09h53
  3. Réponses: 4
    Dernier message: 07/04/2007, 20h02
  4. valeur retournée par une fonction
    Par Biosox dans le forum C
    Réponses: 13
    Dernier message: 19/01/2007, 23h17
  5. Réponses: 11
    Dernier message: 31/10/2005, 17h59

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