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

SL & STL C++ Discussion :

Retourner un conteneur de pointeurs d'objets constants


Sujet :

SL & STL C++

  1. #1
    Membre chevronné
    Avatar de Florian Goo
    Profil pro
    Inscrit en
    Septembre 2008
    Messages
    680
    Détails du profil
    Informations personnelles :
    Âge : 39
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Septembre 2008
    Messages : 680
    Par défaut Retourner un conteneur de pointeurs d'objets constants
    Bonjour à tous,

    J'ai un doute sur une implémentation. Quel est le meilleur moyen de retourner un conteneur de pointeurs d'objets de façon à ce que les objets ne puissent pas être accédés de façon non-const ?

    Soit une classe « panier » contenant une liste d'objets de type « pomme ».
    De façon naïve, on commence par écrire le code suivant :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
     
    #include <list>
     
    class pomme
    {
        public:
            void
            set_taille(int){/*...*/};
     
        //...
    };
     
    class panier
    {
        public:
            //...
     
            const std::list<pomme*>&
            get_pommes() const
            {
                return pommes_;
            }
     
        private:
            std::list<pomme*> pommes_;
    };
    Or, de cette façon, rien ne nous empêche d'appeler une méthode non-const d'un des objets pomme du conteneur (ce qui est tout à fait logique, mais totalement indésirable) :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    void
    foo(const panier& un_panier)
    {
        const std::list<pomme*>& pommes = un_panier.get_pommes();
        if(!pommes.empty()) pommes.back()->set_taille(3);
    }

    Quelle est la bonne façon de faire dans un tel cas de figure ?
    Il y a trois méthodes qui me viennent à l'esprit :
    1. ne pas écrire d'accesseur direct à la liste, mais interfacer son accès via un itérateur qui renverra des const pomme* const ;
    2. gérer deux listes : une contenant des pomme* et une autre des const pomme* ;
    3. modifier l'accesseur à la liste de façon à ce qu'il retourne un objet proxy encapsulant la liste et proposant une interface empêchant une modification des objets pointés (probablement à base d'itérateur similaire à la première méthode).


    La méthode 1 induit l'écriture d'un tas de méthodes membres (get_pommes_begin_iterator(), _end_iterator(), sans parler d'empty() et de size()), ce qui serait extrêmement lourd).
    La méthode 2 me semble être la plus simple à mettre en place.
    La méthode 3 semble propre elle aussi (quoique je n'ai pas énormément creusé cette possibilité), mais si elle était généralement utilisée on aurait des classes proxy template déjà présentes dans la STL ou dans Boost, mais il n'y existe rien de tel à ma connaissance.

    Si je devais faire un choix moi-même, ce serait donc la méthode 2.
    Toutefois, j'aimerais beaucoup avoir vos différents avis. Utilisez-vous une autre méthode à laquelle je n'ai pas pensée ?

    J'attends vos réponses
    Cours : Initiation à CMake
    Projet : Scalpel, bibliothèque d'analyse de code source C++ (développement en cours)
    Ce message a été tapé avec un clavier en disposition bépo.

  2. #2
    Membre éclairé
    Inscrit en
    Avril 2005
    Messages
    1 110
    Détails du profil
    Informations forums :
    Inscription : Avril 2005
    Messages : 1 110
    Par défaut
    Ce n'est peut-être pas très élégant, mais faire un cast pour que get_pommes retourne const std::list<const pomme*>& ?

  3. #3
    Membre chevronné
    Avatar de Florian Goo
    Profil pro
    Inscrit en
    Septembre 2008
    Messages
    680
    Détails du profil
    Informations personnelles :
    Âge : 39
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Septembre 2008
    Messages : 680
    Par défaut
    Eh non, c'est pas valide, comme cast
    Cours : Initiation à CMake
    Projet : Scalpel, bibliothèque d'analyse de code source C++ (développement en cours)
    Ce message a été tapé avec un clavier en disposition bépo.

  4. #4
    Membre expérimenté Avatar de Nogane
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    241
    Détails du profil
    Informations personnelles :
    Âge : 45
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 241
    Par défaut
    Bonsoir,
    Je me permet de rajouter une solution qui a une petite chance de convenir:
    Avec un "const boost::ptr_list" les éléments contenu sont constant eux aussi.

  5. #5
    Membre chevronné
    Avatar de Florian Goo
    Profil pro
    Inscrit en
    Septembre 2008
    Messages
    680
    Détails du profil
    Informations personnelles :
    Âge : 39
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Septembre 2008
    Messages : 680
    Par défaut
    Dans mon cas particulier, mes objets sont alloués sur la pile, puis déplacés (move semantics de C++0x) dans un conteneur d'objets (et non de pointeurs d'objet). Le conteneur de pointeurs d'objet est là pour faire du polymorphisme.
    Par exemple, pour rester dans l'esprit de l'exemple que j'ai donné, ça donnerait plutôt ça :
    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
     
    class panier
    {
        public:
            void
            add(pomme&& p)
            {
                pommes_.push_back(p);
                pomme& p_ref = pommes_.back();
                fruits_.add(&p_ref);
            }
     
            void
            add(poire&& p)
            {
                poires_.push_back(p);
                poire& p_ref = poires.back();
                fruits_.add(&p_ref);
            }
     
            void
            add(scoubidoubidou&& s)
            {
                //...
            }
     
            const std::list<fruit*>
            get_fruits() const
            {
                return fruits_;
            }
     
        private:
            std::list<fruit*> fruits_;
            std::list<pomme> pommes_;
            std::list<poire> poires_;
            std::list<scoubidoubidou> scoubidoubidou_;
            //...
    };
    Quoi qu'il en soit, je ne pense pas qu'on puisse parler d'objets alloués sur le tas dans ce cas-là. Par conséquent, boost::ptr_list n'est pas envisageable.
    Cours : Initiation à CMake
    Projet : Scalpel, bibliothèque d'analyse de code source C++ (développement en cours)
    Ce message a été tapé avec un clavier en disposition bépo.

  6. #6
    Membre émérite

    Profil pro
    Inscrit en
    Septembre 2006
    Messages
    717
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2006
    Messages : 717
    Par défaut
    Pour ma part j'utilise std::vector à 99.9% du temps, les 0.1% sont des std::set ou std::map (je crois que je n'ai jamais trouvé un cas concret où l'utilisation de std::list se justifiait) alors une interface d'accès à base d'index convient souvent :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    class panier
    {
    public:
      int nombre_de_fruits() const {return int(fruits_.size());}
      fruit const& fruit(int i) const {return *fruits_[i];}
     
    private:
      std::vector<fruit*> fruits_;
    };

  7. #7
    Membre éclairé
    Inscrit en
    Avril 2005
    Messages
    1 110
    Détails du profil
    Informations forums :
    Inscription : Avril 2005
    Messages : 1 110
    Par défaut
    Citation Envoyé par Florian Goo Voir le message
    Eh non, c'est pas valide, comme cast
    En trichant un peu si.
    Je le reconnais, c'est un peu gonfflé, mais ça fonctionne très bien.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    std::vector<const unsigned *> & foo()
    {
    	std::vector<unsigned *> u_v;
     
    	void *vp=&u_v;
    	std::vector<const unsigned *> *cu_v_ptr=reinterpret_cast<std::vector<const unsigned *> *>(vp);
    	return *cu_v_ptr;
    }
     
    int main()
    {
    	std::vector<const unsigned *> & r=foo();
    }

  8. #8
    Expert confirmé
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Décembre 2003
    Messages
    3 549
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Décembre 2003
    Messages : 3 549
    Par défaut
    Plutôt que de retourner un conteneur, tu pourrais pas plutôt fournir une paire d'itérateurs, ou une range ?
    Tu fais un simple adapteur autour de ton conteneur, ça fait deux lignes et c'est bon...

    return transformed(v, static_cast_<const unsigned*>(_1));

    la deuxième ligne étant un typedef du type de retour de cette expression.

  9. #9
    Membre chevronné
    Avatar de Florian Goo
    Profil pro
    Inscrit en
    Septembre 2008
    Messages
    680
    Détails du profil
    Informations personnelles :
    Âge : 39
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Septembre 2008
    Messages : 680
    Par défaut
    Oui, c'est ce que j'ai tenté en premier avec ce type de code :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
     
    class panier
    {
        public:
            //...
     
            std::list<fruit*>::const_iterator
            get_fruits_begin() const
            {
                return fruits_.begin();
            }
     
            std::list<fruit*>::const_iterator
            get_fruits_end() const
            {
                return fruits_.end();
            }
     
        private:
            std::list<fruit*> fruits_;
            std::list<pomme> pommes_;
            std::list<poire> poires_;
            std::list<scoubidoubidou> scoubidoubidou_;
            //...
    };
    Mais après il aurait fallut écrire get_fruits_size(), get_fruits_empty(), etc… ce qui au final donne une trop grande quantité de code.

    Ceci dit, si tu as une solution élégante similaire requérant une quantité moindre de code, je suis partant . Retourner une paire d'itérateur semble être la solution privilégiée par les experts (par exemple, l'implémentation du pattern Composite du livre du GoF).

    Cette fonction transformed() provient-elle de boost ? Le grand fan de Boost que tu es me souffle que oui. Je vais me renseigner sur boost::iterator
    Cours : Initiation à CMake
    Projet : Scalpel, bibliothèque d'analyse de code source C++ (développement en cours)
    Ce message a été tapé avec un clavier en disposition bépo.

  10. #10
    Membre chevronné
    Avatar de Florian Goo
    Profil pro
    Inscrit en
    Septembre 2008
    Messages
    680
    Détails du profil
    Informations personnelles :
    Âge : 39
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Septembre 2008
    Messages : 680
    Par défaut
    En trifouillant, je suis tombé sur boost::range qui est tout ce dont j'avais besoin

    Note à moi-même : Pour comprendre les posts de loufoque, penser à préfixer la plupart des mots par « boost:: ».

    Merci à tous
    Cours : Initiation à CMake
    Projet : Scalpel, bibliothèque d'analyse de code source C++ (développement en cours)
    Ce message a été tapé avec un clavier en disposition bépo.

  11. #11
    Expert confirmé
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Décembre 2003
    Messages
    3 549
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Décembre 2003
    Messages : 3 549
    Par défaut
    Mais après il aurait fallut écrire get_fruits_size(), get_fruits_empty(), etc… ce qui au final donne une trop grande quantité de code.
    Ces deux opérations se font déjà sans problème sur une range...

  12. #12
    Membre chevronné
    Avatar de Florian Goo
    Profil pro
    Inscrit en
    Septembre 2008
    Messages
    680
    Détails du profil
    Informations personnelles :
    Âge : 39
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Septembre 2008
    Messages : 680
    Par défaut
    Oui, j'ai vu ça. Ça m'a justement ravi
    Cours : Initiation à CMake
    Projet : Scalpel, bibliothèque d'analyse de code source C++ (développement en cours)
    Ce message a été tapé avec un clavier en disposition bépo.

  13. #13
    Membre éclairé
    Profil pro
    Inscrit en
    Janvier 2007
    Messages
    301
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Janvier 2007
    Messages : 301
    Par défaut
    Florian, pourrais-tu mettre le code que tu as retenu (à priori celui à base de range)? Et as tu réussi à percer le mystère de cette méthode "transformed(v, static_cast_<const unsigned*>(_1));"? loufoque, peux tu nous en dire plus? Je n'ai rien trouvé sur le net (en même temps je n'y pas non plus passé beaucoup de temps) mais je pense que la solution à ce problème mérite d'être développée puisque c'est un problème qui apparait dans de nombreux cas de figure et qui par conséquent intéressera beaucoup de monde. Merci à vous deux

  14. #14
    Membre chevronné
    Avatar de Florian Goo
    Profil pro
    Inscrit en
    Septembre 2008
    Messages
    680
    Détails du profil
    Informations personnelles :
    Âge : 39
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Septembre 2008
    Messages : 680
    Par défaut
    La solution que j'ai retenue fait usage à la fois de boost::iterator et de boost::range.

    J'ai tout d'abord défini les typedefs suivants :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    typedef std::list<fruit*> fruits_t;
    typedef fruits_t::const_iterator fruit_const_iterator;
    typedef boost::indirect_iterator<fruit_const_iterator, const fruit&> fruit_const_indirect_iterator;
    typedef boost::iterator_range<fruit_const_indirect_iterator> fruit_const_iterator_range;
    En privé, on a le conteneur suivant :
    … qu'on retourne comme ceci :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     
    panier::fruit_const_iterator_range
    panier::get_fruits() const
    {
    	fruit_const_iterator first = fruits_.begin();
    	fruit_const_iterator last = fruits_.end();
     
    	fruit_const_indirect_iterator const_indirect_first(first), const_indirect_last(last);
     
    	return fruit_const_iterator_range(const_indirect_first, const_indirect_last);
    }
    La réponse à mon problème était surtout boost::range. Par conséquent, la couche boost::indirect_iterator est facultative.
    Voilà tout

    En ce qui concerne l'hypothétique fonction transformed(), je ne suis pas sûr, je pense qu'il s'agit de std::transform() mais la signature ne correspond pas. De toutes façons, j'ai été comblé par boost::range
    Cours : Initiation à CMake
    Projet : Scalpel, bibliothèque d'analyse de code source C++ (développement en cours)
    Ce message a été tapé avec un clavier en disposition bépo.

  15. #15
    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
    Citation Envoyé par CedricMocquillon Voir le message
    <snip>
    loufoque, peux tu nous en dire plus? Je n'ai rien trouvé sur le net (en même temps je n'y pas non plus passé beaucoup de temps) mais je pense que la solution à ce problème mérite d'être développée puisque c'est un problème qui apparait dans de nombreux cas de figure et qui par conséquent intéressera beaucoup de monde. Merci à vous deux
    La doc ne semble exister qu'en anglais, (enfin, je n'ai pas cherché non plus ) mais elle est sur le site même de boost
    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

  16. #16
    Membre Expert

    Inscrit en
    Mai 2008
    Messages
    1 014
    Détails du profil
    Informations forums :
    Inscription : Mai 2008
    Messages : 1 014
    Par défaut
    Citation Envoyé par Florian Goo Voir le message
    La réponse à mon problème était surtout boost::range. Par conséquent, la couche boost::indirect_iterator est facultative.
    Voilà tout
    Humm, pourrais-tu préciser comment la seule utilisation de boost_range permet de résoudre le problème ?

    J'ai fait une tentative avec l'exemple des fruits et du panier, mais chez moi le problème du const reste désespérément identique.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    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
     
    #include <boost/range.hpp>
    #include <list>
     
    class Fruit
    {
    public:
        void
        set_taille(int taille) {  taille_ = taille; } // fonction non const
    private: 
       int taille_;
    };
     
    class Pomme : public Fruit
    {
    };
     
    class Panier
    {
    public:
     
    typedef std::list<Fruit*> fruits_t;
    typedef fruits_t::const_iterator fruit_const_iterator;
    typedef boost::iterator_range<fruit_const_iterator> fruit_const_iterator_range;
     
       void
       add (const Pomme& p)
       {
          pommes_.push_back(p);
          Pomme& p_ref = pommes_.back();
          fruits_.push_back(&p_ref);
       }
     
        const fruit_const_iterator_range get_fruits() const
        {
            fruit_const_iterator first = fruits_.begin();
    	fruit_const_iterator last = fruits_.end();
     
    	return fruit_const_iterator_range(first, last);
       }
     
    private:
       std::list<Fruit*> fruits_;
       std::list<Pomme> pommes_;
    };
    Et le main :

    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()
    {
       Panier panier;
       Pomme p1;
       Pomme p2;
     
       panier.add(p1);
       panier.add(p2);
     
       Panier::fruit_const_iterator_range r = panier.get_fruits();
       Panier::fruit_const_iterator_range::iterator it = r.begin();
       Panier::fruit_const_iterator_range::iterator end = r.end();
     
       for(; it != end ; ++it)
       {
          (*it)->set_taille(5); // damned, ça compile.
       }
     
    return 0;
    }
    Le Range renvoyé contient des pointeurs constants vers des Fruit, alors que l'on voudrait des pointeurs vers des Fruit constants, exactement comme dans le problème initial en fait. Apparemment, c'est surtout l'utilisation de boost::indirect_iterator qui permet de prolonger la constness du Fruit* au Fruit, non ?
    J'ai manqué quelque chose ?

  17. #17
    Membre chevronné
    Avatar de Florian Goo
    Profil pro
    Inscrit en
    Septembre 2008
    Messages
    680
    Détails du profil
    Informations personnelles :
    Âge : 39
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Septembre 2008
    Messages : 680
    Par défaut
    Oups, oui, tu as raison. L'utilisation d'un indirect_iterator n'est effectivement pas facultative dans le cas d'un conteneur de pointeurs. Au temps pour moi
    Cours : Initiation à CMake
    Projet : Scalpel, bibliothèque d'analyse de code source C++ (développement en cours)
    Ce message a été tapé avec un clavier en disposition bépo.

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

Discussions similaires

  1. Pointeur constant sur objet constant
    Par escafr dans le forum C++
    Réponses: 4
    Dernier message: 30/06/2008, 17h23
  2. Tableau de pointeurs sur objets
    Par bassim dans le forum C++
    Réponses: 11
    Dernier message: 13/12/2005, 19h45
  3. [TTreeView] Problème avec les pointeurs d'objet
    Par BlackWood dans le forum Composants VCL
    Réponses: 2
    Dernier message: 02/07/2004, 14h31
  4. [Debutant VC++.net] Obtenir un pointeur sur objet
    Par SteelBox dans le forum MFC
    Réponses: 6
    Dernier message: 17/06/2004, 18h36
  5. Réponses: 6
    Dernier message: 03/09/2003, 10h29

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