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 :

Utiliser une surcharge d'operateur dans une autre classe du projet ?


Sujet :

C++

  1. #1
    Membre averti
    Homme Profil pro
    Étudiant
    Inscrit en
    Décembre 2012
    Messages
    15
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 34
    Localisation : Belgique

    Informations professionnelles :
    Activité : Étudiant
    Secteur : Industrie

    Informations forums :
    Inscription : Décembre 2012
    Messages : 15
    Par défaut Utiliser une surcharge d'operateur dans une autre classe du projet ?
    Bonjour à tous ,

    Mon programme est assez simple, je crée des objets 'Livre' que j'insère dans un objet 'VecteurTrie', dans lequel j'essaye de les trier par nom, puis par auteurs, pour ensuite les afficher.

    Voici mon code :

    surcharge des operateurs de comparaison dans Livre.cpp:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    int Livre::compare(Livre l)
    {   
        if(strcmp(titre,l.titre) < 0) return -1;
        else if(strcmp(titre,l.titre) > 0) return 1;
        else
        {
           for(int i=0; i<MAX; i++)
           {
             if(strcmp((auteurs[i]->Nom),(l.auteurs[i]->Nom))== -1) return -1;
             if(strcmp((auteurs[i]->Nom),(l.auteurs[i]->Nom))== 1) return 1;
           }
           return 0; // si titre et auteurs identiques
        }
    }
     
    int Livre::operator<(const Livre& l)
    {   
        return compare(l)== -1;
    }
     
    int Livre::operator==(const Livre& l)
    {   
        return compare(l)== 0;  
    }
     
    int Livre::operator>(const Livre& l)
    {   
        return compare(l)== 1;
    }

    Ma classe VecteurTrie.cpp
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
     
    template <class type> VecteurTrie<type>::VecteurTrie()
    {
        tab = new type[10];
        pTop = tab;
        nbrElem=0;                       
    }
     
    template <class type> VecteurTrie<type>::VecteurTrie(int ne)
    {   
        if(ne < 11)
        {
            nbrElem = ne;
        }
        else{ nbrElem = 10; }
     
        tab = new type[nbrElem];
        pTop=tab;
    }
     
    template <class type> VecteurTrie<type>::VecteurTrie(const VecteurTrie<type> &v)
    {
        nbrElem=v.nbrElem;
        tab=new type[nbrElem];
        tab=v.tab;
        pTop=v.pTop;     
    }
     
    // fonction membre
     
    template <class type> void VecteurTrie<type>::insert(type a)
    {
        if(nbrElem < 10)
        {
            *pTop = a;
            pTop++;
     
            int mmin;
            type temp;
     
            // le tri ne fonctionne pas avec les objets livres... 
     
            for (int i=0; i<nbrElem-1; i++)
            {
                mmin = i;
                for (int j=i+1; j<nbrElem; j++)
                {
                    if (tab[j] < tab[mmin])
                    {
                        mmin = j;
                    }
     
                    if (mmin != i)
                    {
                        temp = tab[i];
                        tab[i] = tab[mmin];
                        tab[mmin] = temp;
                    }
                }    
            }
        }
        else{ throw VectorFullException("Erreur: Le vecteur est plein."); }
    }
     
    template <class type> void VecteurTrie<type>::removeElem(type a)
    {
        if(nbrElem > 0)
        {
            int cpt = 0;
            for(int i=0; i<nbrElem; i++)
            {
                cpt++;
                if(a==tab[i])
                {
                    for(int j=cpt-1; j<nbrElem-1; j++)
                    {
                        tab[j]=tab[j+1];
                    }
                    nbrElem--;
                    cout << "Element retire : " << a << endl << endl;
                }
                else { cout << "Error: Element inexistant." << endl << endl; }
            }
        }
        else { cout << "Error: Vecteur vide." << endl << endl; }
    }
     
    template <class type> void VecteurTrie<type>::displayy(void)
    {
        if (nbrElem == 0)
        {
            cout << "Vecteur vide" <<endl;
        }
        else
        {
            for(type *i=pTop; i!=tab;)
            {
                cout << *--i << "    ";
            }
            cout << endl;
        }
    }
     
    // class destructor
    template <class type> VecteurTrie<type>::~VecteurTrie()
    {
    	delete[] tab;
    }
     
    template class VecteurTrie<int>;
    template class VecteurTrie<Livre>;
    Ma classe VecteurTrie.h (au cas où):
    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
    #ifndef VECTEURTRIE_H
    #define VECTEURTRIE_H
     
    #include "Vecteur.h"
    #include "vectorfullexception.h"
    #include "livre.h"
     
    /*
     * No description
     */
    template <class type> class VecteurIter;
     
    template <class type> class VecteurTrie : public Vecteur
    {
        protected:
            type *tab;
            type *pTop;
            int nbrElem;
     
        public:
            VecteurTrie();
            VecteurTrie(int nb);
            VecteurTrie(const VecteurTrie<type> &v);
            ~VecteurTrie();
     
            type& operator[] (int i) {return *(tab+i);}
    	    bool operator<(const VecteurTrie& v) const {return tab < v.tab;}
     
            void insert(type a);
            void removeElem(type a);
            void displayy();
     
            friend class VecteurIter<type>;
    };
     
    #endif // VECTEURTRIE_H

    Ma classe de test main.cpp
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    #include <cstdlib>
    #include <iostream>
    #include <stream.h>
     
    #include "Livre.h"
    #include "Vecteur.h"
    #include "VecteurTrie.h"
     
    using namespace std;
     
    int main(int argc, char *argv[])
    {   
        Livre p;
        int elem;
        int nbelem;
     
        VecteurTrie<Livre> vtl(3);    // je décide qu'il y a trois elems
     
        cout << "Testons maintenant avec des livres." << endl;
        cout << endl;
     
           // je crée les livres:
        char *aut[3]={"Tintin","Spirou","Haddock"};
     
        Livre p0 ("Coucou je suis Pierre Bolle", 3, "977-2-87777-007-1", aut, 3, 45,"Science-fiction");
        Livre p1 ("Coucou ", 3, "977-2-87799-007-2", aut, 3, 55,"Germain");
        Livre p2 ("Coucou je suis ", 3, "977-2-87788-007-3", aut, 3, 65,"Fantaisie");
     
        vtl.insert(p0);
        vtl.insert(p1);
        vtl.insert(p2);
     
        //cout << "Vecteur de trois livres:" << endl << endl;
        //vtl.displayy();
     
     
        system("PAUSE");
        return EXIT_SUCCESS;
    }
    Le problème est qu'au moment de l'exécution, mon programme s'arrête crash, sans donner le moindre message d'erreur.
    Je précise que si je teste ma surcharge directement dans la classe main, elle fonctionne très bien.
    Si je teste mon tri avec des entiers, cela fonctionne très bien également.

    Je débute en c++ et j'ai bien sur bien lu mon cours ainsi que la FAQ du forum avant de poster

    Merci d'avance pour votre aide.

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

    Informations professionnelles :
    Activité : aucun

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

    Je le dis tout de suite, je n'ai pas lu tout le code, mais:

    Plutôt que d'utiliser des chaine de caractères "C style", tu devrais utiliser la classe std::string, qui est beaucoup plus sécurisante à l'utilisation

    Je ne sais pas ce que c'est que ta classe Vecteur, mais, plutôt que d'utiliser une classe personnelle, tu devrais utiliser la classe std::vector, qui, encore une fois, est surement plus sécurisante que n'importe quelle implémentation similaire "a mano".

    Un tableau trié (ou n'importe quelle collection triée) ne devrait jamais hériter de tableau (ou de la collection équivalente non triée) car la notion de tri impose des préconditions incompatibles avec la notion des collections non triées.

    En effet, la dernière chose à vérifier pour déterminer si l'on peut procéder à un héritage est de s'assurer que les règles de la programmation par contrat soient respectées.

    Or, dans un héritage publique, ces règles imposent que les préconditions ne peuvent pas être plus restrictives dans le type dérivé que dans le type de base.

    Et, comme la précondition d'une collection triée est ... qu'elle soit triée avant toute manipulation, et que cette précondition n'existe pas dans une collection non triée, les préconditions sont plus importantes dans la collection triée.

    La règle n'est donc pas respectée et l'héritage doit par conséquent être abandonné

    Ceci étant dit, comme VecteurTrie hérite de Vecteur, on peut décemment estimer que ta classe Vecteur s'occupe déjà de gérer elle-même la mémoire pour les différents éléments qu'elle doit manipuler.

    Dés lors, pourquoi rajouter des membres comme tab et nbrElem dans ton tableau trié n'est-ce pas, typiquement, des données qui se retrouvent dans n'importe quel vecteur non trié
    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 Ehonn
    Homme Profil pro
    Étudiant
    Inscrit en
    Février 2012
    Messages
    788
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    Localisation : France

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

    Informations forums :
    Inscription : Février 2012
    Messages : 788
    Par défaut
    Bonjour

    Il y a pas mal d'erreur dans ce code. Mis à part une dimension pédagogique ou d'approfondissement, en C++, on utilise std::string (à la place des tableaux de char) et std::vector (à la place d'une classe personnelle buguée).

    Pour tracer des erreurs qui provoquent l'arrêt du programme, je te conseille d'utiliser un débuggeur (comme GDB).
    Pour tracer des erreurs plus "silencieuses", je te conseille un outil comme valgrind qui pourra détecter les fuites mémoire et les utilisations de variations non initialisées.

    Citation Envoyé par Evijn Voir le message
    Mon programme est assez simple, je crée des objets 'Livre' que j'insère dans un objet 'VecteurTrie', dans lequel j'essaye de les trier par nom, puis par auteurs, pour ensuite les afficher.
    Ton programme est compliqué ^^
    Il devrait ressembler à ceci :
    - Création de la classe livre qui contient des std::string pour l'auteur, le titre, ISBN, la catégorie, ...
    - Création de deux foncteurs qui permettent de comparer deux livres selon le nom / l'auteur
    - Écriture du programme principal : création d'un std::vector de livres, utilisation de std::sort sur le vector avec le foncteur qui va bien selon le tri voulu (la FAQ donne un exemple qui fait exactement ça ).

    --- --- ---

    Edit : "grillé". J'avais pas vu pour l'héritage... FAQ C++ - Héritage - FAQ C++ - Héritage, LSP

  4. #4
    Membre émérite

    Profil pro
    Inscrit en
    Décembre 2008
    Messages
    533
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2008
    Messages : 533
    Par défaut
    Est-ce que ton type Livre implémente ces méthodes-ci ?
    C'est indispensable, puisque ton vecteur gère ses éléments par valeur.

  5. #5
    Membre averti
    Homme Profil pro
    Étudiant
    Inscrit en
    Décembre 2012
    Messages
    15
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 34
    Localisation : Belgique

    Informations professionnelles :
    Activité : Étudiant
    Secteur : Industrie

    Informations forums :
    Inscription : Décembre 2012
    Messages : 15
    Par défaut
    Bonjour à tous:

    Citation Envoyé par koala01 Voir le message
    Je le dis tout de suite, je n'ai pas lu tout le code, mais:

    Plutôt que d'utiliser des chaine de caractères "C style", tu devrais utiliser la classe std::string, qui est beaucoup plus sécurisante à l'utilisation

    Je ne sais pas ce que c'est que ta classe Vecteur, mais, plutôt que d'utiliser une classe personnelle, tu devrais utiliser la classe std::vector, qui, encore une fois, est surement plus sécurisante que n'importe quelle implémentation similaire "a mano".
    Je ne sais pas le professeur a utilisé des déclarations de chaine/vecteur/... "c-style" dans tout son cours et ne nous a pas demandé de faire autrement.

    Je suis débutant en c++ et l'énoncé de mon travail me demande de créer une classe VecteurTrie template, dérivant d'une classe Vecteur (qui ne connaît
    pas la notion de tri) encapsulant un tableau dynamique.

    Le niveau sécuritaire n'a pas beaucoup d'importance, je ne fais que suivre l'énoncé.

    Citation Envoyé par Ehonn
    Écriture du programme principal : création d'un std::vector de livres, utilisation de std::sort sur le vector avec le foncteur qui va bien selon le tri voulu (la FAQ donne un exemple qui fait exactement ça ).
    Je regarde à celà attentivement et j'essaye actuellement de l'appliquer, mais comme je n'ai jamais appris autrement qu'en "c-style" à l'école, la lecture n'est pas aisée

    Citation Envoyé par cob59
    Est-ce que ton type Livre implémente ces méthodes-ci ? C'est indispensable, puisque ton vecteur gère ses éléments par valeur.
    Que voulez-vous dire par là svp ? Je pense que ceci touche à mon problème puisque mon tri s'effectue correctement pour les entiers.

    Merci pour l'intérêt que vous portez à mon problème ,
    Evijn.

  6. #6
    Membre émérite

    Profil pro
    Inscrit en
    Décembre 2008
    Messages
    533
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2008
    Messages : 533
    Par défaut
    Citation Envoyé par Evijn Voir le message
    Que voulez-vous dire par là svp ? Je pense que ceci touche à mon problème puisque mon tri s'effectue correctement pour les entiers.
    Pour que ton vecteur puisse trier des objets Livre, il a besoin de savoir comment les copier, les affecter, etc. Il y a donc 2 solutions :

    Soit tu ne redéfinis AUCUNE des 4 méthodes citées dans la FAQ, auquel cas ta classe Livre se comportera implicitement comme une structure C classique.
    Exemple :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    Livre() : attr1(), attr2() {}
    Livre(const Livre& other) : attr1(other.attr1), attr2(other.attr2) {}
    Livre& operator=(const Livre& other) { attr1=other.attr1; attr2=other.attr2; }
    ~Livre() {}
    Soit tu redéfinis l'une d'entre elles, auquel cas tu dois impérativement redéfinir les 3 autres. Sinon elles garderont implicitement une implémentation vide (ce n'est pas ce qu'on veut, notamment pour le constructeur par recopie et l'affectation) avec pour conséquence --entre autre -- de faire échouer le tri.

    A côté de ça, il peut aussi être intéressant (voire obligatoire, mais pas dans ton cas) d'implémenter le swap : http://cpp.developpez.com/faq/cpp/?p...orme_canonique

  7. #7
    Membre averti
    Homme Profil pro
    Étudiant
    Inscrit en
    Décembre 2012
    Messages
    15
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 34
    Localisation : Belgique

    Informations professionnelles :
    Activité : Étudiant
    Secteur : Industrie

    Informations forums :
    Inscription : Décembre 2012
    Messages : 15
    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
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    // Constructeur par défaut
    Livre::Livre() : DocumentPapier()
    {             
       n=0;
       prix=0;
       ISBN=0;
       nombreAuteurs=0;
       genre=0;
    }
     
    // constructeur d'initialisation
    Livre::Livre(char *title)
    {
       titre = new char [strlen(title)+1];
       strcpy(titre,title);
     
       n=0; 
       prix=0; 
       ISBN=0; 
       nombreAuteurs=0; 
       genre=0;
     
       for(int i=0; i<MAX; i++)                 
       {
           auteurs[i]=0;
       }
    }
     
    // constructeur de copie
    Livre::Livre(const Livre &l) : DocumentPapier(l)
    {
        n= l.n;
        ISBN = new char [strlen(l.ISBN)];
        strcpy(ISBN,l.ISBN);
        prix=l.prix;
        genre = new char [strlen(l.genre)];
        genre=l.genre;
    }
     
    // Constructeur d'initialisation 2
    Livre::Livre(char *tit, int nb, char *isbn, char *autor[], int nbrAuteurs,
           float p, char *g) : DocumentPapier(autor, nbrAuteurs, tit)
    {
        titre = new char [strlen(tit)+1];
        strcpy(titre,tit);
        n=nb; 
        if (n<0) 
        {
            throw ErrNbExemplaire();
        }
     
        ISBN = new char [strlen(isbn)+1];
        strcpy(ISBN,isbn);
        if (strlen(ISBN) != 17*(sizeof(char))) 
        {
            throw ErrISBN();
        }
        nombreAuteurs=nbrAuteurs;
        Auteur *auteur[nombreAuteurs];
        for (int i=0; i<nombreAuteurs; i++)
        {
           auteurs[i]= new Auteur;
           (auteurs[i]->Nom)=autor[i];        
        }
        PrixAvecTVA pp(p);
        prix=pp.getPrix();
        if (prix<=0) 
        {
            throw ErrPrix();
        }
        genre=g;
    }
    Je pense avoir déjà fait ça correctement


    EDIT: j'ai oublié le destructeur

  8. #8
    Membre émérite

    Profil pro
    Inscrit en
    Décembre 2008
    Messages
    533
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2008
    Messages : 533
    Par défaut
    Et l'opérateur d'affectation ?

  9. #9
    Membre averti
    Homme Profil pro
    Étudiant
    Inscrit en
    Décembre 2012
    Messages
    15
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 34
    Localisation : Belgique

    Informations professionnelles :
    Activité : Étudiant
    Secteur : Industrie

    Informations forums :
    Inscription : Décembre 2012
    Messages : 15
    Par défaut
    oui j'avais également bien surchargé mon operateur d'affectation dans la classe Livre.cpp

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    Livre& Livre::operator=(const Livre& l)
    {
        titre = new char [strlen(l.titre)];
        strcpy(titre,l.titre);
        n=l.n;
        ISBN = new char [strlen(l.ISBN)];
        strcpy(ISBN,l.ISBN);
        prix=l.prix;
        genre = new char [strlen(l.genre)];
        genre=l.genre;
     
        nombreAuteurs=l.nombreAuteurs; 
        Auteur *auteur[nombreAuteurs];
        for(int i=0; i<nombreAuteurs; i++)
        {
          auteurs[i]= new Auteur;
          (auteurs[i]->Nom)=(l.auteurs[i])->Nom;  
        }     
        return *this; 
    }
    J'ai rajouté le code pour le destructeur, que j'avais oublié, mais mon problème persiste.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    Livre::~Livre()
    {
        delete ISBN;
    }

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    Citation Envoyé par Evijn Voir le message
    Je ne sais pas le professeur a utilisé des déclarations de chaine/vecteur/... "c-style" dans tout son cours et ne nous a pas demandé de faire autrement.
    Encore un prof qui n'a pas compris que C++ n'a strictement rien à voir avec C, même s'il ne renie pas son héritage!

    Ca m'énerve au plus haut point, car cela vous oblige a aborder des notions (essentiellement celles des pointeurs et de la gestion dynamique de la mémoire) très tôt dans l'apprentissage, alors que ces notions ne sont réellement utiles qu'une fois que l'on aborde les problèmes d'héritage et de polymorphisme.

    Et, surtout, cela t'obliges à te casser la tête sur certains problèmes collatéraux auxquels C++ fournit des réponses correctes et adaptées de manière standard.
    Je suis débutant en c++ et l'énoncé de mon travail me demande de créer une classe VecteurTrie template, dérivant d'une classe Vecteur (qui ne connaît pas la notion de tri) encapsulant un tableau dynamique.
    Et j'attire ton attention sur le fait que cet énoncé est conceptuellement faux.

    Or, la justesse conceptuelle est impérative, étant donné que toute application n'a, en définitive, pour but que de permettre de représenter une vue de l'esprit.

    Si tu tiens à respecter l'énoncé de ton prof, alors utilises plutôt l'héritage privé qui a la signification de "est implémenté en termes de", car c'est bel et bien la signification de la relation qui peut exister entre un tableau et un tableau trié.

    Quoi qu'il en soit, si Tableau n'est pas seulement une interface, je présume que la classe manipule déjà des membres équivalents à tab, ptop et nbElem.

    Si tu décides de faire hériter ta classe TableauTrie de Tableau, c'est pour réutiliser tout ce que tu peu qui vient de Tableau, rajouter tes propres membres équivalent n'a donc aucun sens
    Le niveau sécuritaire n'a pas beaucoup d'importance, je ne fais que suivre l'énoncé.
    Quand je parle de sécurité, je veux parler de sécurité d'utilisation!

    C'est à dire le genre de sécurité qui va te permettre d'utiliser des chaines de caractères (ou des éléments contigus) sans te poser de question, en étant sur qu'ils seront correctement copiés, assignés et détruit (en cas de besoin).

    Ce genre de sécurité est primordial en tous temps (surtout en temps d'apprentissage !) car elle te permet d'éviter de nombreux cas où ton application risque de "tomber en marche".
    Que voulez-vous dire par là svp ? Je pense que ceci touche à mon problème puisque mon tri s'effectue correctement pour les entiers.
    Lis la réponse que je vais faire ci dessous à cob, cela t'en apprendra d'avantage
    Citation Envoyé par cob59 Voir le message
    Soit tu ne redéfinis AUCUNE des 4 méthodes citées dans la FAQ, auquel cas ta classe Livre se comportera implicitement comme une structure C classique.
    Exemple :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    Livre() : attr1(), attr2() {}
    Livre(const Livre& other) : attr1(other.attr1), attr2(other.attr2) {}
    Livre& operator=(const Livre& other) { attr1=other.attr1; attr2=other.attr2; }
    ~Livre() {}
    Soit tu redéfinis l'une d'entre elles, auquel cas tu dois impérativement redéfinir les 3 autres. Sinon elles garderont implicitement une implémentation vide (ce n'est pas ce qu'on veut, notamment pour le constructeur par recopie et l'affectation) avec pour conséquence --entre autre -- de faire échouer le tri.
    Oui, mais non...

    Oui, le fait de ne pas fournir l'implémentation des autres fonction posera de sérieux problèmes pour (entre autres) le tri, ou pour la simple insertion d'éléments dans le tableau, mais non, ce n'est pas les quatre fonctions qui sont prises ensembles mais il n'y en a que trois qui importent.

    Je m'explique:

    Coplien a déterminé que tout objet ayant sémantique de valeur devait disposer de quatre propriétés de base en pouvant:
    1. être construit
    2. être copié
    3. être assigné
    4. être détruit.
    Il en a donc déduit la "forme canonique orthodoxe" dans laquelle il explique que tout objet ayant sémantique de valeur doit, au minimum, disposer de quatre fonction particulières, à savoir
    1. le constructeur Type::Type(/*...*/)
    2. le constructeur par copie Type::Type(Type const & )
    3. l'opérateur d'affectation Type & Type::operator = (Type const &)
    4. le destructeur Type::~Type()
    (NOTA: il a d'ailleurs également exposé deux formes canoniques adaptées au problème des classe ayant sémantique d'entité, mais je n'en parlerai pas ici )

    Le compilateur est parfaitement en mesure de fournir un comportement "par défaut" pour ces quatre fonctions si on ne lui donne aucune raison de ne pas le faire.

    S'il n'a pas de raison de ne pas le faire, il créera
    1. un constructeur ne prenant aucun argument qui appelle le constructeur ne prenant aucun argument de chaque membre de la classe dans l'ordre de leur déclaration
    2. un constructeur par copie qui appelle le constructeur par copie de chacun des membre de la classe dans l'ordre de leur déclaration
    3. un opérateur d'affectation qui appellera l'opérateur d'affectation de chaque membre de la classe dans l'ordre de leur déclaration
    4. un destructeur qui appellera le destructeur de chaque membre de la classe dans l'ordre inverse de leur déclaration
    Parmi ces quatre fonctions, trois sont intimement liées. Il s'agit du constructeur par copie, de l'opérateur d'affectation et du destructeur.

    Si, pour une raison quelconque, tu en viens à éprouver le besoin de définir un comportement particulier pour l'une de ces trois fonctions, alors, tu devras définir un comportement particulier pour chacune de ces trois fonctions.

    C'est l'énoncé de ce que l'on appelle la règle des trois grands (ou la grande règle des trois, je ne sais jamais )

    Ce qui est important, c'est d'avoir conscience que le fait de définir comportement particulier pour un constructeur autre que le constructeur par copie n'implique pas forcément la nécessité de fournir un comportement particulier pour les trois autres. L'implémentation d'un tel constructeur peut simplement être le résultat de l'absence du (ou du besoin d'utiliser un autre constructeur que le) constructeur ne prenant aucun argument pour un des membres, parfaitement copiable et assignable, de la classe, sans pour autant nécessiter la définition des trois autres comportements (Je présenterais un exemple de ce cas plus bas ).

    La principale raison qui t'incitera généralement à fournir un comportement particulier pour l'une de ces trois fonctions intimement liées (généralement le destructeur, d'ailleurs) est, souvent, le fait que tu manipule des pointeurs et, souvent, le résultat du recours à l'allocation dynamique de la mémoire.

    Il faut en effet se rappeler qu'un pointeur n'est jamais qu'une variable numérique entière (généralement) non signée, tout comme pourrait l'être int ou unsigned long, dont la particularité est de représenter l'adresse mémoire à laquelle on trouvera un élément du type attendu.

    En cela, lorsque l'on écrit un code proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    int main(){
        int * ptr1 = new int[10];
        int * ptr2 = ptr1;
        /* ...*/
        delete ptr1;
    }
    sont deux variables différentes mais de valeur identique: La valeur de ces deux variables correspond à l'adresse à laquelle on trouvera dix éléments de type int contigus en mémoire mais l'adresse représentée par ptr1 sera la même que celle représentée par ptr2, et c'est généralement là que les problèmes commencent à apparaitre.

    En effet, si j'écris une classe proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    class MaClass{
        public:
            MaClass(int nombre):nombre(nombre), tab(new int [nombre]){}
        private:
            int nombre; 
            int  * tab;
    };
    et que je laisse sciemment le compilateur se charger de fournir un comportement pour le constructeur par copie, l'opérateur d'affectation et le destructeur, je serai confronté à différents problèmes.

    Le premier est, bien sur, la fuite mémoire occasionnée que la mémoire allouée à tab n'est jamais libérée.

    L'une des possibilités (car il y en a d'autres, et c'est pour cela que le constructeur "classique" n'entre pas en ligne de compte) est, bien sur, de libérer cet espace mémoire dans le destructeur. Cela nous amène donc "naturellement" à modifier notre classe sous la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    class MaClass{
        public:
            MaClass(int nombre):nombre(nombre), tab(new int [nombre]){}
            ~MaClass(){delete [] tab;}
        private:
            int nombre; 
            int  * tab;
    };
    Mais cela ne résoudra qu'un seul problème (celui de la libération de la mémoire) et en mettra un autre en évidence: Le fait que le compilateur va copier et assigner les pointeurs exactement comme il le ferait pour des valeur numériques entières et qu'il va donc, simplement, copier / assigner la valeur de l'adresse représentée par le pointeur de l'objet d'origine au pointeur de l'objet "copié".

    Le code
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    int main(){
        MaClass c(10);  // très bien, c'est le constructeur qui est appelé
        {
            MaClass c2(c); // CRAK (1)
        } // CRACK (2)
        return 0;
    } // BOUM (3)
    continuera à poser problème car:
    en (1), on appelle explicitement le constructeur par copie: l'adresse représentée par c::tab est identique à celle représentée par c2::tab

    En (2), c2 est automatiquement détruit, car on sort de la portée dans laquelle la variable est déclarée. Le destructeur est appelé et delete[] est invoqué pour c2::tab.

    Or, c::tab représentait la même adresse mémoire que c2::tab, et l'espace mémoire qui se trouvait en c2::tab a été explicitement libéré. A partir d'ici, c::tab pointe donc vers une adresse invalide.

    Toute tentative d'accès à c::tab entre (2) et (3) occasionnera donc une tentative d'accès à une adresse invalide, ce qui provoquera un comportement indéfini (undefined behaviour).

    Si on arrive en (3), le destructeur de c sera automatiquement appelé, et il essayera d'invoquer delete sur c::tab.

    Et comme l'adresse représentée par c::tab est identique à celle qui était représentée par c2::tab et que l'espace mémoire représenté par c2::tab a été libéré en (2), on essaye d'accéder à une adresse mémoire invalide, ce qui se traduit par un comportement indéfini qui, dans le cas présent, correspondra à un erreur de segmentation.

    C'est le phénomène que l'on appelle "double tentative de libération de la mémoire".

    Le fait d'avoir demandé au destructeur de MaClass de libérer la mémoire de tab nous oblige donc à une nouvelle amélioration de la classe, pour nous assurer que chaque instance disposera d'un espace mémoire qui lui est propre, y compris lors d'une copie:
    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
    class MaClass{
        public:
            MaClass(int nombre):nombre(nombre), tab(new int [nombre]){}
            /* le constructeur par copie */
            MaClass(MaClass const & rhs):nombre(rhs.nombre),tab(new int[nombre]){
                /* pour s'assurer que les valeurs soient identiques */
                memcpy(tab, rhs.tab, sizeof(int)+nombre);
                /* OU OU OU (sans doute plus lent, mais plus correct si
                 * on ne travaille pas avec des types POD )
                 */
               for(int i = 0;i<nombre; ++i){
                   tab[i]=rhs.tab[i];
               }
            }
            ~MaClass(){delete [] tab;}
        private:
            int nombre; 
            int  * tab;
    };
    Le code précédant fonctionnera alors correctement:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    int main(){
        MaClass c(10);  // très bien, c'est le constructeur qui est appelé
        {
            MaClass c2(c); // appel explicite au constructeur par copie
        } // c2 est détruite et son destructeur  est appelé.
          // c2::tab est libéré: pas de problème, on ne touche plus à c1::tab
        return 0;
    } // c est détruit et son destructeur est appelé
    Mais il reste un dernier problème à résoudre car on risque de vouloir écrire un code proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    int main(){
        MaClass c(10);
        MaClass c2(2);
        /* différentes manipulations de c et de c2 */
        c2= c; 
        /*...*/
    }
    Mais que se passera-t-il à ce moment là, selon toi

    Hé bien, le problème est double:

    D'abord, l'affectation se fera sur la valeur du pointeur, ce qui fait que c2 et c se partageront de nouveau le même espace mémoire pour leur membre tab (exactement comme ce qui se passait avant l'implémentation personnelle du constructeur par copie), mais, surtout... la mémoire qui était allouée pour c2::tab ne sera pas libérée. Encore une fois, le compilateur manipule les pointeurs exactement comme des valeurs numériques entières.

    Et comme, après cette affectation, nous ne disposerons d'aucun moyen de récupérer l'adresse de l'espace mémoire alloué à l'origine à c2::tab, nous ne pourrons plus jamais libérer correctement cet espace mémoire.

    Bref, nous aurons une fuite mémoire qui, à terme, pourra mettre l'intégrité du système tout entier en péril.

    Comprend bien que la question n'est pas de savoir si cela arrivera un jour, mais plutôt de savoir quand cela arrivera, car ca arrivera forcément si on laisse l'application tourner pendant assez longtemps

    Nous sommes donc obligés de fournir une implémentation de l'opérateur d'affectation plus adéquate.

    Nous devons donc apporter une dernière amélioration à notre sous une forme (naïve pour l'instant, dont je ne parle que pour être complet) 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
    15
    16
    17
    18
    19
    20
    21
    22
    23
    class MaClass{
        public:
            MaClass(int nombre):nombre(nombre), tab(new int [nombre]){}
            /* le constructeur par copie */
            MaClass(MaClass const & rhs):nombre(rhs.nombre),tab(new int[nombre]){
                memcpy(tab, rhs.tab, sizeof(int)+nombre);
               }
            }
            /* l'opérateur d'affectation */
            MaClass & operator=(MaClass const & rhs){
               /* !!! l'ordre d'exécution est TRES important !!! */
                int * tempTab= new int[rhs.nombre];
                memcpy(tempTab, rhs.tab, sizeof(int)+nombre);
                delete [] tab;
                /* sauf pour ces deux dernières instructions */
                tab = tempTab;
                nombre = rhs.nombre;
           }
            ~MaClass(){delete [] tab;}
        private:
            int nombre; 
            int  * tab;
    };
    Notes que je t'ai fait remarquer que la première partie du problème est exactement la même que celui qui nous a incité à définir un comportement personnalisé pour le constructeur par copie.

    Notes aussi que nous avons la certitude, grâce au destructeur, que la mémoire allouée à une instance de notre classe sera correctement libérée lorsque l'instance sera détruite.

    Notes enfin que, si l'on analyse correctement le code de l'opérateur d'affectation, on retrouve exactement les instructions importantes que l'on a écrites aussi bien dans le destructeur que dans le constructeur par copie.

    Si donc on effectuait une copie temporaire de l'objet à affecter, puis qu'on s'arrangeait pour intervertir toutes les valeurs de l'objet courent et de cette copie, notre objet courent serait bel et bien une copie de l'objet à affecter et nous aurions la certitude que la mémoire qui était allouée à l'origine à notre objet courent serait correctement libérée lorsque la copie (qui contiendrait à cet instant les donnée d'origine de l'objet courent) serait détruite.

    Cela aurait, de plus, l'énorme avantage de respecter plusieurs principes qu'il faut tenter de respecter autant que possible, à savoir le principe de la responsabilité unique (chaque classe, chaque fonction ne devrait faire qu'une chose mais devrait le faire correctement) et le principe d'Xtrem Programming DRY (Don't Repeat Yourself: ne te répètes jamais)

    Nous pourrions donc apporter une dernière amélioration à notre classe sous une forme proche de

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    class MaClass{
        public:
            MaClass(int nombre):nombre(nombre), tab(new int [nombre]){}
            /* le constructeur par copie */
            MaClass(MaClass const & rhs):nombre(rhs.nombre),tab(new int[nombre]){
                memcpy(tab, rhs.tab, sizeof(int)+nombre);
               }
            }
            /* l'opérateur d'affectation (1) */
            MaClass & operator=(MaClass const & rhs){
                /* copie explicite de l'objet à affecter */
                MaClass temp(rhs);
                /* intervertissons le contenu de la copie et de l'objet courent */
                swap(temp);
                return *this;
           } // la copie est détruite ici
            ~MaClass(){delete [] tab;}
            /* permet d'intervertir le contenu de deux instances de la classe */
            void swap(MaClass & other){
                /* le plus facile: utiliser std::swap (nécessite l'inclusion du fichier d'en-tête <algorithm>  */
               std::swap(nombre, other.nombre);
               std::swap(tab, other.tab);
               /* OU OU OU une implémentation naïve*/
               int nbreTemp = nombre;
               nombre = other.nombre;
               other.nombre = nbreTemp;
               int * tabTemp = tab;
               tab= other.tab;
               other.tab= tabTemp;
               /* OU OU OU  sans passer par une variable temporaire */
               nombre =  nombre ^ other.nombre;
               other.nombre = nombre ^ other.nombre;
               nombre = nombre ^other.nombre;
     
               tab=  tab^ other.tab;
               other.tab= tab^ other.tab;
               nombre = tab^other.tab;
            }
        private:
            int nombre; 
            int  * tab;
    };
    (1) J'ai pris l'option de passer l'objet par référence constante et de faire une copie explicite, mais il serait tout à fait possible de passer l'objet par valeur, ce qui provoquerait une copie implicite, sous la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    class MaClass{
        public:
            MaClass & operator=(MaClass temp){
                /* intervertissons le contenu de la copie et de l'objet courent */
                swap(temp);
                return *this;
           } // la copie est détruite ici
         /* le reste, comme précédemment */
    };
    Dans certains cas, cela peut avoir un intérêt, mais c'est un autre débat
    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

  11. #11
    Membre Expert
    Profil pro
    Inscrit en
    Mars 2007
    Messages
    1 415
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Mars 2007
    Messages : 1 415
    Par défaut
    Koala tu nous as habitué a des interventions longues mais celle-là est costaud. Tu devrais mesurer ton temps de rédaction de post moyen

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    Citation Envoyé par jblecanard Voir le message
    Koala tu nous as habitué a des interventions longues mais celle-là est costaud.
    Bah, j'ai déjà fait pire: un jour, j'ai du couper dans mon intervention parce que j'avais dépassé la limite du nombre de caractères accepté pour une intervention
    Tu devrais mesurer ton temps de rédaction de post moyen
    Ben quoi, certaines interventions ne me prennent pas plus que deux ou trois minutes

    Mais la moyenne doit néanmoins tourner aux alentours d'une demi heur à trois quarts d'heure
    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

  13. #13
    Membre très actif Avatar de nikau6
    Homme Profil pro
    Inscrit en
    Février 2008
    Messages
    406
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Etats-Unis

    Informations forums :
    Inscription : Février 2008
    Messages : 406
    Par défaut
    Moi j'aime bien ses interventions, on y apprend/ré-apprend toujours un petit quelque chose...:-)

  14. #14
    Membre Expert
    Avatar de Joel F
    Homme Profil pro
    Chercheur en informatique
    Inscrit en
    Septembre 2002
    Messages
    918
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 45
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Chercheur en informatique
    Secteur : Service public

    Informations forums :
    Inscription : Septembre 2002
    Messages : 918
    Par défaut
    Citation Envoyé par koala01 Voir le message
    Encore un prof qui n'a pas compris que C++ n'a strictement rien à voir avec C, même s'il ne renie pas son héritage!

    Ca m'énerve au plus haut point, car cela vous oblige a aborder des notions (essentiellement celles des pointeurs et de la gestion dynamique de la mémoire) très tôt dans l'apprentissage, alors que ces notions ne sont réellement utiles qu'une fois que l'on aborde les problèmes d'héritage et de polymorphisme.
    l'histoire de ma vie

Discussions similaires

  1. Réponses: 1
    Dernier message: 26/12/2010, 21h20
  2. Réponses: 4
    Dernier message: 03/04/2010, 12h05
  3. Ce qui est dans une table mais pas dans l'autre !
    Par youyoule dans le forum Requêtes
    Réponses: 4
    Dernier message: 30/12/2007, 12h57
  4. insertion dans une table puis update dans une autre table
    Par uptoditime dans le forum VBA Access
    Réponses: 5
    Dernier message: 10/10/2007, 18h08
  5. Recherche de valeur dans une feuille et affichage dans une autre
    Par Zebulon777 dans le forum Macros et VBA Excel
    Réponses: 8
    Dernier message: 15/05/2007, 09h40

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