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 :

[C++] Libération de mémoire de tableaux et de classes


Sujet :

Langage C++

  1. #1
    Membre Expert
    Avatar de Aspic
    Homme Profil pro
    Étudiant
    Inscrit en
    Août 2005
    Messages
    3 905
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hauts de Seine (Île de France)

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Août 2005
    Messages : 3 905
    Par défaut [C++] Libération de mémoire de tableaux et de classes
    Bonsoir,

    J'ouvre ce post car je suis un peu perdu dans ce que je lis sur le net sur la destruction des tableaux et des classes en C++.
    Pour tout vous dire je suis perdu
    Je tiens à préciser que j'ai lu avec attention la FAQ, vous verrez par la suite

    Exemple :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    t_classe* maclasse = new t_classe();
    // .... utilisation de maclasse
    delete maclasse;
    Question 1 : Pourquoi dans la faq de dvp, ils disent qu'il faut utiliser les crochets pour supprimer un objet ?? donc faire un delete [] maclasse;

    Maintenant passons aux tableaux :

    J'utilise un tableau de vector l'un, des plus simples mais j'ai du mal à comprendre comment le détruire sans fuites de mémoire !
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    vector<t_ennemie*> ennemies;
     
     
    // pour le détruire je fais :
    for (unsigned int i=0; i<ennemies.size(); i++)
            {
                delete ennemies[i];
            }
    Question 2 : Est ce correcte ? car dans la faq, ils disent que c'est pas bon et qu'il faut utiliser des itérateurs avec des boost, enfin des trucs que j'ai pas du tout compris...
    LIEN : http://cpp.developpez.com/faq/cpp/?p...ssion_elements

    Voilà pour l'instant, je créé actuellement un jeu et les fuites de mémoires doivent être gérées donc c'est important pour moi

    Merci !
    Qui ne tente rien n'a rien !
    Ce qui ne nous tue pas nous rends plus fort !!
    Mon projet ZELDA en C++/Allegro
    http://www.tutoworld.com - Le Forum -
    Mes ressources Dotnet (cours, sources, tutos)
    --------------------------------------------
    + + =

    Ne pas oublier le Tag !

  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
    Citation Envoyé par Aspic Voir le message
    Bonsoir,

    J'ouvre ce post car je suis un peu perdu dans ce que je lis sur le net sur la destruction des tableaux et des classes en C++.
    Pour tout vous dire je suis perdu
    Je tiens à préciser que j'ai lu avec attention la FAQ, vous verrez par la suite

    Exemple :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    t_classe* maclasse = new t_classe();
    // .... utilisation de maclasse
    delete maclasse;
    Question 1 : Pourquoi dans la faq de dvp, ils disent qu'il faut utiliser les crochets pour supprimer un objet ?? donc faire un delete [] maclasse;
    Tu as sans doute mal lu, ou alors tu as mis le doigt sur une grosse erreur dans la FAQ...

    Il faut utiliser delete (sans crochets) lorsque tu as alloué la mémoire avec new (sans crochets) et delete[] (avec crochets) lorsque tu as alloué la mémoire avec new[] (avec crochets)

    La première versions (sans crochet) alloue la mémoire pour un objet unique et la seconde (avec crochet) permet d'allouer de la mémoire contigüe pour un certain nombre d'objets (le nombre apparaissant entre les crochets )
    Maintenant passons aux tableaux :
    J'utilise un tableau de vector l'un, des plus simples mais j'ai du mal à comprendre comment le détruire sans fuites de mémoire !
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    vector<t_ennemie*> ennemies;
     
     
    // pour le détruire je fais :
    for (unsigned int i=0; i<ennemies.size(); i++)
            {
                delete ennemies[i];
            }
    Déjà, pourquoi utiliser des pointeurs

    Si tu as plusieurs classes qui héritent de t_ennemie, cela peut se justifier, mais si tu n'a pas pour objectif d'utiliser le polymorphisme, cela ne sert stritement à rien
    Question 2 : Est ce correcte ? car dans la faq,
    Si tu utilise des pointeurs, tu dois, effectivement commencer par veiller à libérer la mémoire allouée à ces pointeurs avant de supprimer les éléments, donc, dans l'exemple que tu présente, oui, c'est correct
    ils disent que c'est pas bon et qu'il faut utiliser des itérateurs avec des boost, enfin des trucs que j'ai pas du tout compris...
    LIEN : http://cpp.developpez.com/faq/cpp/?p...ssion_elements
    Attention, la faq présente comment supprimer un / des éléments des différentes collection, mais se base sur l'utilisation de pointeur, et ne parle que de la suppression d'élément (avec éventuellement la sélection d'éléments à supprimer), absolument pas de la libération de la mémoire
    Voilà pour l'instant, je créé actuellement un jeu et les fuites de mémoires doivent être gérées donc c'est important pour moi

    Merci !
    Si tu n'es pas particulièrement à l'aise sur la gestion de la mémoire et, entre autres, sur la détermination du meilleur moment pour libérer la mémoire, tu devrais te tourner vers les pointeurs intelligents.

    L'utilisation de pointeurs nu n'est, en effet, pas particulièrement conseillée et, il faut bien le dire, l'utilisation de pointeurs ne devrait être envisagée que de manière particulièrement exceptionnelle, et les pointeurs intelligents sont souvent de nature à apporter un minimum de sécurisation à leur utilisation.

    On parle justement des pointeurs intelligents dans cette discussion... peut être te convaincra-t-elle
    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 Expert
    Avatar de Aspic
    Homme Profil pro
    Étudiant
    Inscrit en
    Août 2005
    Messages
    3 905
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hauts de Seine (Île de France)

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Août 2005
    Messages : 3 905
    Par défaut
    Merci pour cette réponse détaillée

    Pour la première question ca me rassure je ne suis pas fou ^^

    Et pour la deuxième, j'ai toujours utilisé les pointeurs car je suis à l'aise avec sauf au niveau de la libération de la mémoire parfois j'oublie et c'est chiant xD

    Avant je programmais en C avec des typedef (structures) et j'étais obligé d'utiliser les pointeurs pour que les attribus des "objets" soient modifiés dans tous mes sous programmes.

    En C++, j'ai gardé cette habitude sans me poser la question si c'était nécessaire. En fait quand je vais bouger un ennemie donc modifier ses attribus posX et posY, si j'utilise pas de pointeur, est ce que ca sera pris en compte dans tous les sous programmes qui utilise l'objet "ennemie" ?

    Sinon pour en revenir aux pointeurs intélligents, j'ai vu ca dans la FAQ et ca m'a pas emballer pour dire la vérité, car je dois apprendre une nouvelle notion et apparemment y'a de risques donc il faut faire attention.

    Mais je vois pas l'intéret de mettre des blocs try/catch partout pour récupérer les exeptions car si un new échoue par manque de mémoire (chose improbable sur nos pc actuels), le programme plante et puis c'est tout
    Je comprends pas où y'a une fuite de mémoire ? car quand un programme a planté et qu'il est fermé, normalement toutes les ressources sont libérés non ?

    Désolé si j'ai l'air ignorant mais je maitrise bien le C/C++ c'est juste que niveau mémoire, je m'en suis jamais préoccupé car je codais des petits trucs de 500 lignes mais sur un projet tel qu'un jeu, je ne peux pas "m'en foutre"
    Qui ne tente rien n'a rien !
    Ce qui ne nous tue pas nous rends plus fort !!
    Mon projet ZELDA en C++/Allegro
    http://www.tutoworld.com - Le Forum -
    Mes ressources Dotnet (cours, sources, tutos)
    --------------------------------------------
    + + =

    Ne pas oublier le Tag !

  4. #4
    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
    Citation Envoyé par Aspic Voir le message
    Merci pour cette réponse détaillée

    Pour la première question ca me rassure je ne suis pas fou ^^

    Et pour la deuxième, j'ai toujours utilisé les pointeurs car je suis à l'aise avec sauf au niveau de la libération de la mémoire parfois j'oublie et c'est chiant xD

    Avant je programmais en C avec des typedef (structures) et j'étais obligé d'utiliser les pointeurs pour que les attribus des "objets" soient modifiés dans tous mes sous programmes.
    Oui, mais, si c'est pour programmer en C++ comme on programme en C, on perd tous les avantages que C++ peut apporter
    En C++, j'ai gardé cette habitude sans me poser la question si c'était nécessaire. En fait quand je vais bouger un ennemie donc modifier ses attribus posX et posY, si j'utilise pas de pointeur, est ce que ca sera pris en compte dans tous les sous programmes qui utilise l'objet "ennemie" ?
    La réponse tient en un mot: référence...

    Fais petit tour par la FAQ ou une recherche sur le forum pour comprendre, mais tu as, en plus, l'énorme avantage d'assurer l'existence de l'objet passé par référence
    Sinon pour en revenir aux pointeurs intélligents, j'ai vu ca dans la FAQ et ca m'a pas emballer pour dire la vérité, car je dois apprendre une nouvelle notion et apparemment y'a de risques donc il faut faire attention.
    Comme je le faisais remarquer dans ma première intervention à la discussion que je vient de citer, leur apprentissage représente un investissement qui sera très largement récupéré par la suite
    Mais je vois pas l'intéret de mettre des blocs try/catch partout pour récupérer les exeptions car si un new échoue par manque de mémoire (chose improbable sur nos pc actuels), le programme plante et puis c'est tout
    La gestion d'erreurs est un problème tout à fait différent qui, justement, est abordé depuis pas très longtemps dans cette discussion
    Je comprends pas où y'a une fuite de mémoire ? car quand un programme a planté et qu'il est fermé, normalement toutes les ressources sont libérés non ?
    Une fuite mémoire n'est pas *particulièrement* importante au moment où le programme plante, mais ca peut être la cause du plantage...

    Il faut te dire que ton application est rarement la seule application qui tourne sur le PC.

    Et le fait est qu'une fuite mémoire met en péril l'ensemble du système: si une application présente une fuite mémoire, cela peut porter préjudice à ta propre application, même si cette dernière n'en souffre absolument pas

    Désolé si j'ai l'air ignorant mais je maitrise bien le C/C++
    On passe son temps à militer pour que l'on ne fasse pas l'amalgame C et C++, et ce n'est pas un hazard...

    Si C++ revendique son héritage du C, il faut vraiment comprendre que ce sont deux langages totalement différents et que l'idéal est d'éviter à tout prix les possibilités du C lorsqu'il existe une possibilité identique propre au C++.
    c'est juste que niveau mémoire, je m'en suis jamais préoccupé car je codais des petits trucs de 500 lignes mais sur un projet tel qu'un jeu, je ne peux pas "m'en foutre"
    Effectivement.

    Et, justement, cela plaide, encore une fois, en faveur de l'utilisation des possibilités offertes par C++ chaque fois que possible.

    Tu t'éviteras de nombreux cheveux gris, quelle que soit la taille du projet
    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

  5. #5
    Membre Expert
    Avatar de Aspic
    Homme Profil pro
    Étudiant
    Inscrit en
    Août 2005
    Messages
    3 905
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hauts de Seine (Île de France)

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Août 2005
    Messages : 3 905
    Par défaut
    OKi merci pour toute ces infos, j'essaye déjà de limiter le plus possible le C (genre pas de malloc, free ou de fonction pourris pour gérer les chaines de caractères (vive la classe string !)) mais parfois c'est inévitable

    Sinon juste une question, je viens de trouver une fuite de mémoire car je n'avais pas initialisé une variable (honte à moi ) mais le bloc try/catch ne l'a pas levé je comprends pas pourquoi...

    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
     
    try
    {
    int dir = 0;
    int imageCourante = -1; // exprès pour provoquer l'erreur
    sprite_courant = sprites[dir][imageCourante];
    }
    catch ( const std::out_of_range &tmp)
    {
        cout << "Erreur : débordement de mémoire." << tmp.what() << "\n";
    }
    catch (const std::exception &tmp)
    {                
        cout << "Erreur erreur ! (cause " << tmp.what() << ")" << endl;
    }
    catch (...)
    {
       cout << "erreur ";
    }
    Ca ne pase pas dans le catch... bizarre, j'ai essayé plein de catch différent dont std::exception, std::out_of_range mais ca ne passe pas...

    Une idée ?

    Encore merci pour tes explications
    Qui ne tente rien n'a rien !
    Ce qui ne nous tue pas nous rends plus fort !!
    Mon projet ZELDA en C++/Allegro
    http://www.tutoworld.com - Le Forum -
    Mes ressources Dotnet (cours, sources, tutos)
    --------------------------------------------
    + + =

    Ne pas oublier le Tag !

  6. #6
    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
    Citation Envoyé par Aspic Voir le message
    OKi merci pour toute ces infos, j'essaye déjà de limiter le plus possible le C (genre pas de malloc, free ou de fonction pourris pour gérer les chaines de caractères (vive la classe string !)) mais parfois c'est inévitable

    Sinon juste une question, je viens de trouver une fuite de mémoire car je n'avais pas initialisé une variable (honte à moi ) mais le bloc try/catch ne l'a pas levé je comprends pas pourquoi...

    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
     
    try
    {
    int dir = 0;
    int imageCourante = -1; // exprès pour provoquer l'erreur
    sprite_courant = sprites[dir][imageCourante];
    }
    catch ( const std::out_of_range &tmp)
    {
        cout << "Erreur : débordement de mémoire." << tmp.what() << "\n";
    }
    catch (const std::exception &tmp)
    {                
        cout << "Erreur erreur ! (cause " << tmp.what() << ")" << endl;
    }
    catch (...)
    {
       cout << "erreur ";
    }
    Ca ne pase pas dans le catch... bizarre, j'ai essayé plein de catch différent dont std::exception, std::out_of_range mais ca ne passe pas...

    Une idée ?

    Encore merci pour tes explications
    C'est normal, un tableau C style ne connait absolument pas sa taille...

    l'exception out_of_range est essentiellement lancée par... les collections de la STL, et non par les tableaux C style (qui ignorent même que les exceptions existent)

    De plus, quand tu défini la valeur d'un entier (signé) à -1 pour forcer à l'erreur, tu ne force absolument rien:

    Je ne suis pas tout à fait sur de ce qui se passe, mais

    Soit tu accède, tout simplement, à l'adresse pointée par sprites[dir] -1, soit ton entier est considéré non pas comme un entier signé, mais comme un entier non signé.

    En effet, la représentation en mémoire d'un entier signé est strictement identique à celle d'un entier non signé, la seule différence résidant dans le fait qu'un bit (sur ... 32, typiquement ) sert à représenter le signe de la valeur.

    Les valeurs négatives sont, souvent représentée en ce qu'on appelle "complément à 2".

    Ainsi, si la valeur 1 est représentée, en hexadécimal, sous la forme de 0x00000001, la valeur -1 sera, souvent, représentée sous la forme de 0xFFFFFFFF.

    Or, sous sa forme non signée, la valeur 0xFFFFFFFF représente la valeur...4 294 967 295

    Et, comme une adresse mémoire n'est jamais qu'une valeur numérique, il est tout à fait possible d'additionner cette valeur à une adresse, même si, le dépassement de la valeur fait que... le résultat fera que l'on "recommencera" à un certain moment à compter à 0, ce qui reviendra sans doute (en fonction du nombre de valeurs disponibles pour représenter les adresse mémoire) à... soustraire un à l'adresse d'origine
    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

  7. #7
    Membre Expert
    Avatar de Aspic
    Homme Profil pro
    Étudiant
    Inscrit en
    Août 2005
    Messages
    3 905
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hauts de Seine (Île de France)

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Août 2005
    Messages : 3 905
    Par défaut
    D'accord j'ai compris, si j'avais utilisé un tableau de vector avec la fonction at, ca aurait levé une exception alors que le C se fiche complètement de l'index du tableau, il se contente d'essayer de lire la case mémoire et advienne qui pourra (d'ailleurs ca plante pas tout le temps )

    Deux dernières petites choses sur les vectors :

    1) Si j'utilise un tableau banal vector<t_classe> sans pointeurs dans une classe t_globale, pour le libérer dans mon destructeur, qu'est ce que j'ai a faire ? Si je mets 3 éléments dedans, est ce que les destructeurs des "3 t_classe" seront appelées automatiquement lorsque j'aurais appelé le destructeur de ma classe t_globale ?

    2) Même question mais avec la version avec pointeurs

    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
     
    class t_globale
    {
    private :
    vector<t_classe> toto;
    vector<t_classe*> toto2; // version avec pointeurs
     
    public:
     
    t_globale::t_globale()
    {
    toto.push_back(toto());
    toto.push_back(toto());
    toto.push_back(toto());
     
    toto2.push_back(new toto());
    toto2.push_back(new toto());
    toto2.push_back(new toto());
    }
    t_globale::~t_globale()
    {
    // que mettre la pour libérer la mémoire des tableaux correctement ?
    }
     
    };
    Qui ne tente rien n'a rien !
    Ce qui ne nous tue pas nous rends plus fort !!
    Mon projet ZELDA en C++/Allegro
    http://www.tutoworld.com - Le Forum -
    Mes ressources Dotnet (cours, sources, tutos)
    --------------------------------------------
    + + =

    Ne pas oublier le Tag !

  8. #8
    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
    Citation Envoyé par Aspic Voir le message
    D'accord j'ai compris, si j'avais utilisé un tableau de vector avec la fonction at, ca aurait levé une exception alors que le C se fiche complètement de l'index du tableau, il se contente d'essayer de lire la case mémoire et advienne qui pourra (d'ailleurs ca plante pas tout le temps )
    Exactement
    Deux dernières petites choses sur les vectors :

    1) Si j'utilise un tableau banal vector<t_classe> sans pointeurs dans une classe t_globale, pour le libérer dans mon destructeur, qu'est ce que j'ai a faire ?
    Strictement rien...
    Si je mets 3 éléments dedans, est ce que les destructeurs des "3 t_classe" seront appelées automatiquement lorsque j'aurais appelé le destructeur de ma classe t_globale ?
    Oui

    Le destructeur du vector est appelé automatiquement et il va appeler automatiquement le destructeur de chacun de ses éléments.

    Nous sommes d'accord: nous parlons d'un tableau d'objets, et non d'un tableau de pointeurs, hein
    2) Même question mais avec la version avec pointeurs
    Là, tu dois veiller à appeler invoquer explictement delete sur chacun des éléments du tableau...

    Du moins si ta classe t_globale prend la responsabilité de la gestion de la durée de vie des pointeurs.
    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
     
    class t_globale
    {
    private :
    vector<t_classe> toto;
    vector<t_classe*> toto2; // version avec pointeurs
     
    public:
     
    t_globale::t_globale()
    {
    toto.push_back(toto());
    toto.push_back(toto());
    toto.push_back(toto());
     
    toto2.push_back(new toto());
    toto2.push_back(new toto());
    toto2.push_back(new toto());
    }
    t_globale::~t_globale()
    {
    // que mettre la pour libérer la mémoire des tableaux correctement ?
    }
     
    };
    La version "naïve" du destructeur:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    t_globale::~t_globale()
    {
        for(std::vector<t_classe*>::iterator it=toto2.begin();it!=toto2.end()
              ;++it)
            delete *it;
    }
    une version plus évoluée:
    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
    /* "quelque part" dans le code, mais accessible */
    template <class T>
    struct deleter
    {
        void operator(T* t)
        {
            delete t;
        }
    };
    /* et le destructeur */
    #include <algorithm>
    t_globale::~t_globale()
    {
        std::for_each(toto2.begin(),toto2.end(),deleter<t_class>());
    }
    NOTA: for_each n'est dans l'espace de noms std qu'avec la prochaine norme (C++1x), il me semble... Avant, il est dans std::tr1
    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

  9. #9
    Membre Expert
    Avatar de Aspic
    Homme Profil pro
    Étudiant
    Inscrit en
    Août 2005
    Messages
    3 905
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hauts de Seine (Île de France)

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Août 2005
    Messages : 3 905
    Par défaut
    Quelle est la différence entre la version "naive" et la version évoluée (niveau perf, niveau mémoire...) ? De même, quelle est la différence avec la version "débutant" :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    // version "débutant"
    for (unsigned int i=0; i<toto2.size(); i++)
            {
                delete toto2[i];
            }
    Qui ne tente rien n'a rien !
    Ce qui ne nous tue pas nous rends plus fort !!
    Mon projet ZELDA en C++/Allegro
    http://www.tutoworld.com - Le Forum -
    Mes ressources Dotnet (cours, sources, tutos)
    --------------------------------------------
    + + =

    Ne pas oublier le Tag !

  10. #10
    Expert confirmé
    Avatar de Luc Hermitte
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2003
    Messages
    5 291
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Août 2003
    Messages : 5 291
    Par défaut
    Je rebondis sur cette phrase:
    Citation Envoyé par Aspic Voir le message
    Sinon pour en revenir aux pointeurs intélligents, j'ai vu ca dans la FAQ et ca m'a pas emballer pour dire la vérité, car je dois apprendre une nouvelle notion et apparemment y'a de risques donc il faut faire attention.
    Il y a des risques avec ... les pointeurs non-intelligents, a.k.a. nus, a.k.a. bruts.
    Les intelligents ? Ils sont très simples à employer.
    Pourquoi les nus ne le sont pas ?
    - A cause de risque de dandling pointers (doubles libérations)
    - Et surtout à causes des situations non nominales qu'il nous faut gérer. Et là, avec les pointeurs nus, c'est vite l'enfer -- la discussion indiquée par Koala, et la FAQ en parlent déjà en long et en large.

    Une notion à apprendre ? Non pas vraiment. L'intérêt est limpide quand on a compris que nos programmes ne tournent jamais au pays magique où les erreurs n'existent pas. Seule la démonstration de leur force n'est pas évidente quand on n'a connu que le pays magique (i.e. des cours oversimplifiés pour enfants)

    PS: c'est exactement le même combat que les strings et les vecteurs.
    PPS: std::foreach existe depuis 98.
    Blog|FAQ C++|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS|Bons livres sur le C++
    Les MP ne sont pas une hotline. Je ne réponds à aucune question technique par le biais de ce média. Et de toutes façons, ma BAL sur dvpz est pleine...

  11. #11
    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
    Citation Envoyé par Aspic Voir le message
    Quelle est la différence entre la version "naive" et la version évoluée (niveau perf, niveau mémoire...) ?
    A ma connaissance, aucune...

    Ce qui se passe surtout, c'est que la version évoluée fonctionnera avec n'importe quel type de conteneur de la STL, qu'il s'agisse d'un std::vector, d'une std::list, d'un std::set,sans avoir rien à changer.

    Cependant, il serait possible de s'éviter d'avoir à changer la version native si l'on déclarait un alias de type pour std::vector<t_class*>::iterator sous la forme de
    typedef std::vector<t_class>::iterator iterator;
    ce qui aurait pour résultat de nous permettre l'écriture du destructeur sous la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    t_globale::~t_globale()
    {
        for(iterator it=toto2.begin();it!=toto2.end()
              ;++it)
            delete *it;
    }
    De même, quelle est la différence avec la version "débutant" :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    // version "débutant"
    for (unsigned int i=0; i<toto2.size(); i++)
            {
                delete toto2[i];
            }
    En terme de performance, il n'y a aucune différence, normalement, par contre, en terme de maintenabilité et du respect du principe "open close" du code, la différence est majeure.

    En effet, l'idéal est de faire en sorte à ce que le code soit ouvert aux évolutions, mais fermé aux modifications: le code écrit idéal ne devrait nécessiter aucune modification pour s'adapter à de nouveaux besoins ou de nouvelles décisions.

    Par exemple, la décision d'utiliser un std::vector peut être remise en question à n'importe quel moment, et l'on peut imaginer que, après avoir fait de nombreux tests, tu décide, pour une raison qui t'est propre, d'utiliser plutôt une std::list ou un std::set au lieu du std::vector d'origine.

    Or, l'opérateur [] n'existe que... pour le std::vector, alors que toutes les collections de la stl présentent un itérateur que l'on peut incrémenter.

    Si tu écris le code que tu présentes toi, à savoir
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    for (unsigned int i=0; i<toto2.size(); i++)
            {
                delete toto2[i];
            }
    et que tu décide, effectivement, d'utiliser une std::list au lieu du std::vector, tu devra modifier ce code parce que la std::list ne propose pas l'opérateur [], qui n'a, de toutes manières aucune raison d'être pour elle.

    Par contre, le fait d'utiliser les itérateurs permet d'utiliser n'importe quelle collection qui propose un itérateur compatible (tu devra quand même modifier le code si tu décide d'utiliser la std::map, par exempe )

    Cependant, la version que je présente comme "naïve" nécessitera quand même une modification de code pour adapter le type d'itérateur (ou l'alias de type correspondant) à la collection qui maintient effectivement tes pointeurs.

    C'est pourquoi je propose une version "évoluée".

    Elle se base sur le fait que toutes les collections que l'on risque d'utiliser exposent au minimum deux fonctions particulières: begin(), qui renvoie un itérateur sur le premier élément que la collection contient et end() qui renvoie un itérateur sur... ce qui suit le dernier élément que la collection contient.

    On sait en effet que l'on devra veiller à exécuter une action précise (à savoir invoquer l'opérateur delete) sur chaque élément de la collection.

    On va donc créer une "fonction objet" que l'on appelle aussi "foncteur" qui se chargera d'effectuer l'action en question.

    Comme on peut raisonnablement estimer que nous risquons de devoir invoquer l'opérateur delete sur différents types de données, on peut (c'est en tout cas le raisonnement que j'ai suivi ici ) décider de créer directement un foncteur sous la forme template, de manière à ne pas devoir réécrire cinquante fois le code si, au final, on se retrouve avec cinquante types sur lesquels nous devrons invoquer l'opérateur delete.

    On profite, enfin, du fait qu'il est tellement habituel de devoir effectuer une action sur un "range" d'éléments que le standard a carrément prévu une fonction libre qui implémente l'algorithme qui permet de le faire en une seule instruction: la fonction for_each.

    Ainsi, nous pouvons "décortiquer" le code que je propose en:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    std::for_each ( // une fonction qui exécute quelque chose 
                    // sur tous les éléments
        toto2.begin(), // qui sont compris entre le premier élément de toto2
        toto2.end(),   // et ce qui suit le dernier élément de toto2 (non compris)
        deleter<t_classe>() // nous lui demandons d'exécuter le foncteur
                            //  "deleter" en l'adaptant au type "t_classe"
        );
    De cette manière, on ne doit même plus indiquer le type de la collection qui fournit un itérateur, et l'on ne devra donc plus penser à le modifier (vu qu'on ne l'indique pas ) si on décide de remplacer notre std::vecto par une std::list

    Pour être complet, je tiens juste à signaler que, si for_each s'adapte si bien à n'importe quel type d'itérateur, c'est parce que, bien que cela ne se voie pas dans le code, il s'agit d'une fonction template et que l'on profite intelligemment de la déduction automatique des types transmis en paramètres
    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

  12. #12
    Membre Expert
    Avatar de Aspic
    Homme Profil pro
    Étudiant
    Inscrit en
    Août 2005
    Messages
    3 905
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hauts de Seine (Île de France)

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Août 2005
    Messages : 3 905
    Par défaut
    Génial, superbe explication très claire

    Un grand merci à toi pour m'avoir éclairer sur tout ca

    Juste un bémol et je t'embête plus
    Elle se base sur le fait que toutes les collections que l'on risque d'utiliser exposent au minimum deux fonctions particulières: begin(), qui renvoie un itérateur sur le premier élément que la collection contient et end() qui renvoie un itérateur sur... ce qui suit le dernier élément que la collection contient.
    Tu dis que le end(); renvoit un itérateur sur ce qui SUIT LE DERNIER ELEMENT mais je comprends pas ca devrait pas retourner un itérateur sur le dernier élément simplement car quand on veut supprimer tous les éléments, admettions qu'on est dans notre tableau 3 objets :

    begin() => "pointe" sur le 1er objet
    end() => "pointe" sur le 4eme ? c'est bizarre non ?

    Voilà
    Qui ne tente rien n'a rien !
    Ce qui ne nous tue pas nous rends plus fort !!
    Mon projet ZELDA en C++/Allegro
    http://www.tutoworld.com - Le Forum -
    Mes ressources Dotnet (cours, sources, tutos)
    --------------------------------------------
    + + =

    Ne pas oublier le Tag !

  13. #13
    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
    Si tu réfléchis un tout petit peu au type d'intervalles que tu manipules avec les boucles, tu constatera que tu constatera que tu travailles toujours avec des intervalles semi ouvert [ valeur debut ; valeur fin [ (incluant la valeur de début, excluant la valeur de fin).

    Cela se remarque particulièrement bien avec les boucle pour, car, même si la raison principale est que l'indice des tableaux C styles commencent à 0, si tu as un tel tableau de l'ordre de int tab[10], la boucle pour qui te permettra de passer tous les éléments en revue prendra la forme de for (int i = 0 ; i < 10 ; ++i) .

    La condition de fin ( i < 10 ) indique en effet que l'on ne retourne plus dans la boucle dés que la condition n'est plus vérifiée (lorsque i vaut 10, i < 10 est évalué à faux, et on ne retourne plus dans la boucle )

    Il en va de même avec les boucles "tant que" dans lesquelles tu retourne tant que la condition est vérifiée ou avec les boucles "jusqu'à" dans lesquelles tu retourne jusqu'à ce que la condition ne soit plus vérifiée.

    Si la fonction end() renvoyait le dernier élément de la collection, cet élément ne serait pas pris en compte, vu qu'il est exclu de l'intervalle.

    Il est donc normal que la fonction end renvoie... ce qui vient juste après le dernier élément, de manière à ce que lorsque l'on quitte la boucle, l'ensemble des éléments de la collection ait été traité

    De même, si tu effectue une recherche dans ta collection, il faut que tu puisse déterminer si l'itérateur que tu as obtenu grâce à la recherche fait référence à un élément de celle-ci.

    Si end renvoyait un itérateur sur le dernier élément, tu ne pourrais tester que deux choses: soit l'itérateur renvoyé par la fonction de recherche fait référence au dernier élément, soit ce n'est pas le dernier élément, mais cela ne donnerait aucune indication quant à l'existence ou non de l'élément recherché dans la collection

    Là encore, le fait que end renvoie un itérateur sur un élément qui ne se trouve pas dans la collection nous permet de déterminer à tout les coups si, oui ou non, l'élément recherché existe bien dans la collection

    Car, soit l'itérateur renvoyé par la fonction de recherche n'est pas égal à celui renvoyé par end, et, dans ce cas, l'itérateur fait référence à un élément de la collection, soit, au contraire, l'itérateur renvoyé par la fonction de recherche est égal à l'itérateur renvoyé par la fonction end() et... nous avons un élément qui n'est pas dans la collection

    Ainsi, tu te trouves en mesure d'écrire un code proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    iterator it;
    /* on cherche un élément donné dans la collection */
    if(it == collection.end() ) 
    {
        /* oups... l'élément n'a pas été trouvé...
         * ce qu'il faut faire dans ce cas
         */
    }
    else
    {
        /* génial, on a trouvé l'élément ..
         * ce qu'il faut faire dans ce cas
         */
    }
    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

  14. #14
    Membre Expert
    Avatar de Aspic
    Homme Profil pro
    Étudiant
    Inscrit en
    Août 2005
    Messages
    3 905
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hauts de Seine (Île de France)

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Août 2005
    Messages : 3 905
    Par défaut
    Vu sous cet angle, tout s'explique

    Encore merci pour toutes ces explications
    Qui ne tente rien n'a rien !
    Ce qui ne nous tue pas nous rends plus fort !!
    Mon projet ZELDA en C++/Allegro
    http://www.tutoworld.com - Le Forum -
    Mes ressources Dotnet (cours, sources, tutos)
    --------------------------------------------
    + + =

    Ne pas oublier le Tag !

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

Discussions similaires

  1. Réponses: 7
    Dernier message: 27/05/2006, 13h30
  2. Problème libération de mémoire?
    Par Bartuk dans le forum C
    Réponses: 7
    Dernier message: 28/12/2005, 17h20
  3. Libération de mémoire
    Par petitcoucou31 dans le forum Langage
    Réponses: 1
    Dernier message: 16/09/2005, 14h10
  4. [Debutant(e)]problème de libération de mémoire
    Par skywalker3 dans le forum Eclipse Java
    Réponses: 1
    Dernier message: 10/02/2005, 17h38
  5. Réponses: 25
    Dernier message: 16/07/2003, 20h41

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