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 :

Type erasure et accesseurs


Sujet :

Langage C++

  1. #1
    Membre habitué
    Profil pro
    Dev
    Inscrit en
    Mai 2009
    Messages
    257
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Dev

    Informations forums :
    Inscription : Mai 2009
    Messages : 257
    Points : 190
    Points
    190
    Par défaut Type erasure et accesseurs
    Bonjour, j'ai mis en place le pattern type erasure en C++, c-a-d que je masque une classe template avec une classe abstraite

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    class Base{
     
         virtual ~Base(){}
     
         //méthodes virtuelles pures
    }
     
    template<typename T>
    class Derived : Base{
     
    Derived<T>(){}
    ~Derived(){}
     
    //méthodes publiques
     
    private :
    vector<T> datas;
     
    }
    problème : si je veux faire récupérer ou modifier datas, je dois passer par Base

    comment dois je définir les accesseurs getDatas() et SetDatas(vector<T> datas) ?

  2. #2
    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,

    Pose toi déjà la question de savoir si tu as réellement besoin des mutateurs et accesseurs

    Dans bien des cas, on se rend compte qu'il est peu intéressant de proposer des accesseur (getData) et encore moins de proposer des mutateurs (setData), surtout lorsque le membre "visé" par l'opération est un conteneur

    N'oublie pas que le propre de la programmation OO (enfin, l'un des aspects principaux, car ce n'est pas le seul ) consiste à envisager un objet sur base des services que l'on attend de lui, et non des données qu'il manipule, les données manipulées n'étant, en définitive, utilisées par l'objet seulement pour lui permettre de rendre les services attendus.

    De ce point de vue, il est déjà excessivement rare de constater qu'un accesseur soit utile, et c'est encore pis au niveau des mutateurs

    Ceci dit, le gros problème avec le type erasure, c'est que tu perdra de toutes manières... l'information de type lorsque tu l'utilisera.

    Tu pourrais donc, effectivement, placer différentes fonctions permettant d'ajouter, de retirer ou de modifier certains éléments de ton vecteur dans ta classe template, sous une forme proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    template<typename T>
    class Derived : Base{
    public:
        typedef typename std::vector<T>::iterator iterator;
        typedef typename std::vector<T>::const_iterator const_iterator; 
        Derived<T>(){}
        ~Derived(){}
         void add(T const & t){datas.push_back(t);}
        iterator begin() {return datas.begin();}
        iterator end() {return datas.end();}
        const_iterator begin() const{return datas.begin();}
        const_iterator end() const{return datas.end();}
        size_t size() const{return datas.size();}
     
    private :
    vector<T> datas;
    };
    Mais tu ne pourra pas y accéder directement au départ de ta classe base.

    Si il faut que tu puisse y accéder, tu devra, par exemple, envisager le recours au pattern visitor
    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 habitué
    Profil pro
    Dev
    Inscrit en
    Mai 2009
    Messages
    257
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Dev

    Informations forums :
    Inscription : Mai 2009
    Messages : 257
    Points : 190
    Points
    190
    Par défaut
    Intéressant, effectivement manipuler directement datas se révèle difficile et délicat

    exploiter les possibilités de la STL (iterator) m'avait échappé

    si je veux modifier datas par adresse sous forme &datas[0], j'utilise begin, c'est ça ?

  4. #4
    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
    On peut ajouter un opérateur [] en version constante et non constante, si tu le souhaite...

    Cela te ferais modifier ta classe template sous une forme proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    template<typename T>
    class Derived : Base{
    public:
        typedef typename std::vector<T>::iterator iterator;
        typedef typename std::vector<T>::const_iterator const_iterator; 
        Derived<T>(){}
        ~Derived(){}
         void add(T const & t){datas.push_back(t);}
        iterator begin() {return datas.begin();}
        iterator end() {return datas.end();}
        const_iterator begin() const{return datas.begin();}
        const_iterator end() const{return datas.end();}
        T& operator[](size_t i){return datas[i]:}
        T const & operator[] (size_t i) const {return datas[i]:}
        size_t size() const{return datas.size();}
     
    private :
    vector<T> datas;
    };
    Cependant, il faut comprendre que l'utilisation d'itérateurs représente souvent une approche bien meilleure, surtout si tu propose de toi-même un alias de type imbriqué dans la classe.

    En effet, les itérateurs fournissent une interface "uniformisée" pour parcourir les différents éléments d'une collection.

    De ce fait, tant que tu travailles avec des collections "compatibles" (std::vector, std::list, std::set, par exemple), tu peux parfaitement envisager de modifier le type de la collection sans que cela n'implique le moindre changement dans le code utilisateur

    De plus, quand c'est applicable, il est souvent préférable d'utiliser le traitement par "paquet" ou par "écart" (j'hésite réellement sur la traduction à donner à "range") quand c'est possible.

    La possibilité d'obtenir un itérateur sur le premier élément et un autre sur ce qui suit le dernier, et éventuellement un itérateur sur un objet donné (fonction find() ) est alors souvent suffisant
    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

  5. #5
    Membre habitué
    Profil pro
    Dev
    Inscrit en
    Mai 2009
    Messages
    257
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Dev

    Informations forums :
    Inscription : Mai 2009
    Messages : 257
    Points : 190
    Points
    190
    Par défaut
    en fait en parlant d'adresse, je parle de l'adresse du vector en lui-même

    je pensais que begin en était un équivalent

    ça m'évite de renvoyer un void*

  6. #6
    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
    La question que tu dois te poser, c'est: pourquoi voudrais tu récupérer l'adresse "de départ" de ton vecteur.

    Ou, plutôt: Quel intérêt aurais tu à essayer d'encapsuler un vecteur si c'est pour en arriver à l'exposer "comme si de rien n'était"

    Pour répondre à ta question, la fonction begin() renvoie un itérateur sur le premier élément du vecteur.

    S'il est vrai qu'un itérateur présente une interface fort proche de celle que l'on observe avec les pointeurs (possibilité de le déréférencer avec l'étoile * ou la flèche ->, possibilité de l'incrémenter), il faut comprendre que ce n'est pas un pointeur pour autant.

    La différence principale tenant dans le fait qu'il s'agit d'un type totalement particulier, différent du type de donnée réellement utilisé
    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 habitué
    Profil pro
    Dev
    Inscrit en
    Mai 2009
    Messages
    257
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Dev

    Informations forums :
    Inscription : Mai 2009
    Messages : 257
    Points : 190
    Points
    190
    Par défaut
    Oui je viens de me rendre compte que je n'en avais pas forcément besoin

    Autre chose : et si le membre se réduit à T* data et plus à vector<T> datas ? C'est là que se poserait le recours au pattern Visitor ?

    edit : mais j'y pense, dans toutes les signatures de méthodes que vous m'avez fournies, apparaît le type T ? je ne vais pas pouvoir les déclarer dans Base ?

  8. #8
    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
    De manière générale, quel que soit le membre que tu décides de placer dans une accessibilité restreinte (comprend: autre que l'accessibilité publique), tu dois toujours te poser la question de savoir si tu as, effectivement, besoin de fournir un accesseur ou pire, un mutateur.

    Les accesseurs peuvent s'avérer intéressants quand une donnée représente, en réalité, une propriété intrinsèque de l'objet (par exemple: la chaine de caractère qui représente le nom d'une personne), mais présentent déjà un intérêt des plus faibles dans de nombreux cas (tu n'a aucun besoin d'accéder directement à un réservoir de voiture pour l'utiliser: tu va essentiellement utiliser son interface telle que "proportion de remplissage","ajouter XXX litres, ...).

    Mais pour les mutateurs, c'est encore pis: si tu n'as déjà pas jugé utile de mettre un accesseur sur un membre, il n'est surement pas utile ne serait-ce que d'envisager de placer un mutateur sur celui-ci.

    Et, dans le cas où tu as besoin de l'accesseur (je reprend l'exemple du nom d'une personne), le fait de permettre à l'utilisateur de le modifier lui autorise à... créer purement et simplement une personne différente (Abert Chose et Andre Chose sont deux personnes différentes, fussent-ils jumeaux, et bien qu'ayant les mêmes initiales )

    Maintenant, peut être n'ai-je pas compris le sens de ta question

    edit : mais j'y pense, dans toutes les signatures de méthodes que vous m'avez fournies, apparaît le type T ? je ne vais pas pouvoir les déclarer dans Base ?
    C'est ce sur quoi j'essayais d'attirer ton attention dans ma première intervention:
    Mais tu ne pourra pas y accéder directement au départ de ta classe base.
    Comme je te le disais, il est possible d'apporter un début de réponse avec le pattern "visiteur", pour les fonctions qui ne nécessitent pas de prendre en paramètre un objet de type T (les fonctions begin, end et size de mon exemple).

    Tu pourrais même envisager, dans le visiteur, de créer une fonction template qui fera ce qu'il faut, t'évitant ainsi de devoir la recopier pour chaque spécialisation de ta classe

    Pour ce qui est des fonctions qui demandent un objet de type T en paramètre, ce sera un peu plus compliqué

    [EDIT]La solution pour y arriver serait, essentiellement, de faire en sorte que tout type T fasse partie d'une hiérarchie de classes commune, de manière à pouvoir utiliser la covariance
    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

  9. #9
    Membre habitué
    Profil pro
    Dev
    Inscrit en
    Mai 2009
    Messages
    257
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Dev

    Informations forums :
    Inscription : Mai 2009
    Messages : 257
    Points : 190
    Points
    190
    Par défaut
    ...

    bon, si j'ai eu recours au type erasure c'est que
    dans mon projet je dois maintenir une collection de Derived<T>

    donc au départ j'avais
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    map<string, Derived<T> >
    bien sûr erreur et donc mise en place de la solution sans penser à la suite

    la question est, dois-je maintenir cette collection, du moins sous cette forme

  10. #10
    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
    Déjà, si tu veux utiliser le type erasure, tu devra les manipuler sous la forme de pointeurs ou de référence (polymorphisme oblige).

    Mais, comme je l'ai indiqué (par édition) dans mon intervention précédente, cela implique que les différents type que tu utilisera pour spécialiser T devront eux aussi appartenir à une hiérarchie de classe commune.

    Tu aurais donc une hiérarchie de données 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
    class AbstractData
    {
        /*...*/
    };
    class Data1 :public AbstractData
    {
    };
    class Data2 :public AbstractData
    {
    };
    class Data3 :public AbstractData
    {
    };
    /*...*/
    Ta classe actuelle serait modifiée de manière à prendre 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
    class Base
    {
        public:
            virtual void accept(Visitor & , AbstractData /* const */ &) = 0;
    }
    template <class T>
    class Derived : public Base
    {
        public:
            accept(Visitor & v AbstractData /* const */ & d)
            {
                v.visit(*this, dynamic_cast<T /* const */ &> (d);
            }
    };
    le tout mis en musique à l'aide d'un visiteur proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    class Visitor
    {
        public:
            template<class T>
            void visit(typename Derived<T> & v, T const & d)
            {
                v.add(d);
            }
    };
    (par exemple )

    [EDIT]Le gros inconvénient de la manoeuvre, c'est que tu ne pourra utiliser pour spécialiser Derivee que... des types apparaissant dans la hiérarchie de classe dont AbstractData fait partie
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  11. #11
    Membre habitué
    Profil pro
    Dev
    Inscrit en
    Mai 2009
    Messages
    257
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Dev

    Informations forums :
    Inscription : Mai 2009
    Messages : 257
    Points : 190
    Points
    190
    Par défaut
    Déjà, si tu veux utiliser le type erasure,
    ça n'a rien d'une obligation, si vous avez une autre stratégie ça m'intéresse aussi
    le type erasure était la première chose que l'on m'a conseillé

    tu devra les manipuler sous la forme de pointeurs ou de référence (polymorphisme oblige)
    oui j'ai fait une erreur, c'est évidemment

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    map<string, Derived<T>* >
     
    map<string, Base* >

    Mais, comme je l'ai indiqué (par édition) dans mon intervention précédente, cela implique que les différents type que tu utilisera pour spécialiser T devront eux aussi appartenir à une hiérarchie de classe commune.

    ...
    donc si je respecte ce schéma, je peux utiliser les iterator et les T dans le Visitor ? mais ça implique autant de Visitor que de méthodes virtuelles, non ?

    [EDIT]Le gros inconvénient de la manoeuvre, c'est que tu ne pourra utiliser pour spécialiser Derivee que... des types apparaissant dans la hiérarchie de classe dont AbstractData fait partie
    D'un côté ça limite les types farfelus mais de l'autre ça interdit la création d'un type en dynamique


    Sinon, avant de venir ici, j'avais posé la même question sur un forum, j'ai reçu deux suggestions :

    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
    class Base
    {
    public:
      template< class T >
      bool SetData( const std::vector< T >& t )
      {
        return SetData( static_cast< const void* >( &t ), typeid( t ) );
      }
     
    protected:
      virtual bool SetData( const void*, const std::type_info& ) = 0;
    };
     
    template< class T >
    class Derived : public Base
    {
    protected:
      bool SetData( const void* p, const std::type_info& info )
      {
        if( info == typeid( std::vector< T > ) )
        {
          const std::vector< T >& v = *static_cast< const std::vector< T >* >( p );
          //ok same type, this should work
          //do something with data here
          return true;
        }
        else
        {
          //not good, different types
          return false;
        }
      }
    };
    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
    class Base
    {
    public:
      template< class T >
      bool SetData(const std::vector<T>& data)
      {
        return SetData(&data,typeid(T));
      }
    private:
      virtual bool SetData(const void* data, const std::type_info& tid) = 0;
    }
     
    template< class T >
    class Derived : public Base
    {
    public:
      bool DoSetData(const std::vector<T>& data)
      {
        // tbd
      }
    private:
      virtual bool SetData(const void* data, const std::typeinfo& tid)
      {
        if( tid != typeid(T) )
          return false;
        const std::vector<T>* pdata = reinterpret_cast<const std::vector<T>*>(data);
        return DoSetData(*pdata);
      }
    }
    je ne sais pas trop quoi en penser

  12. #12
    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
    Citation Envoyé par coda_blank Voir le message
    donc si je respecte ce schéma, je peux utiliser les iterator et les T dans le Visitor ?
    Oui, effectivement
    mais ça implique autant de Visitor que de méthodes virtuelles, non ?
    Ou, au minimum, autant de visiteur qu'il y a de signature différentes pour tes fonctions virtuelles.

    Tu pourrais envisager de limiter grandement le nombre de visiteurs en lui passant un pointeur de fonction ou un foncteur adapté pour les fonctions qui présentent une signature commune
    D'un côté ça limite les types farfelus mais de l'autre ça interdit la création d'un type en dynamique
    Tu ne pourra utiliser que les types dérivés (selon mon exemple) de AbstractData, avec donc l'obligation de... créer un type correspondant à chaque fois (note que tu pourrais, aussi, envisager d'utiliser le type erasure sur cette hiérarchie )

    Sinon, avant de venir ici, j'avais posé la même question sur un forum, j'ai reçu deux suggestions :

    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
    class Base
    {
    public:
      template< class T >
      bool SetData( const std::vector< T >& t )
      {
        return SetData( static_cast< const void* >( &t ), typeid( t ) );
      }
     
    protected:
      virtual bool SetData( const void*, const std::type_info& ) = 0;
    };
     
    template< class T >
    class Derived : public Base
    {
    protected:
      bool SetData( const void* p, const std::type_info& info )
      {
        if( info == typeid( std::vector< T > ) )
        {
          const std::vector< T >& v = *static_cast< const std::vector< T >* >( p );
          //ok same type, this should work
          //do something with data here
          return true;
        }
        else
        {
          //not good, different types
          return false;
        }
      }
    };
    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
    class Base
    {
    public:
      template< class T >
      bool SetData(const std::vector<T>& data)
      {
        return SetData(&data,typeid(T));
      }
    private:
      virtual bool SetData(const void* data, const std::type_info& tid) = 0;
    }
     
    template< class T >
    class Derived : public Base
    {
    public:
      bool DoSetData(const std::vector<T>& data)
      {
        // tbd
      }
    private:
      virtual bool SetData(const void* data, const std::typeinfo& tid)
      {
        if( tid != typeid(T) )
          return false;
        const std::vector<T>* pdata = reinterpret_cast<const std::vector<T>*>(data);
        return DoSetData(*pdata);
      }
    }
    Je ne l'ai regardé qu'en diagonale, mais le simple fait d'utiliser un pointeur sur void ou un reinterpret_cast a déjà de quoi me faire douter très sérieusement

    Maintenant, pourrais tu expliquer exactement tes besoins, ceux qui t'ont mené à vouloir gérer une std::map<std::string, Base* >

    Peut être essaye tu simplement de faire cohabiter des choses qui n'auraient aucune raison de le faire
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  13. #13
    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
    Ceci dit, et bien que ce ne soit pas forcément l'idéal, une solution pourrait être de profiter d'un retour co-variant pour récupérer le type réel de l'objet:
    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 Base
    {
        public:
            virtual Base & realType() = 0;
            virtual Base const & realType() const = 0;
    };
    template <class T> 
    class Derived : publc Base
    {
        public:
            Derived & realType(){return dynamic_cast<Derived<T> &>(*this);}
            Derived const & realType() const
            {return dynamic_cast<Derived<T> const &>(*this);}
        /* le reste */
    };
    Mais bon, cela n'empêche que tu devra exactement savoir quel spécialisation de Derived<T> tu obtiendra, afin d'utiliser le type correct pour T
    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

  14. #14
    Membre habitué
    Profil pro
    Dev
    Inscrit en
    Mai 2009
    Messages
    257
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Dev

    Informations forums :
    Inscription : Mai 2009
    Messages : 257
    Points : 190
    Points
    190
    Par défaut
    Citation Envoyé par koala01 Voir le message
    Maintenant, pourrais tu expliquer exactement tes besoins, ceux qui t'ont mené à vouloir gérer une std::map<std::string, Base* >

    Peut être essaye tu simplement de faire cohabiter des choses qui n'auraient aucune raison de le faire
    Vous êtes familiers des API 3D (OpenGL, DirectX) ?

  15. #15
    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
    Citation Envoyé par coda_blank Voir le message
    Vous êtes familiers des API 3D (OpenGL, DirectX) ?
    Ne t'inquiètes pas trop de cela...

    En effet, bien que je ne les maitrise pas forcément, je les connais quand même un minimum, mais, surtout, je ne suis pas le seul intervenant du forum, malgré que j'ai été le seul intervenant avec toi jusqu'à présent

    Une fois que tu auras exprimé tes besoins réels, je pourrai (ou non) t'aider à envisager une autre solution, mais d'autres se sentiront peut être inspirés
    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 habitué
    Profil pro
    Dev
    Inscrit en
    Mai 2009
    Messages
    257
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Dev

    Informations forums :
    Inscription : Mai 2009
    Messages : 257
    Points : 190
    Points
    190
    Par défaut
    pour faire court, les API 3d réalisent un rendu graphique à partir de formats de données que l'on appelle vertices (vertex au singulier ) ou encore sommets

    Chaque vertex est composé de plusieurs attributs, c-a-d des ensembles de variables de même type;

    Par exemple l'attribut POSITION est un triplet de 3 float qui représentent la position du vertex dans l'espace

    Un autre attribut, COLOR est codé sur un simple unsigned long

    etc...

    la variété des types autorisés dépend de la version de l'API 3d

    Le nombre d'attributs peut aller de minimum 1 (il faut bien renseigner qqchose) à MAX, MAX étant la limite supportée par l'API 3d (20 pour OpenGL je crois)
    L'utilisateur nomme et choisit le type de ses attributs comme bon lui semble en veillant bien sûr à ce qu'ils soient traités de façon ad hoc par la suite

    Exemple de format de vertex :

    struct vertex{

    float x, y, z; //attribut POSITION
    unsigned long color;//attribut COLOR

    };

    Évidemment les combinaisons possibles sont très nombreuses donc il est difficile et peu flexible de les lister à l'avance

    Une fois que l'on a déterminé un format de vertex, on crée ses vertices et on les range dans un buffer (vector, tableau de taille fixe) que l'on peut donner à traiter à l'API; chaque buffer s'accompagne de quelques paramètres donc mieux vaut wrapper le tout dans une classe (Vertexbuffer)

    Il faut dès lors gérer tous ces vertexbuffers, personellement je l'ai fait avec un gestionnaire qui les collecte dans une map : d'ou le map<string, VertexBuffer<T>* > ou T est un format de vertex

    c'est là que je me retrouve coincé...

  17. #17
    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
    Merci, mais le bases que tu donnes, je les avais

    Cependant tu semble oublier deux points essentiels:

    1- Tu ne travaillera jamais qu'avec une API bien particulière
    2- Une fois que tu as défini les type primitifs qui serviront pour l'ensemble des types plus complexes, tu n'utilisera qu'eux.

    Tu peux donc parfaitement te contenter d'une approche purement générique, basée sur les politiques et les traits de politique

    Tout ce qu'il te faut, à la limite, ce sont des structures tempates qui travaillent "de concert":
    template <typename Axis, typename Color>
    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
    struct vertex
    {
        Axis x,y,z;
        Color color;
    };
    template <typename Axis>
    struct position
    {
        Axis x,y,z;
    };
    /*...*/
    template <typename Axis, typename Color /*, autre types nécessaires>
    class Policy
    {
        public:
        typedef typename vertex<Axis, Color> vertex_type;
        typedef typename position<Axis,> position_type;
        /* autres type utilisés */
    };
    Tu n'a, éventuellement, besoin du type erasure que si tu souhaite effectivement sélectionner "dynamiquement" l'api que tu veux utiliser, mais, une fois que c'est fait, cette api est facilement connue sur l'ensemble de ton projet, et tu peux donc assez facilement en récupérer le type réel une bonne fois pour toute et le manipuler partout où c'est nécessaire
    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

  18. #18
    Membre habitué
    Profil pro
    Dev
    Inscrit en
    Mai 2009
    Messages
    257
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Dev

    Informations forums :
    Inscription : Mai 2009
    Messages : 257
    Points : 190
    Points
    190
    Par défaut
    Merci, mais le bases que tu donnes, je les avais
    tant mieux

    Une fois que tu as défini les type primitifs qui serviront pour l'ensemble des types plus complexes, tu n'utilisera qu'eux.
    qu'entends tu par "type primitif" ? attribut de vertex (position, ...) ou type de donnée (float,...)

    Tu peux donc parfaitement te contenter d'une approche purement générique, basée sur les politiques et les traits de politique

    Tout ce qu'il te faut, à la limite, ce sont des structures tempates qui travaillent "de concert":
    template <typename Axis, typename Color>
    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
    struct vertex
    {
        Axis x,y,z;
        Color color;
    };
    template <typename Axis>
    struct position
    {
        Axis x,y,z;
    };
    /*...*/
    template <typename Axis, typename Color /*, autre types nécessaires>
    class Policy
    {
        public:
        typedef typename vertex<Axis, Color> vertex_type;
        typedef typename position<Axis,> position_type;
        /* autres type utilisés */
    };

    Axis et Color ne seraient donc que des redéfinitions de float et autres ? effectivement ça devient plus lisible

    par contre si j'ai 15 attribut dans un format,
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    template <typename Axis, typename Color /*, autre types nécessaires>
    va être un peu long, non ?

    maintenant qu'est-ce que ça peut donner avec mes vertexbuffers ?

  19. #19
    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
    Citation Envoyé par coda_blank Voir le message
    qu'entends tu par "type primitif" ? attribut de vertex (position, ...) ou type de donnée (float,...)
    Par type primitif, j'entends les types qui représentent exclusivement des valeurs numérique (int, float et autre double)
    Axis et Color ne seraient donc que des redéfinitions de float et autres ? effectivement ça devient plus lisible
    Exactement...

    Lorsque tu veux, par exemple, déclarer une variable de type vertex dont les coordonées x, y et z sont des doublet et dont la couleur est un entier non signé, tu travaille sous la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    vertex<double, unsigned int> var;
    Comme les positions utilisent (sans doute) un type identique pour représenter leurs coordonnées que les vertex, la classe policy nous donne l'occasion de définir, grâce aux différents typedef, les spécialisation pour l'ensemble des types qui t'intéressent.

    Tu pourrais, par exemple, créer ton propre typedef de cette policy sous une forme proche de et utiliser les alias de type qu'elle contient pour... représenter les type de tes différentes variables:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    /* un alias de type pour la facilité d'écriture */
    typedef policy<float, unsigned int, /* ...*/> policyAxisFloatColorInt;
     
    policyAxisFloatColorInt::vertex_type monVertex;
    policyAxisFloatColorInt::position_type maPosition;
    /*...*/
    par contre si j'ai 15 attribut dans un format,
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    template <typename Axis, typename Color /*, autre types nécessaires>
    va être un peu long, non ?
    On semble ne pas forcément parler le même langage : pour moi, un attribut est la valeur "color" ou la valeur de la coordonée x (ou y ou z) dans la structure vertex.

    Si tu as une structure qui utilise cinq attributs différents, mais que tous ces attributs doivent être d'un type identique, tu ne dois, bien évidemment, fournir qu'une fois le type que tu veux utiliser pour représenter cet attribut.

    Tu peux donc avoir vingt structures différentes, étant donné que les types permettant de représenter les différents attributs de ces classes sont fortement liés d'une classe à l'autre, tu peux te dire que dans ll'ensemble, tu ne va pas utiliser plus de trois ou quatre types primitifs différents pour représenter l'ensemble des attributs de ces différentes structures

    Par contre, il est vrai que, si tu as vingt structures différentes, tu aura vingt typedef dans ta structure "policy", qui sera une classe template nécessitant de préciser... trois ou quatre types différents maximum

    [/QUOTE]maintenant qu'est-ce que ça peut donner avec mes vertexbuffers ? [/QUOTE]Les vertexbuffer ne sont, pour faire simple, que des tableaux de vertices. Tu peux donc tout aussi bien décider, une fois que tu a l'alias de type pour policy qui te convient, d'écrire, tout simplement un code proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    /* un buffer de trois vertices  tels que définis avec le typedef 
     *policyAxisFloatColorInt
     */
    policyAxisFloatColorInt::vertex_type buffer[3];
    ou, si tu veux obtenir un buffer plus grand (et de taille pourquoi pas dynamique) sous une forme plus "C++iste" proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    std::vector<policyAxisFloatColorInt::vertex_type> buffer;
    Enfin, si tu veux pouvoir avoir un buffer de taille fixe mais définie en fonction des besoins, tu peux parfaitement envisager une structure proche de
    template <typename Axis, typename Color, size_t s>
    vertexbuffer
    {
    enum{size = s}; // pour pouvoir disposer de sa taille
    vertex<Axis,Color> vertices[s];
    };
    et, pour la facilité, modifier la classe policy sous une forme proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    template <typename Axis, typename Color, size_t s>
    class Policy
    {
        public:
        typedef typename vertex<Axis, Color> vertex_type;
        typedef typename position<Axis,> position_type;
        typedef typename vertexbuffer<vertex_type> vertexbuffer_type[s];
        /* autres type utilisés */
    };
    Ce ne sont là que quelques exemples de manière de faire, il y en a surement d'autres

    [EDIT] je me rend compte que j'oublie systématiquement ici de fournir des typedef adéquats dans toutes les structures afin de permettre à l'utilisateur de donner un type pour les différents attributs de celles-ci...

    La structure vertex serait donc plutôt proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    template <typename Axis, typename Color>
    struct  vertex
    {
        // l'alias de type pour les coordonnees
        typedef axis axis_type;
        // et celui pour la couleur
        typedef Color color_type;
        axis_type x, y, z;
        color_type color;
    };
    et les autres structures à l'avenant
    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

  20. #20
    Membre habitué
    Profil pro
    Dev
    Inscrit en
    Mai 2009
    Messages
    257
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Dev

    Informations forums :
    Inscription : Mai 2009
    Messages : 257
    Points : 190
    Points
    190
    Par défaut
    Tu pourrais, par exemple, créer ton propre typedef de cette policy sous une forme proche de et utiliser les alias de type qu'elle contient pour... représenter les type de tes différentes variables:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    /* un alias de type pour la facilité d'écriture */
    typedef policy<float, unsigned int, /* ...*/> policyAxisFloatColorInt;
     
    policyAxisFloatColorInt::vertex_type monVertex;
    policyAxisFloatColorInt::position_type maPosition;
    /*...*/

    Par contre, il est vrai que, si tu as vingt structures différentes, tu aura vingt typedef dans ta structure "policy", qui sera une classe template nécessitant de préciser... trois ou quatre types différents maximum
    il faudrait donc hard-coder les format de vertex dans les policy, et ce pour chaque policy particulière ? donc l'utilisateur ne peut définir aucun format de vertex ? ou bien alors c'est lui qui définit sa propre policy ? (je suis pas un aigle sur le pattern strategy, dsl)

    On semble ne pas forcément parler le même langage : pour moi, un attribut est la valeur "color" ou la valeur de la coordonée x (ou y ou z) dans la structure vertex.

    Si tu as une structure qui utilise cinq attributs différents, mais que tous ces attributs doivent être d'un type identique, tu ne dois, bien évidemment, fournir qu'une fois le type que tu veux utiliser pour représenter cet attribut.

    hum, pour moi un attribut est un ensemble de données partageant un même type primitif (axis, float, unsigned long...), et un format de vertex (ou structure) est un ensemble d'attributs, j'ai l'impression qu'on est pas d'accord sur ce schéma
    mais sinon j'ai compris pour les types spécifiés une seule fois en paramètre

    et, pour la facilité, modifier la classe policy sous une forme proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    template <typename Axis, typename Color, size_t s>
    class Policy
    {
        public:
        typedef typename vertex<Axis, Color> vertex_type;
        typedef typename position<Axis,> position_type;
        typedef typename vertexbuffer<vertex_type> vertexbuffer_type[s];
        /* autres type utilisés */
    };
    Ce ne sont là que quelques exemples de manière de faire, il y en a surement d'autres
    donc carrément définir le type du vertexbuffer à l'avance dans la policy ?

    bon, mettons

    maintenant je veux stocker un ensemble de vertexbuffer dans une map

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    map<string, VertexBuffer<T> >
     
    template<T>
    class VertexBuffer{
     
          private : 
     
               //membres divers
     
               vector<T> buffer; //?
     
    }
    je dois tous les spécifier avec le même format de vertex ? je ne peux pas regrouper des buffers contenant des formats différents au sein d'une même collection ?

Discussions similaires

  1. Contourner le Type Erasure des collections
    Par scheme dans le forum Langage
    Réponses: 4
    Dernier message: 18/02/2011, 16h00
  2. Réponses: 29
    Dernier message: 20/09/2009, 06h27
  3. utilisation du meta type ANY
    Par Anonymous dans le forum CORBA
    Réponses: 1
    Dernier message: 15/04/2002, 12h36

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