IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Langage C++ Discussion :

utilisation d'un vector comme élément d'une class


Sujet :

Langage C++

  1. #1
    Nouveau membre du Club
    Homme Profil pro
    Inscrit en
    Mai 2009
    Messages
    23
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Mai 2009
    Messages : 23
    Points : 27
    Points
    27
    Par défaut utilisation d'un vector comme élément d'une class
    Bonjour,

    Voilà rapidement mon problème, j'ai créé une classe comprenant (entre autre) un vector d'entiers:
    classes.h:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    class toto
    {
        public:
        vector<int> get_tata();
     
        private : 
        vector<int> m_tata;
    }
    Comme je l'ai lu , pour accéder à cet vecteur m_tata et à ses éléments, il faut construire une méthode de type public qui elle seule peut manipuler le vector tata : get_tata. J'ai bien essayé de coder ça de la façon suivante:
    classes.cpp:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    vector<int> toto::get_tata()
    {
        return m_tata;
    }
    mais ça n'a pas l'air de marcher.
    J'ai regardé (un peu) les différentes FAQ et tutos mais pas de réponse quant à l'utilisation des vector dans une class.
    Le truc que je voudrais faire c'est créer un élément de type toto, remplir le vecteur tata et récupérer d'un autre côté les informations du vecteur tata.

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

    Informations forums :
    Inscription : Juillet 2008
    Messages : 1 580
    Points : 2 205
    Points
    2 205
    Par défaut
    Il faut renvoyer par référence et non par valeur. Mieux, par référence constante et ton accesseur devrait aussi être constant.

    Devenant quelque chose de ce goût là :


    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    const vector<int>& toto::get_tata() const
    {
        return m_tata;
    }
    "Hardcoded types are to generic code what magic constants are to regular code." --A. Alexandrescu

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Salut, et bienvenue sur le forum.

    J'aurais déjà tendance à dire qu'il est mauvais d'autoriser l'acces à l'ensemble de ton vecteur.

    En effet, le fait que tes entiers soient représentés sous la forme d'un tableau (d'un std::vector) fait typiquement partie de ce que l'on appelle les "détails d'implémentation"...

    Il n'est donc absolument pas interdit de penser qu'un jour tu décide de remplacer le std::vector par une std::list, un std::set ou par ... à peu près n'importe quel autre conteneur que tu pourrais juger adapté

    Le jour où tu décidera d'effectuer un tel changement, tu te retrouvera dans l'obligation de vérifier l'ensemble des fonctions qui manipulent cette classe pour t'assurer d'appliquer les changements.

    De plus, il est généralement conseillé de ne pas renvoyer une référence non constante sur un membre de classe, simplement, parce que ca ouvre la porte à des modifications de ce membre sans que l'objet dont il est issu n'aie la possibilité de les valider et que tu risque donc de te retrouver avec un objet dans un état non cohérent

    La solution à ces deux problèmes est de n'exposer que les fonctions qui seront absolument nécessaires à ta classe pour pouvoir travailler, ainsi qu'un typedef (à utiliser idéalement partout) des itérateurs de ton conteneur:

    Typiquement, les "services" que l'on peut envisager de demander à la classe concernant ce conteneur sont:
    • d'ajouter un élément en fin de conteneur
    • d'anouter un élément en début de conteneur
    • d'insérer un conteneur entre deux éléments
    • de supprimer un élément du conteneur
    • de vider le conteneur
    • d'interroger l'objet sur le nombre d'élément contenu dans le conteneur
    • d'interroger l'objet pour savoir si le conteneur est vide
    • d'obtenir un itérateur (constant) sur le premier élément
    • d'obtenir un itérateur (constant) sur la fin du conteneur
    (il va de soi qu'aucune possibilité n'est réellement obligatoire, et que c'est à toi de voir quelles "services" ta classe doit fournir )

    Si tu envisage une classe qui doit fournir tous les "services", elle prendrait 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
    class MaClass
    {
        public:
            MaClass(){}
            /* nous créons des alias de  type pour les itérateurs...
             * ces alias de type seront utilisés chaque fois que nous voudrons
             * accéder à un élément du conteneur et permettront de limiter les
             * changements de conteneurs à apporter à la classe même 
             * (bien que les fonctions qui utilisent ces itérateurs devront être 
             * recompilées)
             */
            typedef std::vector<int>::iterator  iterator;
            typedef std::vector<int>::const_iterator const_iterator;
            /* les fonctions qui permettent de rajouter un élément au conteneur */
            void push_back(int i){m_tata.push_back(i);}
            void push_front(int i){m_tata.push_front(i);}
            void insert(iterator it,int i){m_tata.insert(it, i);}
            /* nous pourrions prévoir l'insertion de groupes d'objets itou ;) */
            /* les fonctions permettant de supprimer des éléments */
            void erase(iterator it){m_tata.erase(it);}
            /* nous pourrions aussi prévoir la suppression d'un groupe d'objet */
            void clear() {m_tata.clear();}
            /* les fonctions permettant de récupérer les itérateurs 
             * dans leur version constante
             */
            const_iterator begin() const{return m_tata.begin();}
            const_iterator end() const{return m_tat.end();}
             /* et dans leurs versions non constante (il faut un iterateur non
              * constant pour l'insertion et la suppression)
              */
            iterator begin(){return m_tata.begin();}
            iterator end(){return m_tata.end();}
            /* et pour finir les fonctions  d'interrogations */
            bool empty() const{return m_tata.empty();}
            size_t size() const{return m_tata.size();}
        private:
            std::vector<int> m_tata;
    };
    De cette manière, tu arrive à ne pas du tout exposer ton vecteur à l'extérieur, et, pour autant que tu veilles à n'utiliser que MaClass::iterator ou MaClass::const_iterator dans les fonctions qui manipulent MaClass (et qui ont besoin d'accéder aux éléments de la collection), tu pourra décider n'importe quand d'utiliser un autre conteneur, "simplement" en modifiant les typdef et les fonctions membres de MaClass...

    Mieux, si, parce que cela peut arriver, tu décide de remplacer m_tata par un autre conteneur et que, dans une fonction, tu as utilisé (par mégarde, sans doute) std::vector<int>::iterator ou std::vector<int> const_iterator pour accéder aux différents éléments, tu aura une erreur à la compilation
    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

  4. #4
    Nouveau membre du Club
    Homme Profil pro
    Inscrit en
    Mai 2009
    Messages
    23
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Mai 2009
    Messages : 23
    Points : 27
    Points
    27
    Par défaut
    Merci pour vos réponses.

    Je vois à travers vos réponses les erreurs que j'ai faite ou les raccourcis que j'ai pris dans la rédaction de mon programme.
    Mais je suis toujours bloqué. Je voudrais construire un premier objet de ma classe toto que j'appelle toto1, en spécifiant la valeur de son membre m_tata. J'utilise le constructeur suivant (là aussi je ne sais pas trop ce que je dois passer en pointeur ou pas).
    classes.cpp
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    void toto::toto(vector <int> tata) : m_tata(tata){}
    Ensuite, je veux construire un deuxième objet toto2 qui dont le membre m_tata sera le même que m_tata de toto1 (je sais pas si c'est très clair du coup!).
    Un truc du genre

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    toto toto2(toto1.get_tata());
    Mais voila, j'ai beau essayer de passer un pointeur en argument ou l'objet lui-même, ça ne veut pas marcher. Ce qui fonctionnait très bien avec de simples entiers me parait plus compliqué avec des objets comme les vector.

  5. #5
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Points : 13 017
    Points
    13 017
    Par défaut
    Salut,
    Le passage par valeur fonctionne très bien avec les vecteurs :
    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
    #include <vector>
    #include <iostream>
     
    struct A
    {
       A(std::vector<int> v_):m_v(v_){}
       std::vector<int> get_v()const{return m_v;}
       private:
       std::vector<int> m_v;
    };
    int main()
    {
       std::vector<int> vect;
       vect.push_back(1);
       vect.push_back(2);
       vect.push_back(3);
       A a(vect);
       std::vector<int> a_vect = a.get_v();
       for(std::vector<int>::const_iterator it= a_vect.begin();it!=a_vect.end();++it){
          std::cout<<*it<<std::endl;
       }
       return 0;
    }
    Il est vrai que ce passage par valeur peut être couteux car il nécessite de passer par une variable copie de l'argument ou de la valeur en retour. C'est pour cela que tu trouveras souvent l'utilisation de références constantes :
    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
    #include <vector>
    #include <iostream>
    
    struct A
    {
       A(std::vector<int> const & v_):m_v(v_){}
       std::vector<int> const & get_v()const{return m_v;}
       private:
       std::vector<int> m_v;
    };
    int main()
    {
       std::vector<int> vect;
       vect.push_back(1);
       vect.push_back(2);
       vect.push_back(3);
       A a(vect);
       std::vector<int> const & a_vect = a.get_v();
       for(std::vector<int>::const_iterator it= a_vect.begin();it!=a_vect.end();++it){
          std::cout<<*it<<std::endl;
       }
       return 0;
    }
    Néanmoins, comme le souligne Koala, c'est en général une mauvais conception que d'exposer aussi fortement les membre privés de ta classe.

    [EDIT] : Pour en revenir à ton problème. Par défaut, le compilateur fournit à ta classe un constructeur de copie qui peut être largement suffisant dès lors que les membres de ta classes sont assez simples. Un bon indicateur pour savoir si cela s'applique est de voir si le destructeur est trivial : si ton destructeur ne fait rien, il est probable que le constructeur de copie te suffise.
    Ainsi, si je reprends le code, on peut faire :
    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
    #include <vector>
    #include <iostream>
     
    struct A
    {
       A(std::vector<int> const & v_):m_v(v_){}
       std::vector<int> const & get_v()const{return m_v;}
       private:
       std::vector<int> m_v;
    };
    int main()
    {
       std::vector<int> vect;
       vect.push_back(1);
       vect.push_back(2);
       vect.push_back(3);
       A a(vect);
       A b(a);// constructeur de copie par défaut : le vecteur de a est copié dans celui de b
       for(std::vector<int>::const_iterator it= b.get_v().begin();it!=b.get_v().end();++it){
          std::cout<<*it<<std::endl;
       }
     
       return 0;
    }

  6. #6
    Nouveau membre du Club
    Homme Profil pro
    Inscrit en
    Mai 2009
    Messages
    23
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Mai 2009
    Messages : 23
    Points : 27
    Points
    27
    Par défaut
    ça y est j'ai apparemment réussi.

    en fait, le problème se règle si au lieu de faire directement

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    toto toto2(toto1.get_tata())
    on passe par une variable intermédiaire:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    vector <int> v_inter = toto1.get_tata();
    toto2(v_inter);
    voila merci à tous.

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

    Informations forums :
    Inscription : Juillet 2008
    Messages : 1 580
    Points : 2 205
    Points
    2 205
    Par défaut
    Certes mais comme dit par 3Darchi tu ferais mieux de passer par la copie de ta classe...
    "Hardcoded types are to generic code what magic constants are to regular code." --A. Alexandrescu

  8. #8
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Points : 13 017
    Points
    13 017
    Par défaut
    Citation Envoyé par thhomas Voir le message
    ça y est j'ai apparemment réussi.

    en fait, le problème se règle si au lieu de faire directement

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    toto toto2(toto1.get_tata())
    on passe par une variable intermédiaire:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    vector <int> v_inter = toto1.get_tata();
    toto2(v_inter);
    voila merci à tous.
    C'est probablement que ton constructeur prend une référence non constante :
    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
    #include <iostream>
    #include <vector>
    
    struct A
    {
       A(std::vector<int>& v_):m_v(v_){}
       std::vector<int> get_v()const{return m_v;}
       private:
       std::vector<int> m_v;
    };
    
    int main()
    {
       std::vector<int> vect;
       vect.push_back(1);
       vect.push_back(2);
       vect.push_back(3);
       A a(vect);
       A b(a.get_v());// erreur !
       return 0;
    }
    Ceci est une erreur de conception.

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Il y a deux choses à prendre en compte...

    La première est qu'il existe un constructeur dit "par copie" qui est automatiquement inséré par le compilateur si tu ne le déclare pas toi-même.

    Cet opérateur par copie sera parfaitement adapté si tu utilise un conteneur de la STL comme membre de ta classe, car il effectue une copie "membre à membre".

    Ainsi, le plus facile, étant donné que le fait de ne passer qu'un membre (qu'il vaut mieux éviter d'exposer) particulier de ta classe, consiste sans doute à "simplement" estimer que ton deuxième objet est... une copie du premier.

    Ainsi, le code suivant est tout à fait valide:
    int main
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    {
       toto t1;
       /* on insère les valeurs dans t1 */
       toto t2(t1); // et on effectue une copie du contenu de t1 pour obtenir t2
       /* on travaille avec t2 ou avec t1 indiféremment sans problème ;) 
        * mais les modifications apportées à t1  ne sont pas automatiquement
        * reportées sur t2, ni inversement
        */
       return 0;
    }
    La deuxième chose à prendre en compte, pour rester dans la lignée de ce que j'expliquais plus haut, c'est que tous les conteneurs de la STL présentent un constructeur prenant un itérateur pointant sur un élément de début et un autre pointant sur un élément de fin.

    Ce que tu peux faire pour en tirer parti, c'est également exposer un typedef sur le conteneur utilisé, et un constructeur prenant deux itérateurs.

    La classe présentée plus haut prendrait alors la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    class MyClass
    {
        public:
        /* les ajouts */
        typedef std::vector<int> container;
        MyClass(const_iterator b, const_iterator e):m_tata(b,e){}
        /* le reste ne change pas */
    };
    et tu pourrais très bien envisager un code proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    int main()
    {
        /* push_back n'existe pas pour tous les conteneurs de la STL, par contre,
         * insert fonctionne avec la plupart (sauf stack et queue)
         * profitons de cette particularité ;) 
         *
         * remplissons un conteneur sans nous inquiéter du type réel
         */
        MyClass::container c;
        c.insert(c.end(),1);
        c.insert(c.end(),2);
        c.insert(c.end(),3);
        /* créons un premier objet en passant les élément de  notre conteneur */
        MyClass first(c.begin(),c.end());
        /* et nous pouvons même créer un second objet en lui passant les
         * itérateurs adéquats issus du premier
         * (mais le constructeur par copie est peut être plus intéressant)
         */
        MyClass second(first.begin(), first.end());
        return 0;
    }
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

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

Discussions similaires

  1. [Débutant] Utilisation d'un string comme paramètre passé à une méthode
    Par LaBoétie dans le forum C#
    Réponses: 10
    Dernier message: 27/02/2015, 11h29
  2. [DisplayTag] Utiliser un Decorator pour chaque élément d'une ligne
    Par guntzerp dans le forum Taglibs
    Réponses: 2
    Dernier message: 24/06/2010, 11h57
  3. Réponses: 2
    Dernier message: 04/06/2009, 19h42
  4. Réponses: 2
    Dernier message: 26/07/2006, 12h46
  5. Objet vector<Type> membre d'une classe
    Par Chewbi dans le forum SL & STL
    Réponses: 3
    Dernier message: 16/02/2006, 17h12

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