Partage
  • Partager sur Facebook
  • Partager sur Twitter

Classe std::string

Copie de celle-ci

Sujet résolu
Anonyme
    31 mai 2007 à 12:40:23

    Bonjour à tous :)

    J'avais, il y a quelques temps, voulu coder une classe Chaine identique (à quelques fonctions en moins près :-° ) à la classe std::string.
    Je viens de reprendre son codage, et je me suis rappelé qu'un certain membre avait soulevé certains problèmes dans le code.
    C'est pourquoi je soumets celui-ci à votre sagacité, pour que vous me disiez ce qui ne va pas, et ce que je dois faire pour obtenir un code vraiment propre.
    Voilà le code:

    libchaine.h
    ////////////////////////////////////////////////
    //                                            //
    //      Créé par Cyprien_ le 08/03/2007       //
    //           Mis à jour le 30/05/2007         //
    //                                            //
    //--------------------------------------------//
    //                                            //
    //            ***  libchaine.h  ***           //
    //                                            //
    //        Contient les prototypes des         //
    //  fonctions de la bibliothèque libchaine.a  //
    //                                            //
    ////////////////////////////////////////////////

    #ifndef DEF_LIBCHAINE
    #define DEF_LIBCHAINE

    #include <iostream>
    #include <vector>

    class Chaine
    {
        public:

            Chaine();
            Chaine(const Chaine& b);
            Chaine(const char* b);
            Chaine(const char& b);

            ~Chaine();

            Chaine& operator=(const Chaine& b);
            Chaine operator+(const Chaine& b) const;
            Chaine& operator+=(const Chaine& b);
            Chaine operator+(const char* b) const;
            Chaine& operator+=(const char* b);
            Chaine operator+(const char& b) const;
            Chaine& operator+=(const char& b);
            Chaine operator*(const int& n) const;
            Chaine operator*=(const int& n);
            Chaine& operator=(const char* b);
            Chaine& operator=(const char& b);
            char& operator[](const int& i) const;
            bool operator==(const Chaine& b) const;
            bool operator!=(const Chaine& b) const;
            bool operator==(const char& b) const;
            bool operator!=(const char& b) const;
            friend std::ostream& operator<<(std::ostream&, const Chaine&);
            friend std::istream& operator>>(std::istream&, Chaine&);
            void aff() const;
            int taille() const;
            int size() const;
            int length() const;
            Chaine extract(const int& d = 0, const int& n = -1) const;
            const char* c_str() const;
            int find(char c) const;
            int find(const char* b)const;
            int find(const Chaine& b) const;
            int find_first_of(char c) const;
            int find_first_of(const char* b) const;
            int find_first_of(const Chaine& b) const;
            bool empty() const;
            void clear();

        private:

            void copie(const Chaine& b);
            int m_taille;
            char* m_buffer;
            mutable std::vector<char*> m_tabs;

    };

    int size(const char* b);
    Chaine operator*(const int& n, const Chaine& b);
    std::ostream& operator<<(std::ostream& out, const Chaine& b);
    std::istream& operator>>(std::istream& in, Chaine& b);
    Chaine operator+(const char* a, const Chaine& b);
    Chaine operator+(const char& a, const Chaine& b);
    bool operator==(const char* a, const Chaine& b);
    bool operator!=(const char* a, const Chaine& b);


    #endif


    fonctions.cpp
    ////////////////////////////////////////////////
    //                                            //
    //                                            //
    //      Créé par Cyprien_ le 08/03/2007       //
    //           Mis à jour le 30/05/2007         //
    //                                            //
    //--------------------------------------------//
    //                                            //
    //           ***  fonctions.cpp  ***          //
    //                                            //
    //   Contient les fonctions qui ne sont pas   //
    //      des méthodes de la classe Chaine      //
    //                                            //
    ////////////////////////////////////////////////

    #include "libchaine.h"

    int size(const char* b) /*Retourne la taille d'une chaîne de caractères*/
    {
        int taille;
        for(taille=0 ; b[taille] != '\0' ; taille++);
        return taille;
    }

    /*Opérateurs surchargés*/

    Chaine operator*(const int& n, const Chaine& b) /*Voir Chaine.cpp*/
    {
        return b*n;
    }

    std::ostream& operator<<(std::ostream& out, const Chaine& b) /*Sortie d'une Chaine dans un flux*/
    {
        out << b.m_buffer;
        return out;
    }

    std::istream& operator>>(std::istream& in, Chaine& b) /*Entrée d'une Chaine*/
    {
        in >> b.m_buffer;
        b.m_taille = size(b.m_buffer);
        return in;
    }


    Chaine operator+(const char* a, const Chaine& b) /*Voir Chaine.cpp*/
    {
        Chaine chaine(a);
        return chaine + b;
    }

    Chaine operator+(const char& a, const Chaine& b) /*Voir Chaine.cpp*/
    {
        Chaine chaine(a);
        return chaine + b;
    }

    bool operator==(const char* a, const Chaine& b) /*Voir Chaine.cpp*/
    {
        return (b==a);
    }

    bool operator!=(const char* a, const Chaine& b) /*Voir Chaine.cpp*/
    {
        return (b!=a);
    }


    Chaine.cpp
    ////////////////////////////////////////////////
    //                                            //
    //      Créé par Cyprien_ le 08/03/2007       //
    //           Mis à jour le 30/05/2007         //
    //                                            //
    //--------------------------------------------//
    //                                            //
    //             ***  Chaine.cpp  ***           //
    //                                            //
    //  Contient les méthodes de la classe Chaine //
    //                                            //
    ////////////////////////////////////////////////

    #include "libchaine.h"


    /*Constructeurs*/

    Chaine::Chaine() /*Constructeur par défaut, initalise le buffer à une liste vide*/
    {
        m_buffer = new char[1];
        m_buffer[0] = '\0';
        m_taille = 0;
    }

    Chaine::Chaine(const Chaine& b) /*Constructeur initialisant la Chaine à partir d'une autre Chaine*/
    {
        m_buffer = new char[1];
        copie(b);
    }

    Chaine::Chaine(const char* b) /*Constructeur initialisant le buffer à un tableau de char*/
    {
        m_buffer = new char[1];
        m_buffer = '\0';
        m_taille = 0;
        *this = b;
    }

    Chaine::Chaine(const char& b) /*Constructeur initialisant le buffer à un unique caractère*/
    {
        m_buffer = new char[1];
        m_buffer = '\0';
        m_taille = 0;
        *this = b;
    }

    /*Destructeur*/

    Chaine::~Chaine()
    {
        for(std::vector<char*>::iterator i = m_tabs.begin() ; i != m_tabs.end() ; i++)
            delete[] (*i);

        delete[] m_buffer;
    }

    /*Méthodes privées*/

    void Chaine::copie(const Chaine& b) /*Copie d'une Chaine*/
    {
        delete m_buffer;
        m_buffer = new char[b.m_taille+1];
        m_taille = b.m_taille;

        for(int i=0;i<m_taille+1;i++)
        {
            m_buffer[i] = b.m_buffer[i];
        }

        m_buffer[m_taille] = '\0';
    }


    /*Méthodes publiques*/

    void Chaine::aff() const /*Affichage de la Chaine*/
    {
        std::cout << m_buffer << std::endl;
    }


    /*Opérateurs surchargés*/

    Chaine Chaine::operator+(const Chaine& b) const /*Addition de 2 Chaine*/
    {
        Chaine newChaine;
        int taille = m_taille + b.m_taille;

        delete[] newChaine.m_buffer;
        newChaine.m_buffer = new char[taille+1];
        newChaine.m_taille = taille;

        for(int i=0;i<m_taille;i++)
            newChaine.m_buffer[i] = m_buffer[i];

        for(int i=m_taille;i<newChaine.m_taille;i++)
            newChaine.m_buffer[i] = b.m_buffer[i-m_taille];

        newChaine.m_buffer[newChaine.m_taille] = '\0';

        return newChaine;

    }

    Chaine& Chaine::operator+=(const Chaine& b) /*Idem avec affectation*/
    {
        *this = *this + b;
        return *this;
    }

    Chaine Chaine::operator+(const char* b) const /*Addition de la Chaine et d'un tableau de char*/
    {
        Chaine chaine(b);
        return *this+chaine;
    }

    Chaine& Chaine::operator+=(const char* b) /*Idem avec affectation*/
    {
        *this = *this + b;
        return *this;
    }

    Chaine Chaine::operator+(const char& b) const /*Addition de la Chaine à un unique caractère*/
    {
        Chaine chaine(b);
        return *this+chaine;
    }

    Chaine& Chaine::operator+=(const char& b) /*Idem avec affectation*/
    {
        *this = *this + b;
        return *this;
    }

    Chaine Chaine::operator*(const int& n) const /*Répétition de la Chaine n fois*/
    {
        Chaine newChaine;
        int taille = m_taille*n;

        delete[] newChaine.m_buffer;
        newChaine.m_buffer = new char[taille+1];
        newChaine.m_taille = taille;

        for(int j=0;j<n;j++)
        {
            for(int i=0;i<m_taille;i++)
                newChaine.m_buffer[i+j*m_taille] = m_buffer[i];
        }

        newChaine.m_buffer[newChaine.m_taille] = '\0';

        return newChaine;
    }

    Chaine Chaine::operator*=(const int& n) /*Idem avec affectation*/
    {
        *this = *this * n;
        return *this;
    }

    Chaine& Chaine::operator=(const Chaine& b) /*Affectation de la Chaine à une autre Chaine*/
    {
        copie(b);
        return *this;
    }

    Chaine& Chaine::operator=(const char* b) /*Affectation du buffer à un tableau de char*/
    {
        int taille = ::size(b);

        delete[] m_buffer;
        m_buffer = new char[taille+1];

        for(int i=0;i<taille;i++)
        {
            m_buffer[i] = b[i];
        }

        m_buffer[taille] = '\0';
        m_taille = taille;

        return *this;
    }

    Chaine& Chaine::operator=(const char& b) /*Affectation du buffer à un unique caractère*/
    {
        m_taille = 1;
        delete[] m_buffer;
        m_buffer = new char[2];

        m_buffer[0] = b;
        m_buffer[1] = '\0';

        return *this;
    }

    char& Chaine::operator[](const int& i) const /*Sortie du i-ème caractère char du buffer*/
    {
        if(i<m_taille && i>=0)
            return m_buffer[i];
        else
            return m_buffer[m_taille];
    }

    bool Chaine::operator==(const Chaine& b) const /*Comparaison de 2 Chaine*/
    {
        int i = 0;

        if(m_taille != b.m_taille)
            return false;

        for( i=0; i<m_taille ; i++ )
        {
            if(m_buffer[i] != b[i])
                return false;
        }

        return true;
    }

    bool Chaine::operator!=(const Chaine& b) const /*Comparaison de 2 Chaine*/
    {
        return !(*this==b);
    }

    bool Chaine::operator==(const char& b) const /*Comparaison avec un tableau de char*/
    {
        Chaine newChaine = b;
        return (*this==b);
    }

    bool Chaine::operator!=(const char& b) const /*Comparaison avec un tableau de char*/
    {
        return !(*this==b);
    }

    int Chaine::taille() const /*Retourne la taille de la Chaine*/
    {
        return m_taille;
    }

    int Chaine::size() const /*Idem*/
    {
        return m_taille;
    }

    int Chaine::length() const /*Idem*/
    {
        return m_taille;
    }

    Chaine Chaine::extract(const int& d, const int& n) const /*Renvoie n caractères de la Chaine à partir du d-ième caractère*/
    {
        Chaine newChaine;

        if(d >= 0 && d < m_taille)
        {
            if(n > 0)
            {
                int i = d;
                int j = n;
                while( i < m_taille && j > 0)
                {
                    newChaine += m_buffer[i];
                    i++;
                    j--;
                }
            }
            else if(n == -1)
            {
                for(int i = d ; i < m_taille ; i++)
                    newChaine += m_buffer[i];
            }
            else if(n != 0)
                newChaine = *this;
        }

        return newChaine;
    }

    const char* Chaine::c_str() const /*Renvoie un pointeur sur un tableau de char*/
    {
        char* newChar = new char[m_taille+1];

        for(int i=0;i<=m_taille;i++)
            newChar[i] = m_buffer[i];

        m_tabs.push_back(newChar);

        return newChar;
    }

    int Chaine::find(char c) const /*Renvoie la premiere position d'une occurence de c*/
    {
        return find_first_of(c);
    }

    int Chaine::find(const char* b) const /*Renvoie la premiere position d'une occurence de b*/
    {
        int taille = ::size(b);
        int max = m_taille - taille + 1;
        int i, j=0;

        for(i=0;i<max && j<taille;i++)
        {
            for(j=0;j<taille && b[j] == m_buffer[i+j];j++);
        }

        if(j == taille)
            i--;

        return (i<max)?i:-1;
    }

    int Chaine::find(const Chaine& b) const /*Idem*/
    {
        return find(b.c_str());
    }

    int Chaine::find_first_of(char c) const /*Renvoie la premiere position d'une occurence de c*/
    {
        char b[2] = {0};
        b[0] = c;
        return find_first_of(b);
    }

    int Chaine::find_first_of(const char* b) const /*Renvoie la premiere position de l'un des caracteres de b*/
    {
        int taille = ::size(b);
        int i, j=taille;

        for(i=0;i<m_taille && j == taille;i++)
        {
            for(j=0;j<taille && b[j] != m_buffer[i];j++);
        }

        if(j < taille)
            i--;

        return (i<m_taille)?i:-1;
    }

    int Chaine::find_first_of(const Chaine& b) const /*Idem*/
    {
        return find_first_of(b.c_str());
    }

    bool Chaine::empty() const /*Renvoie 1 si vide, 0 si contient quelque chose*/
    {
        return (m_taille == 0);
    }

    void Chaine::clear() /*Vide la chaine*/
    {
        *this = Chaine();
    }


    Voilà, je remercie qui aura la patience et la bonté de m'aider à rendre propre mon code.

    Bonne journée :)
    • Partager sur Facebook
    • Partager sur Twitter
      1 juin 2007 à 23:43:05

      Bonsoir,

      OK, c'est parti, commentaires au fil de la lecture -- commentaires inspirés par la solution canonique.

      * Des opérateurs binaires non libres. Cela nuit à la symétrie.

      * Passages de paramètres. Pour les petits types (int, char, short, ...), prend des copies, pas des références constantes.

      * Choisis une langue. Français ou anglais.

      * Comparer une chaine à un caractère ne me parait pas avoir grand sens. Quand on définit des opérateurs, il faut que cela reste logique aux utilisateurs.

      * Ta fonction ::taille(char const*) est redondante avec strlen()

      * Extraction depuis flux.
      Ton extraction est fausse. Ton buffer n'est pas alloué à la bonne taille que tu écris dedans. Une façon juste de faire, serait des += (<=> append()) sur chaque caractère extrait un à un du flux.

      * Ta façon de définir Chaine::op+ et de s'en servir pour le reste me perturbe. Cela engendre des copies inutiles pour définir +=. La façon idiomatique, c'est le contraire.

      * De même il est 100 plus simple de définir l'opérateur d'affectation grâce au constructeur de copie que le contraire.

      * D'ailleurs ton opérateur d'affectation n'est pas exception-safe. Toujours allouer (dans un temporaire) avant de libérer (avec delete[] et non delete).

      * Maintenant que tu as des opérateurs d'insertion dans les flux, dégage la fonction aff(). Ce genre de truc n'est valable que dans des exos quand on (on==prof/tuteur) ne veut pas définir les opérateurs de manipulation de flux, ni contredire le sacro-saint principe d'encapsulation. Cette approche (de fonction membre d'affichage/lecture) est assez contraire au principe de séparation des responsabilités. A quoi cela peut-il bien servir quand l'IHM passe par (n)curses ou un GUI ?

      * Les deux signatures types de l'opérateur [] sont:
      char const& operator[](size_type) const;
      char      & operator[](size_type) ;

      Pour les accès hors-bornes. Pête une erreur. Faire semblant que tout va bien joue beaucoup plus de mauvais tours bien vicieux qu'un bon vieux coredump des familles.

      * Je ne suis toujous pas convaincu par les m_tabs. C'est le coup à faire exploser la mémoire ce truc. Et puis ... pourquoi mémoriser les c_str() et pas les []. Car au fond, &ch[0] et ch.c_str() renvoient la même chose.


      Bon voilà pour une première passe "rapide". Il y aurait bien d'autres pinaillages, mais souvent ils sont induits par des choix incorrects (-> les dépendances entre +=, +, la copie-construction et l'affectation)

      Bon courage.
      • Partager sur Facebook
      • Partager sur Twitter
      C++: Blog|FAQ C++ dvpz|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS| Bons livres sur le C++| PS: Je ne réponds pas aux questions techniques par MP.
      Anonyme
        2 juin 2007 à 7:52:35

        Citation : lmghs


        * Des opérateurs binaires non libres. Cela nuit à la symétrie.


        Ok, je règle ça dès que j'ai le temps.

        Citation : lmghs

        * Passages de paramètres. Pour les petits types (int, char, short, ...), prend des copies, pas des références constantes.

        Idem

        Citation : lmghs

        * Choisis une langue. Français ou anglais.

        Ok, je vais choisir l'anglais je crois.

        Citation : lmghs

        * Comparer une chaine à un caractère ne me parait pas avoir grand sens. Quand on définit des opérateurs, il faut que cela reste logique aux utilisateurs.

        Tant que les 2 sont autorisés, c'est ptêt pas trop gênant ? Ou à ce point ?
        EDIT: oups, j'avais pas vu qu'en fonction membre, je n'ai défini que la comparaison avec un char...

        Citation : lmghs

        * Ta fonction ::taille(char const*) est redondante avec strlen()

        Je sais, mais je ne voulais pas du tout utiliser la bibliothèque cstring, juste pour ça en plus

        Citation : lmghs

        * Extraction depuis flux.
        Ton extraction est fausse. Ton buffer n'est pas alloué à la bonne taille que tu écris dedans. Une façon juste de faire, serait des += (<=> append()) sur chaque caractère extrait un à un du flux.

        Euh...pas compris :euh:

        Citation : lmghs

        * Ta façon de définir Chaine::op+ et de s'en servir pour le reste me perturbe. Cela engendre des copies inutiles pour définir +=. La façon idiomatique, c'est le contraire.

        La façon idiomatique, c'est le fait de définir l'opérateur x= dans la classe, puis les 2 opérateurs x en-dehors ?

        Citation : lmghs

        * De même il est 100 plus simple de définir l'opérateur d'affectation grâce au constructeur de copie que le contraire.

        Là, je ne vois pas trop comment faire :euh: On a la droit d'utiliser le constructeur de copie après création de l'objet ?

        Citation : lmghs

        * D'ailleurs ton opérateur d'affectation n'est pas exception-safe. Toujours allouer (dans un temporaire) avant de libérer (avec delete[] et non delete).

        Pour quelle raison faut-il allouer avant de libérer ? Oups pour le delete :honte: .

        Citation : lmghs

        * Maintenant que tu as des opérateurs d'insertion dans les flux, dégage la fonction aff(). Ce genre de truc n'est valable que dans des exos quand on (on==prof/tuteur) ne veut pas définir les opérateurs de manipulation de flux, ni contredire le sacro-saint principe d'encapsulation. Cette approche (de fonction membre d'affichage/lecture) est assez contraire au principe de séparation des responsabilités. A quoi cela peut-il bien servir quand l'IHM passe par (n)curses ou un GUI ?

        Ok

        Citation : lmghs

        * Les deux signatures types de l'opérateur [] sont:

        char const& operator[](size_type) const;
        char      & operator[](size_type) ;


        Pour les accès hors-bornes. Pête une erreur. Faire semblant que tout va bien joue beaucoup plus de mauvais tours bien vicieux qu'un bon vieux coredump des familles.

        Cela veut-il dire qu'il faut implémenter les 2 ? Ou juste une seule ? Une seule je suppose, vu qu'elles ont la même signatures...
        Et comment je fais pour "péter une erreur" ?
        EDIT: ah non, j'ai rien dit, ça marche en mettant les 2, et au contraire ça fait une erreur si je n'en mets qu'un.

        Citation : lmghs

        * Je ne suis toujous pas convaincu par les m_tabs. C'est le coup à faire exploser la mémoire ce truc. Et puis ... pourquoi mémoriser les c_str() et pas les []. Car au fond, &ch[0] et ch.c_str() renvoient la même chose.

        Mea culpa, c'était il y a longtemps et je m'étais décidé à les enlever... En plus, ça me permettra de me passser des vecteurs ^^ .


        En tout cas, MERCI pour ton courage et ta patience avec moi.
        • Partager sur Facebook
        • Partager sur Twitter
          2 juin 2007 à 11:59:35

          Citation : Cyprien_

          Citation : lmghs

          * Comparer une chaine à un caractère ne me parait pas avoir grand sens. Quand on définit des opérateurs, il faut que cela reste logique aux utilisateurs.

          Tant que les 2 sont autorisés, c'est ptêt pas trop gênant ? Ou à ce point ?


          Génant ? non. Juste pas forcément naturel. Quel est le sens de
          chaine == 'e'
          ?
          Alors que l'on peut aussi tout simplement écrire:
          chaine == "e"

          Qui sémantiquement est beaucoup plus juste. C'est comme comparer un panier de fruits avec une orange. Parler d'égalité est louche.

          Citation : Cyprien_

          Citation : lmghs

          * Extraction depuis flux.
          Ton extraction est fausse. Ton buffer n'est pas alloué à la bonne taille que tu écris dedans. Une façon juste de faire, serait des += (<=> append()) sur chaque caractère extrait un à un du flux.

          Euh...pas compris :euh:


          Le plus important, est-ce que tu vois le problème ?
          En écrivant
          Chaine c;
          std::cin >> c;
          c'est comme si tu écrivais
          char b[1];
          std::cin >> b; // truc syntaxiquement correct, mais qui devrait être interdit!

          Il est obligé que tu aies un débordement de mémoire. Même si tu penses allouer 100 fois trop de mémoire, rien ne garanti qu'il ne puisse pas y avoir de débordement.
          Une solution sub-optimale pour régler cela serait de changer ton opérateur d'extraction en
          std::istream  operator>>(std::istream & is, Chaine & res)
          {   Chaine ch; char c;
              std::ws(is) ; // purge les espaces
              while (! isspace(is . peek()) && is >> c ) // ou un truc comme ça, pas sûr que cela soit juste.
                  ch += c;
              res . swap(ch); // ws nécessaire ?
              return is;
          }
          // isspace n'est pas 100% correct, cf
          // http://dinkumware.com/manuals/?manual=compleat&page=istream.html#ws
          // pour voir le genre d'écriture à utiliser

          // pas sûr de me comporter correctement relativement aux fins de flux.

          // cf les articles d'Angelika Langer, ou son bouquin co-écrit avec
          // Klaus Kreft(?), ou tout simplement la doc de Roguewave que je les
          // soupçonne d'avoir écrite.

          (distinguer et taille allouée (size) et taille occupée (capacity) vaut clairement le coup pour ce genre de choses)

          Citation : Cyprien_

          Citation : lmghs

          * Ta façon de définir Chaine::op+ et de s'en servir pour le reste me perturbe. Cela engendre des copies inutiles pour définir +=. La façon idiomatique, c'est le contraire.

          La façon idiomatique, c'est le fait de définir l'opérateur x= dans la classe, puis les 2 opérateurs x en-dehors ?


          Un exemple vaut mieux qu'un long discours :
          http://www.siteduzero.com/forum-83-49667-1346053.html#r1346053 § c-
          Il faut savoir que si on (la communeauté en général) a fini par converger vers cette approche, c'est pour :
          - n'écrire qu'une seule fois le code de l'addition (=> maintenabilité accrue, taille des exécutables réduite, ...)
          - minimiser les copies inutiles
          - avoir des opérateurs symétriques lorsque des conversions implicites peuvent être réalisées.

          Les autres techniques qui avaient été expérimentées sont systématiquement moins efficaces sur un point ou un autre, quand ce n'est pas plusieurs.

          Citation : Cyprien_

          Citation : lmghs

          * De même il est 100 plus simple de définir l'opérateur d'affectation grâce au constructeur de copie que le contraire.

          Là, je ne vois pas trop comment faire :euh: On a la droit d'utiliser le constructeur de copie après création de l'objet ?

          Citation : lmghs

          * D'ailleurs ton opérateur d'affectation n'est pas exception-safe. Toujours allouer (dans un temporaire) avant de libérer.

          Pour quelle raison faut-il allouer avant de libérer ?


          Parce que le new peut lever une exception. Auquel cas tu te retrouves avec une chaine inutilisable qui a déjà libéré son pointeur (=> qui plantera dans son destructeur). Toujours préférer que les fonctions qui avortent ne modifient pas l'état de l'objet qu'elles aurait dû modifier (on parle d'"opérations atomiques" : elles passent et modifient, elles cassent et ne touchent pas). C'est pour cela que je suis passé par une chaine intermédiaire pour l'opérateur d'extraction (>>).

          Bref. Plus d'info dans la FAQ de développez. La solution, c'est le swap. La solution rédigée par Laurent dans la FAQ s'éloigne un chouilla de la solution canonique qui est (attention, c'est super simple et pourtant super correct)
          Chaine & Chaine::operator=(Chaine const& other) {
              Chaine tmp(other);
              this->swap(tmp);
              return *this;
          }



          Citation : Cyprien_

          Citation : lmghs

          * Les deux signatures types de l'opérateur [] sont:

          char const& operator[](size_type) const;
          char      & operator[](size_type) ;


          Cela veut-il dire qu'il faut implémenter les 2 ? Ou juste une seule ? Une seule je suppose, vu qu'elles ont la même signatures...


          Il faut les deux. Les signatures sont complètement différentes
          - la première s'applique aux chaines non modifiables (p.ex. celles reçues par référence constante)
          - la seconde s'applique aux autres
          - ta version était sémantiquement incorrecte vu que tu renvoyais une référence (non const) vers un truc que tu n'es pas sensé pouvoir modifier (la chaine est vue "const" => non altérable)


          Citation : lmghs

          En tout cas, MERCI pour ton courage et ta patience avec moi.


          Avec plaisir.
          • Partager sur Facebook
          • Partager sur Twitter
          C++: Blog|FAQ C++ dvpz|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS| Bons livres sur le C++| PS: Je ne réponds pas aux questions techniques par MP.
          Anonyme
            2 juin 2007 à 13:03:23

            Pour l'égalité avec un caractère, c'est réglé.
            Par contre, je viens de me rendre compte que même sans mettre dans le code la méthode comparant un Chaine avec un char*, cette comparaison marche. Est-il alors toujours obligatoire de la mettre ?
            Même question pour les opérateurs + et +=.

            En fait, après réflexion (désolé, j'écris aussi au fil de ma pensée ^^ ), je crois avoir compris: toutes les méthodes avec char* et char que j'ai implémentées sont totalement inutiles, car j'ai défini des constructeurs permettant la conversion de char* / char en Chaine, donc celle-ci est automatiquement faite. C'est juste ?
            Si ça l'est, ça veut dire que je peux enlever la moitié de mes méthodes non ?
            Je préfère te demander avant de tout effacer...

            Ensuite, pour l'opérateur >>, maintenant j'ai compris d'où venait le risque. Par contre, je n'ai pas compris tout ton code, mais pour l'instant, passons, ça viendra plus tard :-°
            En revanche, je vois que tu utilises souvent la méthode swap... c'est une méthode que je dois implémenter, non ?

            Bon, tu me parles aussi d'exceptions, je vois vaguement ce que c'est, mais je n'en sais pas encore trop dessus, donc là il faut m'excuser :p

            Enfin, pour l'opérateur [], ça a l'air d'être réglé, mais je ne sais hélas toujours pas comment faire pour "péter une erreur" (exception ?).


            Je ne le dirai jamais assez : MERCI !
            • Partager sur Facebook
            • Partager sur Twitter
              2 juin 2007 à 19:31:02

              Si tu peux utiliser un "char const*" sans avoir les opérateurs qui le supportent explicitement, c'est parce que ta classe dispose d'un constructeur non déclaré "explicit" qui prend un seul argument qui est un "char const*".

              Garder les opérateurs spécialisés n'est pas nécessaire si tous tes opérateurs sont définis comme des fonctions libres (si tu veux une symétrie dans les conversions). Ils ont cependant un avantage (c'est pour cela que je ne les avais pas "critiquées") : utilisés correctement, ils sont un moyen d'optimiser certaines écritures. En effet, sans ces opérateurs spécialisés, ''ch + "toto"'' convertirait d'abord la chaine 0-terminée "toto" en objet de type "Chaine" ce qui impliquerait une copie.

              Concernant le constructeur qui accepte un caractère, il est usuel de faire en sorte qu'il ne puisse pas permettre des convertions implicites. Soit en le déclarant "explicit". Soit en lui donnant deux arguments : le caractère et un nombre d'occurences. (Voire un mélange des deux si le nombre dispose d'une valeur par défaut de 1)
              Cela évite les trucs bizarres comme "ch == 'c'".

              swap est une opération pratique à supporter pour définir des affectations exception-safe à moindre coût. Pourque cela en vaille la peine, il faut que la classe dispose de peu d'attributs légers pour avoir un tout peu cher à copier (ou échanger -> tous les conteneurs standard proposent une fonction membre swap)


              Pour l'erreur à lever. Deux approches possibles (c'est toi qui voit).
              Soit tu considères qu'appeler l'opération avec un indice hors bornes est une erreur de programmation (toujours mieux que de ne rien faire, car là le comportement sera indéterminé). Alors tu peux utiliser les assertions. Compilée en mode debug, ta classe plantera violemment le programme à la ligne du problème. Ce qui permettra à un développeur d'insvestiguer le contexte d'exécution avec un debuggueur. (En mode release, aucun test n'étant effectué)

              Soit tu considères que c'est une erreur liée au contexte d'exécution (typique des fichiers qui ne peuvent être ouverts, qui sont corompus, des pairs non joignables, ...). Là tu lèves une exception qui devra être interceptée et traitée par le code client à l'endroit où cela est traitable.

              Une bonne partie de la SL a choisi une approche mixte. Les opérateurs [] claquent des assertions (car toujours mieux que rien -- un choix des sociétés/groupes qui fournissent des SL ; dans mes souvenirs, ce n'est pas imposé par le standard), les fonctions membre at() lèvent des exceptions.

              On en a déjà parler avec peut-être un peu plus de détails sur developpez (-> bibliographie, détails, ...). Mais avant de rentrer plus dans ces détails, je pense, qu'il vaudrait mieux que tu commences à te documenter sur les exceptions.

              EDIT:
              PS: comme tu le vois, bien que d'apparence anodine, c'est un sujet riche avec plein de choses à dire dessus. Dans le même genre, tu as les vecteurs et les matrices (pour ces dernières les améliorations sont un sujet d'études et de discussions sans bornes).
              • Partager sur Facebook
              • Partager sur Twitter
              C++: Blog|FAQ C++ dvpz|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS| Bons livres sur le C++| PS: Je ne réponds pas aux questions techniques par MP.
                2 juin 2007 à 20:45:36

                A propos de la comparaison chaine avec caractère, c'est sûr que sémantiquement parlant, c'est incorrect.

                Mais je ne trouve pas que ce soit si louche que ça, pour les utilisateurs. Il y a bien un point commun entre une chaîne de caractères qui n'en contient qu'un seul et un caractère en lui même : c'est le nombre de caractères considérés.

                En théorie des langages, s'il y a bien un abus de notation, c'est bien celui là : on identifie toujours le langage {a} (singleton d'un seul symbole) avec le symbole 'a' par commodité.

                Enfin, là c'est un choix.
                • Partager sur Facebook
                • Partager sur Twitter
                Anonyme
                  3 juin 2007 à 11:03:15

                  Pour le mot-clé explicit, je crois que je ne vais pas encore m'en préoccuper, vu que là, je ne me soucie pas trop de l'optimisation.
                  Je crois que je vais également laisser le constructeur prenant en paramètre un seul char, après tout, ce n'est qu'une possibilité en plus pour l'utilisateur, ça ne peut pas le déranger.

                  Pour swap, j'ai implémenté la méthode ainsi, est-ce correct ?
                  void Chaine::swap(Chaine& b) /*Echange 2 Chaines*/
                  {
                      char* buffer_b = b.m_buffer;
                      int taille_b = b.m_taille;

                      b.m_buffer = m_buffer;
                      m_buffer = buffer_b;

                      b.m_taille = m_taille;
                      m_taille = taille_b;
                  }


                  Pour l'erreur à générer, je vais effectivement tenter de me documenter un peu plus sur les exceptions, mais il faut dire que j'attendais le cours du sdz, il doit sûrement arriver dans 1 ou 2 chapitres :-°

                  HanLee, merci à toi d'avoir également eu le courage de me répondre, je vais effectivement suivre ton conseil, comme je l'ai dit, et garder cette compraison.


                  A présent, j'aurais d'autres question ^^ :

                  Pour l'opérateur +, j'ai donc essayé d'adopter ton approche lmghs, ce qui me donne ceci:

                  Chaine& Chaine::operator+=(const Chaine& b) /*Addition avec affectation*/
                  {
                      Chaine newChaine;
                      int taille = m_taille + b.m_taille;
                      char* temp = newChaine.m_buffer;

                      newChaine.m_buffer = new char[taille+1];
                      delete[] temp;

                      newChaine.m_taille = taille;

                      for(int i=0;i<m_taille;i++)
                          newChaine.m_buffer[i] = m_buffer[i];

                      for(int i=m_taille;i<newChaine.m_taille;i++)
                          newChaine.m_buffer[i] = b.m_buffer[i-m_taille];

                      newChaine.m_buffer[newChaine.m_taille] = '\0';

                      this->swap(newChaine);

                      return *this;
                  }

                  Chaine operator+(const Chaine& a, const Chaine& b) /*Voir Chaine.cpp*/
                  {
                      return Chaine(a) += b;
                  }


                  Par contre, je me demandais: je suppose que pour l'opérateur *, qui prend en paramètre un int et une Chaine, il n'est pas possible d'avoir aussi cette symétrie ? Dois-je garder ce que j'avais déjà fait ?
                  Ou ne garder que l'opérateur *= en fonction-membre et mettre les 2 opérateurs * en fonctions-libres ?

                  Pour les opérateurs == et !=, même question...

                  Enfin, j'ai également implémenté ce que tu m'as conseillé pour l'opérateur d'affectation, et ça marche à merveille !


                  Je me répète, je me répète, mais un grand MERCI à vous deux (c'est tellement rare que des personnes veuillent bien répondre à mes topics [en fait il n'y avait presque que lmghs avant :D ])
                  • Partager sur Facebook
                  • Partager sur Twitter
                    3 juin 2007 à 14:40:41

                    Pour le swap, c'est ça.
                    Tu peux même utiliser std::swap -- à ce sujet tu pourrais spécialiser std::swap pour ton type, histoire qu'il ne réalise pas de copie, mais qu'il utilise à la place ta fonction membre d'échange.

                    Pour l'opérateur d'ajout (+=). Passer par une chaine temporaire et enchainer par un swap est une bonne idée. Mais va plus loin. Ici, tu t'embêtes à refaire à la main ce que le destructeur de ta classe sait faire parce qu'il te manque un constructeur qui construit à une taille donnée, sans rien remplir.


                    A partir du moment où l'on part sur l'utlisation d'ajout, il devient rapidement intéressant de distinguer taille allouée et taille utilisée. Ce que font std::vector et std::string. Particulièrement utile pour lire depuis un flux, ...

                    Du coup += pourrait ressembler à ceci (je ne garantis pas le 0 bug):
                    Chaine & Chaine::operator+=(Chaine const& rhs)
                        const int taille_finale = this->taille() + rhs.taille();
                        if (taille_finale < capacite())
                            this->resize(taille_finale); // <-- il est là dedans le swap
                        std::copy(&rhs[0], &rhs[rhs.taille()], &m_buffer[this->taille()]);
                        m_buffer[taille()] = '\0';
                        return *this;
                    }



                    Pour la répétition "n * ch" ou "ch * n", il est tout à fait possible d'avoir la symétrie. Ce qui n'est pas le cas pour *=, vu que je ne vois pas le sens de "n *= ch". Le code des deux opérateurs *() est idetique, c'est juste l'ordre des paramètres qui diffère.
                    Par contre, (normalement) pas de convertion implicite possible ici (il y a des règles particulières quand les deux opérandes sont des types natifs).

                    AMHA, si tu as peu de réponses à tes topics, c'est parce que c'est assez pointu. Et que l'on a essentiellement des débutants ici. Même si j'en reconnais quelques uns que soupçonne de pouvoir répondre. Mais cela demande du temps. J'avoue qu'il m'arrive de retarder d'un ou deux jours mes réponses à tes posts ^^'.
                    • Partager sur Facebook
                    • Partager sur Twitter
                    C++: Blog|FAQ C++ dvpz|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS| Bons livres sur le C++| PS: Je ne réponds pas aux questions techniques par MP.
                    Anonyme
                      3 juin 2007 à 15:34:39

                      Pour le swap, je crois que je vais (pour le moment) garder le mien, avant d'en savoir un peu (beaucoup...) plus sur la SL, puisqu'il convient.

                      Par contre, pour la capacité, je savais bien qu'il faudrait un jour que je m'y lance (car ça permet aussi de réduire le nombre d'allocations, etc.), mais ça implique de tout repenser, donc va falloir que je voie si j'ai le temps (ptêt ce soir...).

                      Enfin, pour l'opérateur * , c'est bien ce que je pensais, mais quand je parlais de symétrie, je voulais dire, comme pour l'opérateur +, avoir un seul opérateur += en membre et un seul opérateur + en libre. Mais je me doutais bien que ce ne serait pas possible, donc je le garde comme ça.

                      Par contre, je ne comprends pas, dans ta fonction operator+=, pourquoi tu fais appel aux fonctions-membres taille() ? N'est-il pas plus rapide d'utiliser directement m_taille ?


                      Comme toujours, merci, et ne t'inquiète pas, je comprends parfaitement que tu diffères ta réponse de quelques temps, car au moins la réponse est de qualité :D
                      • Partager sur Facebook
                      • Partager sur Twitter
                        4 juin 2007 à 23:51:46

                        Il n'y a aucune différence significative entre m_taille et taille(). D'autant plus que si défine inlinée, taille() sera directement remplacée par l'attribut encapsulé.

                        Après, c'est en parti une question de style.
                        Tu noteras p.ex. que j'ai utilisé l'opérateur [] de l'opérande droit. Je n'ai pas référé à son attribut m_buffer. C'est ici un premier pas vers une solution générique. Car il y a moyen de faire des trucs très poussés avec un peu de méta-programmation. Mais ... on va garder ça au chaud pour dans un an au moins ;)
                        • Partager sur Facebook
                        • Partager sur Twitter
                        C++: Blog|FAQ C++ dvpz|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS| Bons livres sur le C++| PS: Je ne réponds pas aux questions techniques par MP.

                        Classe std::string

                        × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                        × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
                        • Editeur
                        • Markdown