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 :

Classe pour tableau d'entiers à taille variable


Sujet :

C++

  1. #1
    Membre régulier

    Profil pro
    Étudiant
    Inscrit en
    Mars 2013
    Messages
    118
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Mars 2013
    Messages : 118
    Points : 81
    Points
    81
    Par défaut Classe pour tableau d'entiers à taille variable
    Bonjour,

    Je veux créer une classe TableauInt qui a pour attributs un tableau d'entiers et sa taille, ainsi que les méthodes suivantes :
    - constructeur sans paramètres
    - constructeur avec la taille
    - destructeur
    - getteur/setteur sur un élement du tableau grâce a l'indice
    - getteur sur la taille
    - saisie
    - affichage

    J'ai donc fait ceci (.h en premier, .cpp en suivant):

    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
    class TableauInt{
     
        private:
            int* tableau;
            int taille;
     
        public :
            TableauInt();
            TableauInt(int);
            ~TableauInt();
            int getElement(int);
            void setElement(int, int);
            int getTaille();
            void saisie();
            void afficher();
    };
    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
    #include "TableauInt.h"
    #include <iostream>
     
    using namespace std;
     
    TableauInt::TableauInt(): taille(0), tableau(NULL) { }
     
    TableauInt::TableauInt(int t): taille(t), tableau(new int[taille]) { }
     
    TableauInt::~TableauInt() {
        delete [] tableau;
    }
     
     int TableauInt::getElement(int i) {
        return tableau[i];
     }
     
     void TableauInt::setElement(int i, int elt) {
        tableau[i] = elt;
     }
     
      int TableauInt::getTaille() {
        return taille;
     }
     
     void TableauInt::saisie() {
        for (int i=0 ; i < taille ; i++) {
            cout << "Saisir l'entier d'indice " << i << " :     ";
            cin >> tableau[i];
        }
     }
     
    void TableauInt::afficher() {
        for (int i=0 ; i < taille ; i++) {
            cout << tableau[i] << "  ";
        }
    }
    Mais ensuite, je voudrais ajouter un setteur sur la taille : si la nouvelle taille est supérieure a l'ancienne alors on complète le tableau avec des 0, et si elle est inférieure alors on enlève les valeurs dont les indices sont les plus élevés.

    Ma question est: comment faire varier la taille de mon tableau ?

    Merci.

  2. #2
    Rédacteur

    Avatar de ram-0000
    Homme Profil pro
    Consultant en sécurité
    Inscrit en
    Mai 2007
    Messages
    11 517
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 61
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Consultant en sécurité
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : Mai 2007
    Messages : 11 517
    Points : 50 367
    Points
    50 367
    Par défaut
    Tu es sûr de vouloir travailler avec un "int * tableau".

    un "std::vector" serait plus cool (et plus c++ aussi)
    Raymond
    Vous souhaitez participer à la rubrique Réseaux ? Contactez-moi

    Cafuro Cafuro est un outil SNMP dont le but est d'aider les administrateurs système et réseau à configurer leurs équipements SNMP réseau.
    e-verbe Un logiciel de conjugaison des verbes de la langue française.

    Ma page personnelle sur DVP
    .

  3. #3
    Membre régulier

    Profil pro
    Étudiant
    Inscrit en
    Mars 2013
    Messages
    118
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Mars 2013
    Messages : 118
    Points : 81
    Points
    81
    Par défaut
    C'est la deuxième fois qu'on me pose cette question, du coup je prends conscience que ça doit être plus facile de travailler avec vector.
    Malheureusement dans le cadre de mon cours et pour réussir mon examen, c'est "bel et bien" un int* que je dois utiliser ...

    Même si "ça fait pas très C++", j'espère quand même que vous pourrez m'aider.

  4. #4
    Rédacteur

    Avatar de ram-0000
    Homme Profil pro
    Consultant en sécurité
    Inscrit en
    Mai 2007
    Messages
    11 517
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 61
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Consultant en sécurité
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : Mai 2007
    Messages : 11 517
    Points : 50 367
    Points
    50 367
    Par défaut
    Citation Envoyé par Jéjé34 Voir le message
    Malheureusement dans le cadre de mon cours et pour réussir mon examen, c'est "bel et bien" un int* que je dois utiliser ...
    OK, je me doutais un peu de la réponse mais c'était pour s'assurer

    Citation Envoyé par Jéjé34 Voir le message
    Ma question est: comment faire varier la taille de mon tableau ?
    Pour réallouer, je ne vois que 2 solutions :
    une solution new/delete
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    new_table = new (nouvelle taille)
    recopie des éléments de old_table dans new_table
    delete old_table
    old_table = new_table
    une solution à base de realloc/free (plus C que C++ du coup)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    table = realloc(taille)
    table = realloc(nouvelle taille)
    table = realloc(encore nouvelle taille)
    le reallloc retaille la mémoire et recopie en même temps les anciennes données.

    regarde le man realloc() pour voir comment cela marche (PS, ne pas oublier le free du tableau quand tu as fini)
    Raymond
    Vous souhaitez participer à la rubrique Réseaux ? Contactez-moi

    Cafuro Cafuro est un outil SNMP dont le but est d'aider les administrateurs système et réseau à configurer leurs équipements SNMP réseau.
    e-verbe Un logiciel de conjugaison des verbes de la langue française.

    Ma page personnelle sur DVP
    .

  5. #5
    Membre chevronné Avatar de Ehonn
    Homme Profil pro
    Étudiant
    Inscrit en
    Février 2012
    Messages
    788
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 34
    Localisation : France

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

    Informations forums :
    Inscription : Février 2012
    Messages : 788
    Points : 2 160
    Points
    2 160
    Par défaut
    Il faut juste que tu es conscience que tu es en train de recoder std::vector mais en moins bien. (Tu réinventes la roue carrée.)

    Sinon, pour changer la taille de ton tableau, il faut en déclarer un plus grand, recopier l'ancien dans le nouveau et ne surtout pas oublier de supprimer l'ancien.
    Généralement, on travaille avec une taille (la partie utilisée du tableau) et une capacitée (la vraie taille du tableau).
    Lorsqu'on manque de place, il est "courant" d'allouer deux fois l'ancienne taille.

  6. #6
    Membre régulier

    Profil pro
    Étudiant
    Inscrit en
    Mars 2013
    Messages
    118
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Mars 2013
    Messages : 118
    Points : 81
    Points
    81
    Par défaut
    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
    void TableauInt::setTaille(int newSize) {
        if (newSize > taille) {
            int* newTable = new int[newSize];
            for (int i=0 ; i < taille ; i++) {
                newTable[i] = tableau[i];
            }
            for (int j=taille ; j < newSize ; j++) {
                newTable[j] = 0;
            }
            delete [] tableau;
            tableau = newTable;
            taille = newSize;
        }
        if (newSize < taille) {
            int* newTable = new int[newSize];
            for (int i=0 ; i < newSize ; i++) {
                newTable[i] = tableau[i];
            }
            delete [] tableau;
            tableau = newTable;
            taille = newSize;
        }
    }
    Je suis parvenu a ça grâce a vos conseils, ça fonctionne.
    Merci à vous.

    PS: Je coche pas "Résolu" toute de suite, j'aurai surement d'autres questions dans la soirée.

  7. #7
    Membre régulier

    Profil pro
    Étudiant
    Inscrit en
    Mars 2013
    Messages
    118
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Mars 2013
    Messages : 118
    Points : 81
    Points
    81
    Par défaut
    Quand je fais ceci dans le main :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    TableauInt T1(3);
        T1.saisie();
        T1.afficher();
     
        TableauInt T2(T1);
        T2.afficher();
    Le programme plante lors de son exécution, apparemment le constructeur par copie défini par defaut ne convient pas.
    Je n'arrive pas a comprendre pourquoi.

  8. #8
    Rédacteur/Modérateur


    Homme Profil pro
    Network game programmer
    Inscrit en
    Juin 2010
    Messages
    7 113
    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 113
    Points : 32 958
    Points
    32 958
    Billets dans le blog
    4
    Par défaut
    Citation Envoyé par Jéjé34 Voir le message
    Le programme plante lors de son exécution, apparemment le constructeur par copie défini par defaut ne convient pas.
    Le constructeur par copie par défaut (de même que l'opération =) ne font rien d'autre qu'un appel à la même méthode pour chacun des membres.

    Ton membre est un simple int*, quand il est "copié", on copie juste la valeur pointé (le premier élément du tableau).
    Le crash survient forcément à la destruction, tu fais un delete dans la première class et delete un élément déjà delete dans la 2°.

    A partir du moment où tu manipules des pointeurs, il faut faire attention à ce qu'on fait, et le plus souvent les implémentations par défaut ne suffisent clairement pas.
    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.

  9. #9
    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 518
    Points
    41 518
    Par défaut
    Citation Envoyé par Ehonn Voir le message
    Il faut juste que tu es conscience que tu es en train de recoder std::vector mais en moins bien. (Tu réinventes la roue carrée.)
    C'est dans un but scolaire, il faut bien passer par là.
    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.

  10. #10
    Membre régulier

    Profil pro
    Étudiant
    Inscrit en
    Mars 2013
    Messages
    118
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Mars 2013
    Messages : 118
    Points : 81
    Points
    81
    Par défaut
    Citation Envoyé par Bousk Voir le message
    Le constructeur par copie par défaut (de même que l'opération =) ne font rien d'autre qu'un appel à la même méthode pour chacun des membres.

    Ton membre est un simple int*, quand il est "copié", on copie juste la valeur pointé (le premier élément du tableau).
    Le crash survient forcément à la destruction, tu fais un delete dans la première class et delete un élément déjà delete dans la 2°.

    A partir du moment où tu manipules des pointeurs, il faut faire attention à ce qu'on fait, et le plus souvent les implémentations par défaut ne suffisent clairement pas.
    Merci. Du coup, j'ai crée mon propre constructeur par copie qui semble fonctionner et que voici :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    TableauInt::TableauInt(const TableauInt& T): taille(T.taille) {
       tableau = new int[taille];
       for (int i=0 ;i<taille ; i++) {
            tableau[i] = T.tableau[i];
       }
    }
    Par contre, pour mon opérateur d'affectation j'ai une erreur de compilation sur la première ligne:
    'TableauInt& operator=(const TableauInt&)' must be a nonstatic member function

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    TableauInt::TableauInt& operator=(const TableauInt& T) {
      if (&T != this) {
            taille = T.taille;
            delete[] tableau;
            tableau = new int[taille];
            for (int i=0 ; i<taille ; i++) {
                tableau[i] = T.tableau[i];
            }
        }
        return *this;
    }

  11. #11
    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 611
    Points
    30 611
    Par défaut
    Salut,

    Il faut juste faire attention au fait que ta classe TableauInt a, typiquement, sémantique de valeur, dans le sens où tu voudras sans doute pouvoir faire quelque chose comme
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    TableauInt tab1(10);
     
    /*... */
     
    TableauInt tab2 = tab1;
    /* ... */
     
    /* ou */
    TableauInt tab3(tab1);
    Ton code en son état actuel va poser des problèmes aussi bien pour tab2 que pour tab3

    Je m'explique:

    Si l'on regarde le destructeur de ta classe, on se rend compte qu'il est implémenté sous la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    TableauInt::~TableauInt() {
        delete [] tableau;
    }
    ce qui est la manière correcte de l'implémenter, pour éviter les fuites mémoire.

    Seulement, il faut savoir qu'un certain Coplien a déterminé il y a déjà ... ouh, de nombreuses années, le fait qu'une classe devrait présenter quatre comportements majeurs:
    1. la possibilité de se construire (fournie par les constructeurs que tu as implémentés)
    2. la possibilité de se copier
    3. la possibilité d'être réaffectée (l'opérateur "=")
    4. la possibilité de se détruire (fournie par le destructeur que tu as implémenté)
      (pour être précis, il a fourni plusieurs formes différentes, mais c'est celle qui nous intéresse ici, et elle est connue sous le nom de "forme canonique orthodoxe de Coplien )
    Et c'est là que les choses deviennent intéressantes.

    En effet, le compilateur va, sauf ordre contraire de ta part (comprends: si tu ne fournis pas une implémentation personnelle de ces quatre comportement), implémenter ces quatre comportements de manière triviale:
    • le constructeur par copie se contentera de copier les membres de ton objet, dans l'ordre de leur déclaration
    • L'opérateur d'affectation se contentera de faire une affectation des différents membres (dans l'ordre de leur déclaration, toujours) avant de renvoyer ce qui est pointé par this
    • le destructeur se contentera de détruire les différents membres dans l'ordre inverse de leur déclaration.
    Comme tu as fourni une implémentation personnelle (et tout à fait juste au demeurant) pour le destructeur, et plusieurs implémentation personnelles pour le constructeur, ce sont ces implémentations personnelles qui seront utilisées, mais le compilateur fournira sa propre implémentation triviale pour le constructeur par copie (TableauInt(TableauInt const &)) et pour l'opérateur d'affectation (TableauInt & operator = (TableauInt const &)).

    Et c'est là que les problèmes vont commencer

    Pour bien comprendre ces problèmes, il faut avoir conscience de ce que sont réellement les pointeurs : ce ne sont jamais que des variables de type numérique (souvent non signées, d'ailleurs) qui correspondent à l'adresse à laquelle on pourra accéder à un élément du type indiqué (et tant pis si tu en avait conscience, un petit rappel n'est jamais superflu )

    Le fait, c'est que lorsque le compilateur va copier (ou affecter) ce pointeur, il ne va copier (ou affecter) que le pointeur (autrement dit l'adresse représentée par le pointeur d'origine) et ne va absolument rien faire en ce qui concerne la donnée sous-jacente, et ca va occasionner une série de problèmes...

    Mon code va mettre tous les problèmes en évidences
    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
    int main()
    {
        TableauInt tab(10);
        tab.setElement(2,15); // getElement(2) == 15 ;)
        if(/* une condition quelconque */)
        {
            TableauInt tab2 = tab; // on croit avoir un autre tableau indépendant de tab
            tab2.setElement(2,20); // sauf que setElement travaille avec la même adresse
                                   // pour ses différents éléments que tab
            // tab.getElement() == 15 ? non! tab.getElement() == ... 20
        } //CRACK : tab2 est détruit (~TableauInt automatiquement appelé pour tab2
         // ce qui implique delete[] tab2::tableau et comme tab2::tableau == tab::tableau ... )
        /* ...*/
        return 0; // tab est détruit (BOUM :~TableauInt automatiquement appelé pour tab
                  // ce qui implique delete[] tab::tableau... qui a été libéré en meme temps 
                  // que tab2::tableau)
    }
    Le comportement observé dans le commentaire de la ligne 10 est, en soi, déjà fort embêtant (*), mais le gros problème prend sa source à la ligne 11 et trouve son épilogue à la ligne 14 car la tentative de libération d'une adresse mémoire déjà libérée provoque un comportement indéfini (souvent indiqué par une erreur de segmentation )
    (*) on se serait en effet attendu à ce que tab.getElement(2) renvoie 15 et tab2.getElement(2) 20, non

    Il est donc temps de rajouter une règle en corolaire à la forme canonique orthodoxe de Coplien.

    Cette règle est connue sous le nom de big rule of three, ou, si tu préfères en français, la "grande règle des trois".

    Cette règle nous dit que si tu as du fournir une implémentation spécifique pour n'importe quelle fonction parmi le constructeur par copie, l'opérateur d'affectation ou le destructeur, alors, il faut que tu fournisse une implémentation spécifique adaptée pour les trois.

    Autrement dit : comme tu as fourni une implémentation spécifique pour le destructeur, tu dois penser à fournir une implémentation spécifique pour le constructeur par copie et pour l'opérateur d'affectation.

    Commençons par le constructeur par copie.

    L'idée du constructeur par copie est de permettre un code proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    int main()
    {
        TableauInt tab(3);
        TableauInt tab2(tab);
        /* tab et tab2 gèrent chacun leurs propres valeurs de manière indépendante */
        /* ... */
        return 0;
    }
    Pour y arriver, bah, c'est *relativement* simple : "YAKA" veiller à ce que le constructeur par copie fasse une copie en profondeur du pointeur d'origne.

    Pour ta classe TableauInt, cela pourrait ressembler à quelque chose comme
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    TableauInt::TableauInt(TableauInt const & rhs):tableau(rhs.taille!=0? new int[rhs.taille] : NULL),taille(rhs.taille)
    {
        memncpy(tableau, rhs.tableau, taille * sizeof(int));
    }
    Enfin, pour comprendre la raison pour laquelle il faut redéfinir aussi l'opérateur d'affectation (et par la même occasion, comment s'y prendre), réfléchissons un peu à ce qui se passerait avec le code
    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
    int main()
    {
        TableauInt tab(10);
        /* ...*/
        TableauInt tab2(20); // aucun problème : tab2::tableau != tab::tableau
        tab.setElement(2,10);
        tab2.setElement(2,15);
        /* ...*/
        tab = tab2; // 1- CRACK tab::tableau == tab2::tableau, mais on a perdu toute référence
                    // au tab::tableau d'origine ==> fuite mémoire
                    // 2- CRACK tab::tableau == tab2::tableau
        tab.setEment(2,20);
        /* OUCH... tab2.getElement(2) == 15 ? non!!! tab2.getElement(2) == 20 
         */
        /* ...*/
        return 0; // BOUM : tab2 est détruit en premier (~TableauInt appelé pour
                  // tab2 ce qui équivaut à delete [] tab2::tableau)
                  // tab est détruit en second (~TableauInt appelé pour
                  // tab ce qui équivaut à delete [] tab::tableau et comme
                  // tab::tableau == tab2::tableau ==>double libération de la mémoire)
    }
    Hé oui...

    Dans le meilleur des cas, ton application plantera au moment de quitter, à cause de la double tentative de la mémoire.

    C'est déjà assez embêtant pour essayer de l'éviter, mais, en plus, on observe une fuite mémoire, et ca, c'est de nature à rendre tout ton système instable, ce qui est encore moins agréable

    Et je ne parle pas du désagrément observé au niveau de getElement re

    Par chance, il existe un idiome appelé copy-and-swap qui permet de résoudre tous ces problèmes.

    L'idée, lorsque l'on veut assigner un objet à un autre, c'est de faire en interne (comprends : au niveau de l'opérateur d'affectation) une copie de l'objet assigné (comprends : celui qui se trouve à droite du =), puis d'intervertir les membres de l'objet courent avec ceux de la copie.

    Comme l'opérateur d'affectation n'est jamais qu'une fonction et que la copie n'est jamais qu'une variable locale à cette fonction, la copie sera correctement détruite (et son destructeur appelé) au moment de quitter la dite fonction.

    Comme nous aurons interverti le pointeur d'origine de l'objet courent et celui de la copie, la mémoire correspondant au pointeur d'origine sera correctement libérée.

    Et comme nous aurons agit sur une copie de l'objet assigné, il y aura déjà eu une copie en profondeur du pointeur, ce qui fait que tab et tab2 (selon mon exemple) utiliseront bel et bien deux adresses différentes

    Enfin, voyons un peu comment nous y prendre

    L'idéal est de rajouter une fonction membre "swap" qui intervertira les différents membres entre eux.

    Vu que c'est pour l'apprentissage, je vais te présenter différents moyens "faits main", mais le résultat sera identique
    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
    void TableauInt::swap(TableauInt & rhs)
    {
        int * tabtemp= tableau; 
        tableau = rhs.tableau;
        rhs.tableau = temp
        int tailleTemp = taille;
        taille = rhs.taille;
        rhs.taille = tailleTemp;
    }
    /* OU - OU - OU - en utilisant XOR */
    void TableauInt::swap(TableauInt & rhs)
    {
        tableau ^=  rhs.tableau;
        rhs.tableau ^= tableau;
        tableau ^= rhs.tableau;
        taille^=  rhs.taille;
        rhs.taille^= taille;
        taille^= rhs.taille;
    }
    Mais la STL nous fournit une fonction swap, disponible dans l'espace de noms std par simple inclusion du fichier <algorithme> et l'on pourrait donc envisager d'écrire cette fonction sous la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    void TableauInt::swap(TableauInt & rhs)
    {
        std::swap(tableau, rhs.tableau);
        std::swap(taille, rhs.taille);
    }
    Et, enfin, nous redéfinirions l'opérateur d'affectation sous la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    TableauInt & operator = (TableauInt const & rhs)
    {
       TableauInt temp(rhs); // copie de l'élément affecté
       swap(temp); //inversion des membre de this avec la copie
       return *this; // renvoie une référence sur l'élément courent
                     // la copie est détruite en sortant de la fonction
    }
    Et maintenant, tout fonctionne:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    int main()
    {
        TableauInt tab(10);
        tab.setElement(2,10);
        Tableauint tab2(tab); //tab2.getElement(2) == 10
        tab2.setElement(2,20) // tab2.getElement(2) == 20, tab.getElement(2) == 10
        Tableauint tab3(15);
        tab3 = tab; // pas de fuite mémoire, tab3.getElement(2) == 10
        tab.setElement(2,5); tab.getElement(2) == 5, tab3.getElement == 10
        return 0;
    } // destruction de tab3
      // destruction de tab2
      // destruction de tab
      // aucun problème parce que chaque tableau manipule une adresse qui lui est propre
    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 régulier

    Profil pro
    Étudiant
    Inscrit en
    Mars 2013
    Messages
    118
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Mars 2013
    Messages : 118
    Points : 81
    Points
    81
    Par défaut
    Merci beaucoup !
    Ton code et surtout les commentaires m'ont ouverts les yeux sur ce que sont un constructeur par copie, un opérateur d'affectation et comment fonctionne un destructeur ! (je pense que je vais meme me l'imprimer)
    Et surtout, je n'avais pas a encore assimiler que c'était l'adresse (!) qui était copié lors de l'appel au constructeur par copie défini par défaut.
    Bref, merci pour tout ça.

    Par contre je pense que tu étais en train d'écrire lorsque j'ai posté mon précédent message.

    Pour ce qui est du constructeur par copie, je pense avoir résolu mon problème en créant un nouveau tableau dynamique de même taille et qui a donc une adresse différente.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    TableauInt::TableauInt(const TableauInt& T): taille(T.taille) {
       tableau = new int[taille];
       for (int i=0 ;i<taille ; i++) {
            tableau[i] = T.tableau[i];
       }
    }
    En tout cas, quand je fais les mêmes tests que toi quand ça fonctionne très bien.

    Pour ce qui est de l'opérateur d'affectation, j'aimerai d'abord tester ma méthode avant d'utiliser "swap" mais je ne comprends pas l'erreur de compilation.

  13. #13
    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 611
    Points
    30 611
    Par défaut
    Citation Envoyé par Jéjé34 Voir le message
    Merci beaucoup !
    Ton code et surtout les commentaires m'ont ouverts les yeux sur ce que sont un constructeur par copie, un opérateur d'affectation et comment fonctionne un destructeur ! (je pense que je vais meme me l'imprimer)
    Et surtout, je n'avais pas a encore assimiler que c'était l'adresse (!) qui était copié lors de l'appel au constructeur par copie défini par défaut.
    Bref, merci pour tout ça.
    Mais, de rien, ca fait toujours plaisir de constater qu'une prose est utile
    Par contre je pense que tu étais en train d'écrire lorsque j'ai posté mon précédent message.

    Pour ce qui est du constructeur par copie, je pense avoir résolu mon problème en créant un nouveau tableau dynamique de même taille et qui a donc une adresse différente.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    TableauInt::TableauInt(const TableauInt& T): taille(T.taille) {
       tableau = new int[taille];
       for (int i=0 ;i<taille ; i++) {
            tableau[i] = T.tableau[i];
       }
    }
    En tout cas, quand je fais les mêmes tests que toi quand ça fonctionne très bien.
    Disons que j'avais honteusement profité du fait que tu travailles avec des int pour me contenter d'un "simple" memcpy.

    Mais le fait de copier explicitement chaque élément n'est pas forcément plus mal
    Pour ce qui est de l'opérateur d'affectation, j'aimerai d'abord tester ma méthode avant d'utiliser "swap" mais je ne comprends pas l'erreur de compilation.
    C'est une simple erreur d'attention...
    ton code
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    TableauInt::TableauInt& operator=(const TableauInt& T) {
    déclare une fonction libre "opérator=" qui renvoie une référence sur un objet de type TableauInt::TableauInt, c'est à dire que le compilateur s'attendrait à trouver quelque chose comme
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    class TableauInt
    {
        class TableauInt
        {
        };
        /* ... */
    };
    Si rien n'interdit d'avoir une classe imbriquée du même nom que la classe dans laquelle elle est imbriquée, étant donné que la résolution des noms pleinement qualifiés fera la distinction, ce n'est pourtant pas ce que tu as

    Ce que tu as, c'est une fonction membre de TableauInt qui renvoie une référence sur un objet de type TableauInt

    Il faut donc remplacer cette ligne par
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    TableauInt& TableauInt::operator=(const TableauInt& T) {
    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
    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 611
    Points
    30 611
    Par défaut
    Citation Envoyé par Jéjé34 Voir le message
    Pour ce qui est de l'opérateur d'affectation, j'aimerai d'abord tester ma méthode avant d'utiliser "swap" mais je ne comprends pas l'erreur de compilation.
    Oh, tu fais comme tu l'entends, je te donne des pistes, voilà tout...

    Mais penses :
    • qu'il est important d'éviter les fuites mémoires
    • qu'un code proche de tab = tab est tout à fait légal, et qu'il faut peut etre prendre des précautions
    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

  15. #15
    Membre régulier

    Profil pro
    Étudiant
    Inscrit en
    Mars 2013
    Messages
    118
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Mars 2013
    Messages : 118
    Points : 81
    Points
    81
    Par défaut
    Resolu ✓

    Merci koala01, et aux autres avant lui !

  16. #16
    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 518
    Points
    41 518
    Par défaut
    Juste une chose pour le copy-and-swap: prendre directement la source par valeur peut éviter la copie quand on utilise l'opérateur = avec un temporaire:
    Code C++ : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    TableauInt::TableauInt& operator=(TableauInt tmp) {
    	swap(tmp);
    	return *this;
    }
    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. Réponses: 4
    Dernier message: 25/04/2015, 16h51
  2. Classe pour tableau HTML
    Par gui80 dans le forum Langage
    Réponses: 14
    Dernier message: 25/09/2008, 10h04
  3. javascript pour tableau à largeur de colonnes variables
    Par barkleyone dans le forum Général JavaScript
    Réponses: 3
    Dernier message: 06/06/2006, 18h14
  4. Comment obtenir un tableau à taille variable ?
    Par marsupilami34 dans le forum Langage
    Réponses: 6
    Dernier message: 27/06/2005, 16h03
  5. Réponses: 10
    Dernier message: 15/03/2005, 11h31

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