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 :

Wrapper std::vector ou pas ?


Sujet :

C++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre confirmé
    Profil pro
    Inscrit en
    Avril 2010
    Messages
    96
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2010
    Messages : 96
    Par défaut Wrapper std::vector ou pas ?
    Bonsoir,

    Je suis régulièrement confronté à un choix et je n'arrive pas à me décider.

    Si une objet A comporte un std::vector, et qu'un autre objet B cherche à accéder à tous les éléments de ce vector :
    - Il vaut mieux écrire les accesseurs de A utilisés par B qui permettent cette opération. Fastidieux mais opaque.



    - Il vaut mieux retourner std::vector<MonType>&. Simple mais transparent.

  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,

    Commence d'abord par te poser la question de savoir s'il est cohérent que ton objet de type C puisse accéder aux éléments (de type B) contenus dans le vecteur du type A...

    En effet, la loi demeter nous incite à veiller à ce que, si un objet de type A contient un objet de type B, un objet de type C connaissant l'objet de type A ne devrait pas pouvoir accéder, au départ de l'objet de type A, à l'objet de type B, dont C ne devrait d'ailleurs avoir aucune connaissance.

    Le corolaire de cette loi est que la réponse dépendra principalement de la responsabilité que tu envisage de donner pour A.

    Dans certains cas, il sera plutot opportun de fournir des fonctions membres à A qui permettent "simplement" d'effectuer certains traitement sur tout ou partie du contenu de la collection de B sans pour autant exposer le fait que A manipule des B, les B n'étant alors considéré que comme... des données permettant à A de fournir les services que l'on attend de sa part

    Dans d'autres, la classe A se "substitue" à la collection (au vector) et sa responsabilité est alors de gérer les objets de type B.

    L'approche la plus maléable est alors de se dire que le vector n'est en réalité qu'un détail d'implémentation, qui pourrait parfaitement être remplacé par n'importe quel autre type de conteneur présentant une interface identique (list, set ou autre), mais que l'utilisateur de A n'a strictement aucun besoin de savoir si les différents objets sont bel et bien placés dans un vector, ou s'ils sont en réalité placés dans une list

    Dans ce cas là, quelques typedef de visibilité bien choisie et quelques fonctions utiles représentent, à mon sens, la meilleur alternative.

    Il peut, en effet, paraitre fastidieux de créer les typedefs et les fonctions membres, mais le temps "perdu" à le faire sera très largement récupéré en terme de facilité et de sécurisation d'utilisation, voire, en terme de décision différente ultérieure.

    Ainsi, il faut comprendre que si tu renvoie une référence sur le conteneur (quel qu'il soit) non constante au départ d'une fonction membre non constante de ta classe, tu ouvre la porte à une modification incontrôlée et incontrôlable du contenu de celui-ci, sans compter le fait que tu rend bien plus compliquée la décision éventuelle de changer le type de la collection parce que certains bench en auraient démontré l'opportunité

    En un mot, et bien que cela puisse paraitre paradoxal, je dirais presque que ton but doit être de te compliquer la vie afin de faciliter celle de ceux qui utiliseront ton travail

    Pour moi, la meilleure manière de t'y prendre, s'il est acquis que A se substitue réellement à une collection, serait donc de partir d'une classe A ressemblant à quelque chose comme
    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 A
    {
        /* ce typedef est privé par défaut, car à usage interne uniquement */
        typedef std::vector<UnType> vector;
        public:
            /* il faudra surement l'itérateur constant */
            typedef vector::const_iterator const_iterator;
            /* et  * peut être * l'itérateur non constant */
            typedef vector::iterator iterator;
            /* on rajoute les fonctions qui vont bien */
            A(){}
            template <typename iterator>
            A(iterator b, iterator e):tab(b,e){}
            const_iterator begin() const{return tab.begin();}
            const_iterator end() const{return tab.end();}
            const_iterator find(/* paramètres */) const; // si elle est utile
            size_t size() const{return tab.size();}
            void add(UnType);
            /* toutes les autres fonctions utiles (éventuellement non constantes) 
             */
        private:
            vector tab;
    }
    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 confirmé
    Profil pro
    Inscrit en
    Avril 2010
    Messages
    96
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2010
    Messages : 96
    Par défaut
    Merci

    J'ai rien compris à la loi Demeter.
    Ça signifie qu'une méthode d'un objet A ne doit pas accéder à une collection d'objet dans un autre objet B qui se trouve dans A ?
    EDIT : Google est mon amis, Oui la loi demeter c'est ça...

    Mais comme tu dis, masquer le fait qu'il s'agit d'un std::vector est une chose importante.

  4. #4
    Membre Expert

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2007
    Messages
    1 895
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : Septembre 2007
    Messages : 1 895
    Par défaut
    Citation Envoyé par Agoudard Voir le message
    Mais comme tu dis, masquer le fait qu'il s'agit d'un std::vector est une chose importante.
    C'est même certainement le point le plus important, car ainsi tu masque ton implémentation, forçant l'utilisateur à utiliser ton interface (paradigme program to interface). Tu t'assure ainsi d'un meilleur contrôle de l'invariant de la classe, puisque l'état de ta classe ne peut être modifié sans passer par le chemin que tu imposes.

    Bien évidemment, ça nécessite de prévoir une interface bien pensée, ce qui te force à réfléchir aux objectifs que tu t'es fixé et à la place de cette classe dans ton plan global.

    Bref, bien programmer, ça force à bien concevoir
    [FAQ des forums][FAQ Développement 2D, 3D et Jeux][Si vous ne savez pas ou vous en êtes...]
    Essayez d'écrire clairement (c'est à dire avec des mots français complets). SMS est votre ennemi.
    Evitez les arguments inutiles - DirectMachin vs. OpenTruc ou G++ vs. Café. C'est dépassé tout ça.
    Et si vous êtes sages, vous aurez peut être vous aussi la chance de passer à la télé. Ou pas.

    Ce site contient un forum d'entraide gratuit. Il ne s'use que si l'on ne s'en sert pas.

  5. #5
    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
    Je reste dubitatif (je suis comme le PO, je me pose cette question depuis un moment déjà).

    @koala01 : si la classe A dispose en interne d'un autre vecteur, l'interface peut vite devenir compliquée

    Personnellement, j'en suis arrivé à retourner une référence constante sur le vecteur interne avec une méthode add. J'utilise énormément du concept de range, donc j'ai pas vraiment besoin des begin / end

    Mon design m'amène souvent dans ce cas à considérer que A doit gérer l'aspect collection (je restreint donc la possibilité d'ajouter des éléments) par contre A ne doit pas contrôler le contenu (mes vecteurs sont alors des vecteurs de pointeurs ce qui permet lors du renvoi de la référence constante sur le vecteur d'interdire l'ajout / suppression d'éléments mais autorise l'appel de méthodes non constantes sur mes objets)

    ex:
    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
     
    #define foreach BOOST_FOREACH
     
    struct A
    {
      typedef std::vector<std::shared_ptr<MonType>> lstTypes;
     
      void add(std::shared_ptr<MonType>& elt);
      //il m'arrive aussi d'utiliser des méthodes add avec les paramètres nécessaires à la construction d'un MonType
     
      lstTypes const& mes_types() const;
     
    private:
       lstTypes mes_types_m;
    };
     
    int main()
    {
      A a;
      using namespace boost::adaptors;
      foreach(UnType& t, a.mes_types() | indirected)
      {
        //faire un truc avec t mais pas forcement de méthodes constantes
      }
    }

  6. #6
    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 Agoudard Voir le message
    Merci

    J'ai rien compris à la loi Demeter.
    Ça signifie qu'une méthode d'un objet A ne doit pas accéder à une collection d'objet dans un autre objet B qui se trouve dans A ?
    EDIT : Google est mon amis, Oui la loi demeter c'est ça...
    Un petit exemple, ca vaudra sans doute mieux qu'un grand discours :
    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
    class B
    {
        public:
            void doSomthing()/* const */ {/*...*/}
    };
    class A
    {
        private /* ou protected */:
        B b;
    };
    class C
    {
        public:
            void foo(A /* const & */ a);
    };
    Si tu veux invoquer la fonction doSomething du membre b de type B qui se trouve dans la classe A, tu as la mauvaise méthode qui consiste à offrir un accesseur sur b:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class A
    {
       public:
           B /* const & */ getB() /* const{return b;}
           /* comme plus haut */
    };
    void C::foo(A /* const & a)
    {
        a.getB().doSomething();
    }
    En effet, bien que *relativement simple*, cette solution oblige le type C à connaitre le type B, alors qu'il doit déjà connaitre le type A.


    Et tu as la bonne méthode qui consiste à se dire que la classe A connait déjà la classe B et sait donc qu'il existe la fonction B::doSomething.

    Il n'y a donc aucune raison à forcer C à connaitre la classe B: Par contre, on peut estimer que l'invocation de b::doSomething fait partie des services rendus (éventuellement à l'intérieur d'une logique plus complexe) par la classe A.

    De cette manière, celui qui utilise la classe A (C dans mon exemple) ne doit connaitre que la classe A, et rien d'autre :
    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
    class A
    {
        public:
            void anyService() /* const */
            {
                /* éventuellement quelque chose avant */
                b.doSomething();
            }
        private:
            B b;
    };
    void C::foo(A /* const & */ a)
    {
        a.anyService();
    }
    Citation Envoyé par CedricMocquillon Voir le message
    Je reste dubitatif (je suis comme le PO, je me pose cette question depuis un moment déjà).

    @koala01 : si la classe A dispose en interne d'un autre vecteur, l'interface peut vite devenir compliquée
    Elle ne devrait pas...

    Il y a en effet un autre principe qu'il s'agit de garder en tête: il est connu sous le terme de SRP (Single Responability Principle, ou principe de la responsabilité unique, si tu préfère en francais )

    C'est ce fameux principe qui dit, entre autre, que si une fonction fait plus d'une chose, c'est sans doute qu'elle en fait trop.

    Mais ce n'est pas seulement vrai pour les fonctions, ce l'est aussi pour les classes et les structures

    Même en généralisant au mieux la responsabilité que l'on souhaite donner à une classe qui manipule une collection d'objets, on reste dans l'ordre de "sa resposabilité est de permettre la gestion des différents objets".

    Mais, comme Emmanuel l'a fait remarquer, l'idéal reste toujours de veiller à ne
    pas exposer le type de collection qui est manipulé par la classe
    Personnellement, j'en suis arrivé à retourner une référence constante sur le vecteur interne avec une méthode add.
    C'est un tord
    J'utilise énormément du concept de range, donc j'ai pas vraiment besoin des begin / end
    Et pourtant, si tu présente simplement des fonctions qui renvoient un itérateur:
    1. begin permet d'obtenir le premier élément sans se poser de question
    2. end permet de s'assurer que l'itérateur renvoyé est valide
    3. il manque "juste" une fonction find qui te permette de déterminer les deux extrêmes de ton range (outre, bien sur, les fonctions permettant de rajouter des éléments


    Mon design m'amène souvent dans ce cas à considérer que A doit gérer l'aspect collection (je restreint donc la possibilité d'ajouter des éléments) par contre A ne doit pas contrôler le contenu (mes vecteurs sont alors des vecteurs de pointeurs ce qui permet lors du renvoi de la référence constante sur le vecteur d'interdire l'ajout / suppression d'éléments mais autorise l'appel de méthodes non constantes sur mes objets)
    Tu peux parfaitement renvoyer des itérateurs non constants, s'il apparait que la modification de l'élément n'occasionne pas la modification de l'objet gérant la collection... mais ce n'est pas forcément le cas
    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
     
    #define foreach BOOST_FOREACH
     
    struct A
    {
      typedef std::vector<std::shared_ptr<MonType>> lstTypes;
     
      void add(std::shared_ptr<MonType>& elt);
      //il m'arrive aussi d'utiliser des méthodes add avec les paramètres nécessaires à la construction d'un MonType
     
      lstTypes const& mes_types() const;
     
    private:
       lstTypes mes_types_m;
    };
     
    int main()
    {
      A a;
      using namespace boost::adaptors;
      foreach(UnType& t, a.mes_types() | indirected)
      {
        //faire un truc avec t mais pas forcement de méthodes constantes
      }
    }
    Là, tu n'utilise pas vraiment un range: tu invoque de manière systématique une série de fonctions sur l'ensemble du contenu

    Par contre, avec begin et end, tu peux *réellement* travailler sur des ranges, et ce sera encore plus le cas avec la nouvelle norme qui autorise
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    std::for_each(iter_begin, iter_end, functor);
    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

  7. #7
    Membre Expert

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2007
    Messages
    1 895
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : Septembre 2007
    Messages : 1 895
    Par défaut
    Citation Envoyé par CedricMocquillon Voir le message
    Je reste dubitatif (je suis comme le PO, je me pose cette question depuis un moment déjà).

    @koala01 : si la classe A dispose en interne d'un autre vecteur, l'interface peut vite devenir compliquée
    Si la classe en interne possède un autre vecteur, alors son rôle n'est clairement pas celui d'un conteneur - du coup, son interface se doit de le refléter. Elle ne devient donc pas nécessairement plus compliquée.

    Citation Envoyé par CedricMocquillon Voir le message
    Personnellement, j'en suis arrivé à retourner une référence constante sur le vecteur interne avec une méthode add. J'utilise énormément du concept de range, donc j'ai pas vraiment besoin des begin / end

    Mon design m'amène souvent dans ce cas à considérer que A doit gérer l'aspect collection (je restreint donc la possibilité d'ajouter des éléments) par contre A ne doit pas contrôler le contenu (mes vecteurs sont alors des vecteurs de pointeurs ce qui permet lors du renvoi de la référence constante sur le vecteur d'interdire l'ajout / suppression d'éléments mais autorise l'appel de méthodes non constantes sur mes objets)
    L'intérêt d'encapsuler un vecteur, c'est de permettre d'effectuer des traitements plus spécialisés sur ce vecteur. Dans on exemple, il eut été plus simple de proposer un méthode foreach() dans ta classe (prenant par exemple un foncteur en paramètre) plutot que d'exposer la collection interne.

    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
     
    struct A // la même que ta classe, plus : 
    {
    public:
      template <class Callable> void foreach(Callable callable)
      {
        std::for_each(mes_types_m.begin(), mes_types_m.end(), callable);
      }
      typedef lstTypes::value_type type;
    };
     
    int main()
    {
      A a;
     
      // je mets une lambda, histoire de simplifier le code ; on peut passer
      // n'importe quel callable, que ce soit un foncteur, un boost.function,
      // etc.
      a.foreach([](A::type& v){ v.bar(); });
    }
    [FAQ des forums][FAQ Développement 2D, 3D et Jeux][Si vous ne savez pas ou vous en êtes...]
    Essayez d'écrire clairement (c'est à dire avec des mots français complets). SMS est votre ennemi.
    Evitez les arguments inutiles - DirectMachin vs. OpenTruc ou G++ vs. Café. C'est dépassé tout ça.
    Et si vous êtes sages, vous aurez peut être vous aussi la chance de passer à la télé. Ou pas.

    Ce site contient un forum d'entraide gratuit. Il ne s'use que si l'on ne s'en sert pas.

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

Discussions similaires

  1. std::vector semble ne pas utiliser std::move, pourquoi ?
    Par n0-sheep dans le forum SL & STL
    Réponses: 7
    Dernier message: 15/03/2014, 01h25
  2. Réponses: 10
    Dernier message: 30/06/2008, 19h59
  3. vertex array et std::vector marche pas, help!
    Par filoo dans le forum OpenGL
    Réponses: 14
    Dernier message: 07/07/2007, 13h00
  4. std ::vector [erreur que je ne comprend pas]
    Par aaronw dans le forum SL & STL
    Réponses: 8
    Dernier message: 05/03/2006, 21h00
  5. Sauvegarde std::vector dans un .ini
    Par mick74 dans le forum MFC
    Réponses: 2
    Dernier message: 12/05/2004, 13h30

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