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 :

[Optimisation] Votre avis sur ma bibliothèque Multiple Dispatch


Sujet :

Langage C++

  1. #1
    Membre du Club
    Homme Profil pro
    C++
    Inscrit en
    Janvier 2013
    Messages
    45
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : C++

    Informations forums :
    Inscription : Janvier 2013
    Messages : 45
    Points : 44
    Points
    44
    Par défaut [Optimisation] Votre avis sur ma bibliothèque Multiple Dispatch
    Bonjour a tous,
    J'ai programmé une bibliothèque c++ dans le but d'alléger la mise en oeuvre d'un Multiple Dispatch en utilisant un pattern Visitor simple (pas de RTTI)

    Le principe est le suivant :
    Chaque classe de la hiérarchie implémente le mécanisme accept(AVisitor& v), ma bibliothèque s'en sert pour permettre le multiple dispatch.

    Vous trouverez ci-joint la bibliothèque (et un header de compatibilité c++1y), ainsi que deux fichiers .cpp d'exemple.
    Par commodité, vous trouverez également ci dessous le fichier source de la bibliothèque ainsi qu'un des deux fichiers d'exemple.

    Qu'en pensez-vous ? Toutes remarques et tous conseils sont les bienvenus !
    Merci d'avance

    EDIT : Je met les codes à jour au fur et à mesure des corrections. Dernière mise a jour : 17.09.14

    Fichier joint : Multiple Dispatch Release v1.1.zip
    Un copier-coller de code multiple_dispatch.h :

    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
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
     
    #ifndef Multiple_Dispatch_Via_Visitor_Wrapper_h
    #define Multiple_Dispatch_Via_Visitor_Wrapper_h
    /************************************************************************
     Project : Multiple Dispatch Wrappers
     File    : multiple_dispatch.h ,
               Single header, sans fonctionnalité avancée
     
     Author  : HARBULOT Julien
     
     Copyright (C) 2014 HARBULOT Julien Edmond René
     
     This program is free software: you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
     the Free Software Foundation, either version 3 of the License, or
     (at your option) any later version.
     
     This program is distributed in the hope that it will be useful,
     but WITHOUT ANY WARRANTY; without even the implied warranty of
     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     GNU General Public License for more details.
     
     You should have received a copy of the GNU General Public License
     along with this program.  If not, see <http://www.gnu.org/licenses/>.
     
     email : info [at] julienh.fr
     
     ************************************************************************
     * Système de multiple dispatch à N arguments (pour tout N > 0)
     *
     * Utilisation :
     *  Chaque classe dont on voudra reconnaître le type implémente le
     *  pattern visitor [aka : elle possède une methode accept(...) ]
     *  Les wrappers prennent un nombre quelconque d'arguments et une action.
     *  Ils s'occupent de retrouver le type de tous les arguments via leurs
     *  méthodes accept(...), puis apellent l'action demandée.
     *
     * Version : 4 - std::tuple
     
     ************************************************************************/
     
     
    #include <tuple>
     
    //========================================================================
    //================================ LIBRARY ===============================
    //========================================================================
    namespace dispatch{
    namespace multiple_dispatch_impl{
     
        //====================================================================
        //====================================================================
        //
        // === Family ===
        //
     
        // A partir du type BaseClass, un dispatcher doit retrouver le bon type dérivé.
        // Pour cela, il doit connaitre la liste des types dérivés (DerivedList)
        // Et il doit pouvoir visiter la hiérarchie en héritant de VisitorBase
        // Ces données constituent une famille :
     
        template <class VisitorBase, class BaseClass, class... DerivedList>
        struct Family{
            template <class... Ts> struct L;
     
            using Visitor = VisitorBase;
            using Base = BaseClass;
            using Deriveds = L<DerivedList...>;
            //using ToList = L<VisitorBase, BaseClass, DerivedList...>;
        };
     
        //====================================================================
        //====================================================================
        //
        // === Multiple Dispatch class ===
        //
     
        template
        <
        class Action, class Data_t,
        size_t nFamilyToProcess, size_t curFamilyIndex, // MaxN et CurN
        class... Families
        >
        struct MultiDisp;
     
        //
        // On écrit : methodeGenerique(BaseClass1* d1, BaseClass2* d2, BaseClass3* d3, ...)
        // Et le MultiDisp appelle : ActionSpecifique(Derivee1A* d1a, Derivee2A*, d2a, Derivee3B*, d3b)
        //
        // Pour ce faire, le MultiDisp doit retrouver le type dérivé de ses nFamilyToProcess arguments.
        // Or, pour retrouver un type dérivé au sein d'une hiérarchie (Family), le MultiDisp
        //   implémente les fonctions visit() d'un curVisitor dont il hérite, action pour laquelle il doit connaitre
        //   toutes les classes dérivées possibles (DerivedList) de la hiérarchie.
     
        // Principe de résolution :
        // On a des pointeurs vers les classes mères d'une part (dans Data_t).
        // On a une liste de familles d'autre part (dans le parameter_pack).
        //
        // Le type des pointeurs ne change qu'au dernier moment (Data_t ne change pas).
        // Ce qui change, c'est notre parameter_pack qui fonctionne comme une file :
        // On enlève une famille à traiter au début (Step 1) et on ajoute le type trouvé a la fin (Step 2).
        // Ensuite, il suffit de caster chaque pointeur vers le type trouvé qui lui correspond (Condition d'arrêt).
        //
        // Pour savoir où on en est dans la file, on utilise deux index : CurN (index courant) et MaxN (nb familles)
        // Lorsque CurN == MaxN, on a traité toute la file
        //    et le parameter_pack ne contient plus que les types trouvés.
     
        // Nomenclature :
        // Une famille est representée par un parameter_pack de la facon suivante :
        // Family<curVisitor , curBase , DerivedList...>
        // L'avantage d'une telle représentation est de pouvoir utiliser le filtrage de motif sur DerivedList :
        // Family<DerivedList...> = Family<DerivedHead, DerivedTail...>
     
        //Step 1 :
        // On itère sur les derivées de la famille F1 pour implémenter les fonctions visit(...)
        // A chaque étape, la liste <DerivedTail...> décroit.
     
        template
        <
        class A, class D_t,
        size_t MaxN, size_t CurN,
        class curVisitor, class curBase, class DerivedHead, class... DerivedTail,
        class... otherFamilies
        >
     
        struct MultiDisp<A, D_t, MaxN, CurN, Family<curVisitor, curBase, DerivedHead, DerivedTail...> , otherFamilies...>
        : public MultiDisp<A, D_t, MaxN, CurN, Family<curVisitor, curBase, DerivedTail...>, otherFamilies...> //on enleve DHead
        {
        protected:
            using Parent = MultiDisp<A, D_t, MaxN, CurN, Family<curVisitor, curBase, DerivedTail...>, otherFamilies...>;
     
        public:
            using Parent::Parent;
            using Parent::visit;
            void visit(DerivedHead*){
                // On arrive dans cette fonction après avoir déclanché la visite (Step 2).
                // A ce stade, on a trouvé le type du pointeur correspondant à la famille CurN.
                // Le type trouvé (DerivedHead) est alors stocké à la fin de la file.
                // La recherche se poursuit pour les familles suivantes.
                // (Rappel : cas d'arrêt quand MaxN = CurN, (cf Condition d'arret)
                MultiDisp<A, D_t, MaxN, CurN+1, otherFamilies..., DerivedHead> next{ Parent::data };
                next.action();
            }
        };
     
        //Step 2 :
        // Fin de l'itération sur la famille courante : plus de dérivées.
        // On va utiliser le pattern visitor pour trouver le bon type dérivé.
        // Pour ce faire, on hérite du bon VisitorBase (curVisitorBase) et on visite le
        //    bon pointeur (dont on connait l'index CurN).
        //
        // La procédure se poursuit quand on arrive dans la bonne fonction visit(DerivedHead*)
        //    (voir plus haut).
        template
        <
        class A, class Data_t,
        size_t MaxN, size_t CurN,
        class curVisitorBase, class curBase,
        class... otherFamilies
        >
     
        struct MultiDisp<A, Data_t, MaxN, CurN, Family<curVisitorBase, curBase> , otherFamilies...>
        : public curVisitorBase
        {
        protected:
            Data_t const& data;
        public:
            MultiDisp(Data_t const& data) : data(data) {}
            void action(){
                std::get<CurN>(data)->accept(this);
            }
        };
     
        //Condition d'arrêt :
        // Quand CurN == MaxN, toutes les familles ont été visitées.
        // Comme à chaque fois les types trouvés ont été placés à la fin de la file,
        //    le parameter_pack <Families...> est devenu <KnownTypes...>.
        // Il nous reste à appeler l'action sur tous les arguments, après
        //    les avoir castés vers le bon type.
        template
        <
        class Action, class Data_t,
        size_t nbTypes,
        class... KnownTypes
        >
        struct MultiDisp<Action, Data_t, nbTypes, nbTypes, KnownTypes...>{
        protected:
            Data_t const& data;
        public:
            MultiDisp(Data_t const& data) : data(data) {}
     
            void action(){
                actionHelper(std::make_index_sequence<nbTypes>{});
            }
        private:
            template <size_t... I>
            void actionHelper(std::index_sequence<I...>){
                Action action;
                action(dynamic_cast<KnownTypes*>(std::get<I>(data))...); //@TODO
            }
        };
     
     
        // ===================================
        // Interface / gestion des pointeurs :
        //
        // Les pointeurs sont gérés par un std::tuple<BaseClass*...>
        // Pour éviter la duplication, c'est cette classe qui garde les pointeurs en mémoire.
        // On s'occupe aussi d'initialiser les index.
     
        template <class Action, class... Families>
        struct Dispatcher{
        private:
            using Data_t = std::tuple<typename Families::Base* ...>;
            using DispatcherImpl = MultiDisp<Action, Data_t, sizeof...(Families), 0, Families...>;
     
            Data_t data;
        public:
            Dispatcher(Action* a, typename Families::Base* ... to_solve)
            : data{to_solve...} {
            }
            void operator()(){
                DispatcherImpl{data}.action();
            }
        };
     
    } //namespace implementation
     
    using multiple_dispatch_impl::Family;
    using multiple_dispatch_impl::Dispatcher;
     
    } //namespace public
     
    #endif // defined Multiple_Dispatch_Via_Visitor_Wrapper_h */
    Un copier-coller de : exemple_simple.cpp
    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
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
     
     
    //
    // Exemple d'utilisation simple de la bibliothèque MultipleDistpach
    // Auteur : Julien Harbulot
    //
     
    #include <iostream>
     
    #include "cpp11compatibility.h" //delete if c++1y
    #include "multiple_dispatch.h"
     
    using namespace std;
     
    //===========================================================
    // Utilisation basique de la bibliothèque MultipleDispatch.
    //
    //
    // Dans cet exemple, nous allons effectuer un double dispatch (Note : Il est tout aussi simple d'effectuer un triple dispatch, ou plus)
    //
    // On possède une collection d' Animal* (Chat*, Chien*)
    // On veut qu'il puissent s'attaquer entre eux en ayant des
    //   comportements différents selon le type de l'attaquant
    //   et de l'attaqué.
    //
    // Pour cela, on définit les comportements voulus dans une
    //   classe ActionAttaquer.
    // Par exemple : ActionAttaquer::operator()(Chat*, Chat*){}
    //
    // Ensuite on crée une fonction (ou une méthode) qui prend
    //  en argument deux Animal* puis qui appelle le dispatcher,
    //  et enfin l'action sur les types retrouvés.
    //
    // Pour pouvoir retrouver les bons types, il faut indiquer
    // à la bibliotheque sur quelle familles on veut travailler.
    //
    // Une famille est constituée :
    // - d'une interface de Visiteur (ex : AnimalVisitor)
    // - d'une classe Mère (Animal)
    // - de types dérivés (Chat, Chien)
    //
    // Par exemple, pour créer la famille Animaux :
    //
    // using Animaux
    // = dispatch::Family<AnimalVisitor, Animal, Chat, Chien>;
     
     
     
    //===========================================================
    // Dans cette partie, tout est classique :
    //
    // On crée une hierarchie classique (avec pattern Visitor)
    //    Chat et Chien héritent de Animal
    //    Les classes implémentent : void accept(AnimalVisitor&)
    //
     
    class Animal;
    class Chat;
    class Chien;
     
    class AnimalVisitor{
    public:
        virtual void visit(Chat* c) = 0;
        virtual void visit(Chien* c) = 0;
    };
     
    class Animal{
    public:
        virtual void accept(AnimalVisitor* v) = 0;
        virtual ~Animal(){}
    };
     
    class Chat : public Animal{
    public:
        virtual void accept(AnimalVisitor* v){ v->visit(this); }
    };
     
    class Chien : public Animal{
    public:
        virtual void accept(AnimalVisitor* v){ v->visit(this); }
    };
     
    //===========================================================
    // Ici on utilise la bibliothèque :
    //
    // On crée une action qui nécessite un multiple dispatch
    // On l'enregistre auprès d'un multiple dispatcher
    // Nombre de lignes pour utiliser la bibliotheque : 3 lignes.
     
    class ActionAttaquer{
    public:
        void operator()(Chat* lhs, Chat* rhs) { cout <<  "griffe - griffe" << endl;}
        void operator()(Chat* lhs, Chien* rhs){ cout <<  "griffe - mord  " << endl;}
        void operator()(Chien* lhs, Chat* rhs){ cout <<  "mord   - griffe" << endl;}
        void operator()(Chien* lhs, Chien* rhs){ cout << "mord   - mord  " << endl;}
     
        void operator()(Animal* a1, Animal* a2){
            // D'abord on précise sur qui l'on veut effectuer le dispatch
            using Animaux = dispatch::Family<AnimalVisitor, Animal, Chat, Chien>;
     
            // Puis on appelle le Dispatcher
            using Dispatcher = dispatch::Dispatcher<ActionAttaquer, Animaux, Animaux>;
            Dispatcher(this, a1, a2)();
        }
    };
     
    //===========================================================
    // fonction main(), on crée des Animal* et on essaie le
    // double dispatch.
     
    int main()
    {
        cout << "Debut." << endl;
     
        Chat chat;
        Chien chien;
     
        Animal* a1 = &chat;
        Animal* a2 = &chien;
     
        ActionAttaquer attaquer;
        attaquer(a1, a2);
        attaquer(a2, a2);
     
        cout << "Fin." << endl;
        return 0;
    }
    [EDIT] Voici comment effectuer un triple dispatch :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     
    void operator()(Animal* a1, Animal* a2, Animal* a3){
            // D'abord on précise sur qui l'on veut effectuer le dispatch
            using Animaux = dispatch::Family<AnimalVisitor, Animal, Chat, Chien>;
     
            // Puis on appelle le Dispatcher
            using Dispatcher = dispatch::Dispatcher<ActionAttaquer, Animaux, Animaux, Animaux>; //ajout ici
            // Remarque : On peut utiliser trois familles différentes si on veut.
     
            Dispatcher(this, a1, a2, a3)();
        }

  2. #2
    Expert éminent sénior
    Avatar de Luc Hermitte
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2003
    Messages
    5 275
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Août 2003
    Messages : 5 275
    Points : 10 985
    Points
    10 985
    Par défaut
    Je n'ai pas trop le temps de rentrer dans les détails (les identifiants de 3 lettres n'aident pas à la relecture), mes remarques sont donc presque toutes superficielles.

    - Pour la doc, je ne peux que conseiller d'utiliser un standard de-facto. Typiquement le format de Doxygen
    - Evite de mélanger anglais et français dans les identifiants
    - Et surtout : pourquoi des pointeurs ? Ils n'apportent pas la garantie de non nullité que tu sembles exiger pour ne pas planter => références.
    - Si je comprends bien, tu ne fais pas du multi-dispatch, mais juste du double dispatch (le visiteur ne permet pas d'augmenter l'arité au delà de 2). Pour plus, il y a un chapitre de Modern C++ Design qui est dédié à la question -- où il y a abus de RTTI dans mes souvenirs.

    Bon maintenant, si tu publies, j'en déduis que ça marche, donc c'est cool!
    D'ailleurs, comment gères-tu la const-correctness ? Ca passe toujours ?
    Blog|FAQ C++|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS|Bons livres sur le C++
    Les MP ne sont pas une hotline. Je ne réponds à aucune question technique par le biais de ce média. Et de toutes façons, ma BAL sur dvpz est pleine...

  3. #3
    Membre du Club
    Homme Profil pro
    C++
    Inscrit en
    Janvier 2013
    Messages
    45
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : C++

    Informations forums :
    Inscription : Janvier 2013
    Messages : 45
    Points : 44
    Points
    44
    Par défaut
    Citation Envoyé par Luc Hermitte Voir le message
    (les identifiants de 3 lettres n'aident pas à la relecture)
    J'ai longtemps hésité pour trouver les noms actuels.
    Le problème face auquel j'ai été confronté est que les noms plus significatifs sont plus longs. Au regard du grand nombre de variables, cela obscurcissait le code...
    Voici les idées que j'ai eu, il est toujours temps de changer :

    Base Visitor de la famille (F1V) : F1VisitorBase, F1Visitor
    BaseClass de la famille 1 (F1B) : F1Base
    Dérivée 1 de la famille 1 (F1D1) : F1Head, curDerived
    Autres dérivées de la famille 1 (F1Ds) : F1Tail, Deriveds, otherDeriveds
    Autres familles : FamilyTail, otherFamily, Familys

    Citation Envoyé par Luc Hermitte Voir le message
    - Et surtout : pourquoi des pointeurs ? Ils n'apportent pas la garantie de non nullité que tu sembles exiger pour ne pas planter => références.
    Utiliser des références serait beaucoup mieux en effet.
    Il faut vérifier (1) que ca n'introduit pas de copie supplémentaire les des casts, (2) que c'est compatible avec le stockage dans un std::tuple, (3) le const correctness (je demande des references constantes, ou pas ?).

    Citation Envoyé par Luc Hermitte Voir le message
    - Si je comprends bien, tu ne fais pas du multi-dispatch, mais juste du double dispatch.
    Si ! Multiple dispatch made easy.
    Voici comment faire un triple dispatch, c'est tout aussi simple que pour le double.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     
    void operator()(Animal* a1, Animal* a2, Animal* a3){
            // D'abord on précise sur qui l'on veut effectuer le dispatch
            using Animaux = dispatch::Family<AnimalVisitor, Animal, Chat, Chien>;
     
            // Puis on appelle le Dispatcher
            using Dispatcher = dispatch::Dispatcher<ActionAttaquer, Animaux, Animaux, Animaux>; //ajout ici
            // Remarque : On peut utiliser trois familles différentes si on veut.
     
            Dispatcher(this, a1, a2, a3);
        }
    Citation Envoyé par Luc Hermitte Voir le message
    - Pour la doc, je ne peux que conseiller d'utiliser un standard de-facto. Typiquement le format de Doxygen
    - Evite de mélanger anglais et français dans les identifiants
    Il faut que je m'occupe de ça.

  4. #4
    Expert éminent sénior

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 189
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Essonne (Île de France)

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

    Informations forums :
    Inscription : Juin 2007
    Messages : 5 189
    Points : 17 141
    Points
    17 141
    Par défaut
    L'avantage du dispatch est de ne pas indiquer les types cibles.
    Préciser la famille casse un peu tout l'intérêt.

    Ou alors j'ai rien compris, ce qui est tout à fait possible
    Mes principes de bases du codeur qui veut pouvoir dormir:
    • Une variable de moins est une source d'erreur en moins.
    • Un pointeur de moins est une montagne d'erreurs en moins.
    • Un copier-coller, ça doit se justifier... Deux, c'est un de trop.
    • jamais signifie "sauf si j'ai passé trois jours à prouver que je peux".
    • La plus sotte des questions est celle qu'on ne pose pas.
    Pour faire des graphes, essayez yEd.
    le ter nel est le titre porté par un de mes personnages de jeu de rôle

  5. #5
    Membre du Club
    Homme Profil pro
    C++
    Inscrit en
    Janvier 2013
    Messages
    45
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : C++

    Informations forums :
    Inscription : Janvier 2013
    Messages : 45
    Points : 44
    Points
    44
    Par défaut
    Citation Envoyé par leternel Voir le message
    L'avantage du dispatch est de ne pas indiquer les types cibles.
    Préciser la famille casse un peu tout l'intérêt.

    Ou alors j'ai rien compris, ce qui est tout à fait possible

    Bonjour les familles n'ont pas de rapport avec le multiple dispatch en lui meme, mais avec le pattern visitor.
    Elles precisent une liste de types autorisés (ceux pour lesquels on a programmé un comportement spécifique). Le pattern visitor permet de retrouver le bon type dérivé dans cette liste.

    Pour parler de facon plus technique, le pattern visitor impose que l'on implemente une methode visit(D*) pour chaque type dérivé D possible. Les familles servent seulement a indiquer a la bibliotheque pour quels types il faut implementer une telle fonction.

    Les familles sont un raccoursi d'ecriture qui n'impose aucune contrainte supplémentaire.

    Cordialement,
    JH

  6. #6
    En attente de confirmation mail

    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2004
    Messages
    1 391
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 33
    Localisation : France, Doubs (Franche Comté)

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

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Points : 3 311
    Points
    3 311
    Par défaut
    Quelques remarques (en plus de celles faites par Luc, en particulier celles des pointeurs) :
    • C'est plus probablement un dynamic_cast qu'il faut faire.
    • Ajoutes un using Parent::visit;, c'est pas strictement nécessaire dans ton code, mais ça évite un warning et c'est plus cohérent.
    • Utilises directement Parent::data à la place de réimporter le symbole dans la protée publique.
    • Utilises les portées protected/private.
    • Dispatcher(this, a1, a2); me gène un peu. Quand je vois un objet temporaire être construit et tout de suite détruit, j'ai envie d'enlever la ligne. Les effets de bord dans les constructeurs/destructeurs ça apporte souvent des surprises. Tu pourrais passer l'appel à action dans un operator(). Ce qui donnerait : Dispatcher(this, a1, a2)(); à l'utilisation.


    @Luc: En faite il fait bien un pattern visiteur, mais pas au sens où tu l'entends habituellement. Classiquement tu écris directement ce que tu veux faire dans tes fonction visit alors qu'ici tu écris un foncteur et son framework va écrire tout seul la hiérarchie du visiteur pour qu'au final il transmette l'appel au foncteur avec les bon types.

  7. #7
    Invité
    Invité(e)
    Par défaut
    Salut, ceci me semble assez compliqué, les forward déclaration ici ne te posent pas de problèmes ?

    Code cpp : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
    class Animal;
    class Chat;
    class Chien;
     
    class AnimalVisitor{
    public:
        virtual void visit(Chat* c) = 0;
        virtual void visit(Chien* c) = 0;
    };

    J'avais essayé aussi de stocker les types dérivés dans un tuple, le problèmes c'est que ça mène souvent à des erreurs du style :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    Invalid use of incomplete type ...
    Je suis parti sur un système utilisant du RTTI, plutôt que de faire un multiple dispatch qui :

    -n'est pas pratique dans le cas ou il peut y avoir beaucoup de classe dérivées et beaucoup de classes de bases. (Même si tu génères la hiérarchie de manière automatique, le nombre de méthodes visit à rajouté et le nombre de familles à créer peut devenir vite gênant, j'ai vite donc laissé tombé ce pattern pour utilisé un système avec de RTTI.)
    -permet d'utiliser des fonctions template et faire un système de fonction virtuelle template en enregistrant chaque spécialisation de la fonction template pour tout les types dérivé dans une factory. (Si héritage multiple il y a alors chaque classe de base possède sa propre factory ce qui m'évite de devoir créer une interface, et une nouvelle famille à chaque fois, le truc le plus lourd qu'il me reste à faire est juste d'enregistrer tout dans la factory mais, c'est déjà beaucoup plus rapide.)

    Bref j'ai lu pas mal d'articles sur la génération automatique de hiérarchie, mais, je trouvais que il fallait écrire encore trop de code. (Définition de types listes et créations d'interfaces à la main + définition des actions pour toues les familles à la main.)

    Dans ma librairie je n'ai juste qu'à définir les actions pour les différents familles. (je ne peux pas faire moins à cause de l'absence d'un système de réflexion en c++ similaire à celui du langage java ou là je n'aurait même pas eu besoin d'enregistrer tout dans la factory à la main)

    Ta bibliothèque conviendrait donc dans le cadre de petites hiérarchie, mais je ne l'utiliserai pas pour coder un rpg par exemple.

    boost variant et le pattern visitor résultant plutôt à des erreurs de conception et un manque total de souplesse dans mon cas.

    Maintenant, un système avec boost::any et des states (ce que j'utilise aussi dans ma librairie si je dois en plus, annuler un comportement) pourrait convenir aussi afin d'éviter d'avoir trop d'héritage et de s'embrouiller ce qui m'arrivait souvent avec du double dispatch.

    Perso je n'aime pas ce pattern et je ne l'utilise jamais.

  8. #8
    Membre du Club
    Homme Profil pro
    C++
    Inscrit en
    Janvier 2013
    Messages
    45
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : C++

    Informations forums :
    Inscription : Janvier 2013
    Messages : 45
    Points : 44
    Points
    44
    Par défaut
    Bonjour Lolilolight,
    Tu critiques le pattern visitor ( mais pas le double dispatch ) et tu dis préférer faire un double dispatch en utilisant le RTTI plutôt qu'en utilisant les visitors, comme l'a proposé la bibliothèque Loki.
    Il est vrai que le pattern visitor est souvent critiqué à cause de la rigidité qu'il impose sur les hiérarchies qui le mettent en place.

    Mon objectif était de proposer un système de Multiple Dispatch n'utilisant pas le RTTI.
    Pour l'instant, seul le code correspondant au méthodes visit() est simplifié, mais j'ai de bons espoirs d'arriver à simplifier le code correspondant aux forward declarations + méthodes accept() dans un future proche. Néanmoins ceci sera un autre projet.

    Un avantage certain du pattern visitor face au RTTI, c'est qu'il est beaucoup plus rapide à l'exécution. Par exemple, face à la bibliothèque Loki, dans mes tests ma bibliothèque est 10x plus rapide.

    Au sujet des RPG, connais-tu les systèmes entity-component ? Cette conception me semble approprié aux jeux. Mais bon, on s'écarte du sujet initial du topic

    Cordialement,
    JH

  9. #9
    Membre du Club
    Homme Profil pro
    C++
    Inscrit en
    Janvier 2013
    Messages
    45
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : C++

    Informations forums :
    Inscription : Janvier 2013
    Messages : 45
    Points : 44
    Points
    44
    Par défaut
    Bonjour,
    Mise à jour pour tenir compte des points suivants :
    Citation Envoyé par Flob90 Voir le message
    • C'est plus probablement un dynamic_cast qu'il faut faire.
    • Ajoutes un using Parent::visit; [...]
    • Utilises directement Parent::data à la place de réimporter le symbole dans la protée publique.
    • Utilises les portées protected/private.
    • Dispatcher(this, a1, a2); me gène un peu. [...]
    Citation Envoyé par Luc Hermitte
    - Evite de mélanger anglais et français dans les identifiants
    Reste à s'assurer que je l'ai fait correctement.

    Il faut encore que je m'occupe de changer les pointeurs en références, et que je change ma conception pour éviter le dynamic_cast.
    Une solution serait d'enregistrer la bonne variable dérivée quand on la reçoit dans la méthode visit(), mais cela soulève deux questions, un peu d'aide sera la bienvenue :

    1. Comment changer une seule variable et son type dans un std::tuple sans avoir à tout recopier ?
    2. Comment mettre des references dans un std::tuple, et comment gérer la const correctness pour ces références ?


    Encore un fois, merci beaucoup pour vos précieuses remarques, et conseils !


    Un copier-coller du header mis à jour :

    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
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
     
    #ifndef Multiple_Dispatch_Via_Visitor_Wrapper_h
    #define Multiple_Dispatch_Via_Visitor_Wrapper_h
    /************************************************************************
     Project : Multiple Dispatch Wrappers
     File    : multiple_dispatch.h ,
               Single header, sans fonctionnalité avancée
     
     Author  : HARBULOT Julien
     
     Copyright (C) 2014 HARBULOT Julien Edmond René
     
     This program is free software: you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
     the Free Software Foundation, either version 3 of the License, or
     (at your option) any later version.
     
     This program is distributed in the hope that it will be useful,
     but WITHOUT ANY WARRANTY; without even the implied warranty of
     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     GNU General Public License for more details.
     
     You should have received a copy of the GNU General Public License
     along with this program.  If not, see <http://www.gnu.org/licenses/>.
     
     email : info [at] julienh.fr
     
     ************************************************************************
     * Système de multiple dispatch à N arguments (pour tout N > 0)
     *
     * Utilisation :
     *  Chaque classe dont on voudra reconnaître le type implémente le
     *  pattern visitor [aka : elle possède une methode accept(...) ]
     *  Les wrappers prennent un nombre quelconque d'arguments et une action.
     *  Ils s'occupent de retrouver le type de tous les arguments via leurs
     *  méthodes accept(...), puis apellent l'action demandée.
     *
     * Version : 4 - std::tuple
     
     ************************************************************************/
     
     
    #include <tuple>
     
    //========================================================================
    //================================ LIBRARY ===============================
    //========================================================================
    namespace dispatch{
    namespace multiple_dispatch_impl{
     
        //====================================================================
        //====================================================================
        //
        // === Family ===
        //
     
        // A partir du type BaseClass, un dispatcher doit retrouver le bon type dérivé.
        // Pour cela, il doit connaitre la liste des types dérivés (DerivedList)
        // Et il doit pouvoir visiter la hiérarchie en héritant de VisitorBase
        // Ces données constituent une famille :
     
        template <class VisitorBase, class BaseClass, class... DerivedList>
        struct Family{
            template <class... Ts> struct L;
     
            using Visitor = VisitorBase;
            using Base = BaseClass;
            using Deriveds = L<DerivedList...>;
            //using ToList = L<VisitorBase, BaseClass, DerivedList...>;
        };
     
        //====================================================================
        //====================================================================
        //
        // === Multiple Dispatch class ===
        //
     
        template
        <
        class Action, class Data_t,
        size_t nFamilyToProcess, size_t curFamilyIndex, // MaxN et CurN
        class... Families
        >
        struct MultiDisp;
     
        //
        // On écrit : methodeGenerique(BaseClass1* d1, BaseClass2* d2, BaseClass3* d3, ...)
        // Et le MultiDisp appelle : ActionSpecifique(Derivee1A* d1a, Derivee2A*, d2a, Derivee3B*, d3b)
        //
        // Pour ce faire, le MultiDisp doit retrouver le type dérivé de ses nFamilyToProcess arguments.
        // Or, pour retrouver un type dérivé au sein d'une hiérarchie (Family), le MultiDisp
        //   implémente les fonctions visit() d'un curVisitor dont il hérite, action pour laquelle il doit connaitre
        //   toutes les classes dérivées possibles (DerivedList) de la hiérarchie.
     
        // Principe de résolution :
        // On a des pointeurs vers les classes mères d'une part (dans Data_t).
        // On a une liste de familles d'autre part (dans le parameter_pack).
        //
        // Le type des pointeurs ne change qu'au dernier moment (Data_t ne change pas).
        // Ce qui change, c'est notre parameter_pack qui fonctionne comme une file :
        // On enlève une famille à traiter au début (Step 1) et on ajoute le type trouvé a la fin (Step 2).
        // Ensuite, il suffit de caster chaque pointeur vers le type trouvé qui lui correspond (Condition d'arrêt).
        //
        // Pour savoir où on en est dans la file, on utilise deux index : CurN (index courant) et MaxN (nb familles)
        // Lorsque CurN == MaxN, on a traité toute la file
        //    et le parameter_pack ne contient plus que les types trouvés.
     
        // Nomenclature :
        // Une famille est representée par un parameter_pack de la facon suivante :
        // Family<curVisitor , curBase , DerivedList...>
        // L'avantage d'une telle représentation est de pouvoir utiliser le filtrage de motif sur DerivedList :
        // Family<DerivedList...> = Family<DerivedHead, DerivedTail...>
     
        //Step 1 :
        // On itère sur les derivées de la famille F1 pour implémenter les fonctions visit(...)
        // A chaque étape, la liste <DerivedTail...> décroit.
     
        template
        <
        class A, class D_t,
        size_t MaxN, size_t CurN,
        class curVisitor, class curBase, class DerivedHead, class... DerivedTail,
        class... otherFamilies
        >
     
        struct MultiDisp<A, D_t, MaxN, CurN, Family<curVisitor, curBase, DerivedHead, DerivedTail...> , otherFamilies...>
        : public MultiDisp<A, D_t, MaxN, CurN, Family<curVisitor, curBase, DerivedTail...>, otherFamilies...> //on enleve DHead
        {
        protected:
            using Parent = MultiDisp<A, D_t, MaxN, CurN, Family<curVisitor, curBase, DerivedTail...>, otherFamilies...>;
     
        public:
            using Parent::Parent;
            using Parent::visit;
            void visit(DerivedHead*){
                // On arrive dans cette fonction après avoir déclanché la visite (Step 2).
                // A ce stade, on a trouvé le type du pointeur correspondant à la famille CurN.
                // Le type trouvé (DerivedHead) est alors stocké à la fin de la file.
                // La recherche se poursuit pour les familles suivantes.
                // (Rappel : cas d'arrêt quand MaxN = CurN, (cf Condition d'arret)
                MultiDisp<A, D_t, MaxN, CurN+1, otherFamilies..., DerivedHead> next{ Parent::data };
                next.action();
            }
        };
     
        //Step 2 :
        // Fin de l'itération sur la famille courante : plus de dérivées.
        // On va utiliser le pattern visitor pour trouver le bon type dérivé.
        // Pour ce faire, on hérite du bon VisitorBase (curVisitorBase) et on visite le
        //    bon pointeur (dont on connait l'index CurN).
        //
        // La procédure se poursuit quand on arrive dans la bonne fonction visit(DerivedHead*)
        //    (voir plus haut).
        template
        <
        class A, class Data_t,
        size_t MaxN, size_t CurN,
        class curVisitorBase, class curBase,
        class... otherFamilies
        >
     
        struct MultiDisp<A, Data_t, MaxN, CurN, Family<curVisitorBase, curBase> , otherFamilies...>
        : public curVisitorBase
        {
        protected:
            Data_t const& data;
        public:
            MultiDisp(Data_t const& data) : data(data) {}
            void action(){
                std::get<CurN>(data)->accept(this);
            }
        };
     
        //Condition d'arrêt :
        // Quand CurN == MaxN, toutes les familles ont été visitées.
        // Comme à chaque fois les types trouvés ont été placés à la fin de la file,
        //    le parameter_pack <Families...> est devenu <KnownTypes...>.
        // Il nous reste à appeler l'action sur tous les arguments, après
        //    les avoir castés vers le bon type.
        template
        <
        class Action, class Data_t,
        size_t nbTypes,
        class... KnownTypes
        >
        struct MultiDisp<Action, Data_t, nbTypes, nbTypes, KnownTypes...>{
        protected:
            Data_t const& data;
        public:
            MultiDisp(Data_t const& data) : data(data) {}
     
            void action(){
                actionHelper(std::make_index_sequence<nbTypes>{});
            }
        private:
            template <size_t... I>
            void actionHelper(std::index_sequence<I...>){
                Action action;
                action(dynamic_cast<KnownTypes*>(std::get<I>(data))...); //@TODO
            }
        };
     
     
        // ===================================
        // Interface / gestion des pointeurs :
        //
        // Les pointeurs sont gérés par un std::tuple<BaseClass*...>
        // Pour éviter la duplication, c'est cette classe qui garde les pointeurs en mémoire.
        // On s'occupe aussi d'initialiser les index.
     
        template <class Action, class... Families>
        struct Dispatcher{
        private:
            using Data_t = std::tuple<typename Families::Base* ...>;
            using DispatcherImpl = MultiDisp<Action, Data_t, sizeof...(Families), 0, Families...>;
     
            Data_t data;
        public:
            Dispatcher(Action* a, typename Families::Base* ... to_solve)
            : data{to_solve...} {
            }
            void operator()(){
                DispatcherImpl{data}.action();
            }
        };
     
    } //namespace implementation
     
    using multiple_dispatch_impl::Family;
    using multiple_dispatch_impl::Dispatcher;
     
    } //namespace public
     
    #endif // defined Multiple_Dispatch_Via_Visitor_Wrapper_h */

  10. #10
    Membre chevronné Avatar de Ehonn
    Homme Profil pro
    Étudiant
    Inscrit en
    Février 2012
    Messages
    788
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 34
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Février 2012
    Messages : 788
    Points : 2 160
    Points
    2 160
    Par défaut
    Citation Envoyé par n0-sheep Voir le message
    Comment changer une seule variable et son type dans un std::tuple sans avoir à tout recopier ?
    En les déplaçant (?) std::move.

    Citation Envoyé par n0-sheep Voir le message
    Comment mettre des references dans un std::tuple
    Jette un œil à std::tie et à std::reference_wrapper.

  11. #11
    Invité
    Invité(e)
    Par défaut
    Sinon, si tu persistes à utiliser un double-dispatch et le pattern visitor, sache que, le but du double dispatch est de se passer d'un cast, donc, ton cast ici n'est pas bon :

    Code cpp : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
     template <size_t... I>
            void actionHelper(std::index_sequence<I...>){
                Action a;
                a(static_cast<TypesConnus*>(std::get<I>(data))...);
            }
    Ton architecture est trop complexe, trop rigide, lente, etc... (Je pense qu'elle sera même plus lente qu'un RTTI)

    Tu devrais plutôt partir sur quelque chose comme ça (et utiliser des références au passage):

    Code cpp : 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
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
     
    class Chien;
    class Chat;
    class AnimalVisitor{
    public:
        virtual void visit(Chat* c) = 0;
        virtual void visit(Chien* c) = 0;
    };
     
    class Animal{
    public:
        virtual void accept(AnimalVisitor* v) = 0;  
        virtual void operator()(Action& a, Animal& an) = 0;
        virtual void operator()(Action& a, Chat& chat) = 0;
        virtual void operator()(Action& a, Chien& chien) = 0;  
        virtual ~Animal(){}
    };
     
    class Chat : public Animal{
    public:
         virtual void accept(AnimalVisitor* v){ v->visit(this); }
         void operator()(Action& a, Animal& an) {
                a(*this, an);
         }
         void operator()((Action& a, Chat& lhs) {
                a(*this, chat);
         }
         void operator()((Action& a, Chien& lhs) {
                a(*this, chien);
         }    
    };
     
    class Chien : public Animal{
    public:
        virtual void accept(AnimalVisitor* v){ v->visit(this); }
        void operator()(Action& a, Animal& an) {
               a(*this, an);
        }
        void operator()((Action& a, Chat& lhs) {
                 a(*this, chat);
        }
        void operator()((Action& a, Chien& lhs) {
                 a(*this, chien);
        }   
    };
     
    //===========================================================
    // Ici on utilise la bibliothèque :
    //
    // On crée une action qui nécessite un multiple dispatch
    // On l'enregistre auprès d'un multiple dispatcher
    // Nombre de lignes pour utiliser la bibliotheque : 3 lignes.
    class Action {         
             void operator()(Animal& a1, Animal &a2) {
                    a1(*this, a2);
             }
             void operator()(Chat& lhs, Animal& rhs) {
                   (*rhs)(*this, lhs);
             }
             void operator()(Chien& lhs, Animal& rhs) {
                   (*rhs)(*this, lhs);
             }        
             virtual void operator()(Chat& lhs, Chat& rhs)=0;
             virtual void operator()(Chat& lhs, Chien& rhs)=0;
             virtual void operator()(Chien& lhs, Chat& rhs)=0;
             virtual void operator()(Chien& lhs, Chien& rhs)=0;
    };
    class ActionAttaquer : Action {
    public:             
            void operator()(Chat& lhs, Chat& rhs) { cout <<  "griffe - griffe" << endl;}
            void operator()(Chat& lhs, Chien& rhs){ cout <<  "griffe - mord  " << endl;}
            void operator()(Chien& lhs, Chat& rhs){ cout <<  "mord   - griffe" << endl;}
            void operator()(Chien& lhs, Chien& rhs){ cout << "mord   - mord  " << endl;} 
    };

    Je te laisse chercher comment faire pour le multi dispatch, l'utilisation de plusieurs familles, et la génération du code source par ta librairie, bref, ça optimisera.
    Personnellement c'est comme ça que je fais dans ma bibliothèque. (Là ou il n'y a pas trop d'héritage)
    Je ne génère pas le code source automatiquement mais je serais intéressé si ta librairie le fait.

    Le RTTI est peut être plus lent mais indispensable dans les RPG sinon trop d'héritage, sinon oui il y a un système d'entité et de composant dans ma bibliothèque.
    Dernière modification par Invité ; 17/09/2014 à 11h01.

  12. #12
    Invité
    Invité(e)
    Par défaut
    Ce code-ci marche chez moi mais, je ne vois pas du tout comment faire ça avec une librairie :

    Code cpp : 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
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
     
    #include <iostream>
    class Chien;
    class Chat;
     
    class AnimalVisitor{
    public:
        virtual void visit(Chat* c) = 0;
        virtual void visit(Chien* c) = 0;
    };
    class Action;
    class Animal{
    public:
        virtual void accept(AnimalVisitor* v) = 0;
        virtual void operator()(Action& a, Animal& an) = 0;
        virtual void operator()(Action& a, Chat& chat) = 0;
        virtual void operator()(Action& a, Chien& chien) = 0;
        virtual ~Animal(){}
    };
    class Action {
    public :
             virtual void operator()(Animal& a1, Animal &a2) = 0;
             void operator()(Chat& lhs, Animal& rhs) {
                   rhs(*this, lhs);
             }
             void operator()(Chien& lhs, Animal& rhs) {
                   rhs(*this, lhs);
             }
             virtual void operator()(Chat& lhs, Chat& rhs)=0;
             virtual void operator()(Chat& lhs, Chien& rhs)=0;
             virtual void operator()(Chien& lhs, Chat& rhs)=0;
             virtual void operator()(Chien& lhs, Chien& rhs)=0;
    };
    class ActionAttaquer : Action {
    public:
            void operator()(Animal& a1, Animal &a2) {
                a1(*this, a2);
            }
            void operator()(Chat& lhs, Chat& rhs) { std::cout <<  "griffe - griffe" << std::endl;}
            void operator()(Chat& lhs, Chien& rhs){ std::cout <<  "griffe - mord  " << std::endl;}
            void operator()(Chien& lhs, Chat& rhs){  std::cout <<  "mord   - griffe" << std::endl;}
            void operator()(Chien& lhs, Chien& rhs){  std::cout << "mord   - mord  " << std::endl;}
    };
    class Chat : public Animal{
    public:
         virtual void accept(AnimalVisitor* v){ v->visit(this); }
         void operator()(Action& a, Animal& an) {
                a(*this, an);
         }
         void operator()(Action& a, Chat& chat) {
                a(*this, chat);
         }
         void operator()(Action& a, Chien& chien) {
                a(*this, chien);
         }
    };
     
    class Chien : public Animal{
    public:
        virtual void accept(AnimalVisitor* v){ v->visit(this); }
        void operator()(Action& a, Animal& an) {
               a(*this, an);
        }
        void operator()(Action& a, Chat& chat) {
               a(*this, chat);
        }
        void operator()(Action& a, Chien& chien) {
               a(*this, chien);
        }
    };
     
    int main (int argv, char* argc[]) {
        Chat chat;
        Chien chien;
     
        Animal* a1 = &chat;
        Animal* a2 = &chien;
     
        ActionAttaquer attaquer;
        attaquer(*a1, *a2);
        attaquer(*a2, *a2);
        std::cout << "Fin." <<std::endl;
        return 0;
    }
    Et je ne pense pas qu'une librairie soit nécessaire pour faire ce genre de chose.

    PS : ce code s'exécute en 0.003s voir 0.004s au pire chez moi.

  13. #13
    En attente de confirmation mail

    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2004
    Messages
    1 391
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 33
    Localisation : France, Doubs (Franche Comté)

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

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Points : 3 311
    Points
    3 311
    Par défaut
    @Lolilolight: Ton code est très intrusif et non maintenable, je rajoute une classe dans la hiérarchie ou je passe à du triple dispatch, ça change combien de ligne à combien d'endroit ? Dans le code de l'OP ça se limite à quelques endroits (même si c'est pas encore parfait).

  14. #14
    En attente de confirmation mail

    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2004
    Messages
    1 391
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 33
    Localisation : France, Doubs (Franche Comté)

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

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Points : 3 311
    Points
    3 311
    Par défaut
    Le plus gros défaut qu'il y a à ton système, c'est qu'il demande quand même un certain effort de la part de l'utilisateur, je pense qu'on peut l'améliorer pour arriver à une utilisation du type :
    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
     
    class Animal;
    class Chat;
    class Chien;
     
    struct Chat : /*STUFF*/<Chat>
    { };
     
    struct Chien : /*STUFF*/<Chien>
    { };
     
    struct ActionAttaquer : /*STUFF*/
    {
        void operator()(Chat* lhs, Chat* rhs) { cout <<  "griffe - griffe" << endl;}
        void operator()(Chat* lhs, Chien* rhs){ cout <<  "griffe - mord  " << endl;}
        void operator()(Chien* lhs, Chat* rhs){ cout <<  "mord   - griffe" << endl;}
        void operator()(Chien* lhs, Chien* rhs){ cout << "mord   - mord  " << endl;}
    };
     
    int main()
    {
        cout << "Debut." << endl;
     
        Chat chat;
        Chien chien;
     
        Animal* a1 = &chat;
        Animal* a2 = &chien;
     
        dispatch(ActionAttaquer(),a1,a2);
     
        cout << "Fin." << endl;
        return 0;
    }
    C'est pas si simple à faire, mais je pense que c'est faisable.

    Au niveau fonctionnement interne, j'aime assez le principe que tu as mis en place, par contre j'ai un sentiment de complexité de ton MultiDisp, dans le sens où il itère sur deux dimension (par l'héritage et dans visit). C'est pas évident de comprendre directement ce qu'il se passe, j'aimerais bien trouver une écriture qui rend les choses plus clair mais c'est pas évident.

  15. #15
    Invité
    Invité(e)
    Par défaut
    Ha oui, du CRTP et de l'héritage multiple pourquoi pas, en principe, c'est ce que j'utilise dans ces cas là!

    Je voulais juste dire que je trouvais que le dynamic_cast n'était pas bien, en général on le fait que lorsqu'on a pas le choix c'est à dire dans le cadre d'un système de RTTI.

    Donc bon j'étais justement entrain de chercher un solution pour rentre mon code plus maintenable, mais, je pense que en utilisant du CRTP et l'héritage multiple, il y a moyen de faire un code plus maintenable.

    Un truc du genre :

    Code cpp : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    template <typename B1>
    class Registered : Visitor<B1> {
            //
    };
    template <typename B2>
    class Registered : Visitor<B2> {
             //
    };
    Pour les différentes familles, et ensuite tu peux facilement faire une classe avec un tuple pour le reste : (Perso j'utilise RTTI et un allocator car dans mon cas le tuple ne suffit pas, il me faut le type complet de la classe dérivée pour pouvoir alloué un objet, hors, dans ma librairie, je ne le connais pas, mais ici apparemment tu n'as aps besoin d'allocator donc je pense que ça peut passer!)

    Code cpp : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
    template<typename B>
    class BaseVisitor
    {
    };
    template <template<typename... B>, class ...D, class V>
    class MultipleDispatch : V...
    {
         std::tuple<D...> deriveds;
    };

    J'ai fait un truc très semblable avec du RTTI mais ici il y a une difficulté supplémentaire : il y a plusieurs familles, donc, le CRTP avec de l'héritage simple ne suffit pas.

  16. #16
    Membre du Club
    Homme Profil pro
    C++
    Inscrit en
    Janvier 2013
    Messages
    45
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : C++

    Informations forums :
    Inscription : Janvier 2013
    Messages : 45
    Points : 44
    Points
    44
    Par défaut Version 5 : CircularBuffer
    Bonsoir tout le monde,
    Voici une nouvelle version. J'ai revu la conception : celle-ci est beaucoup plus simple.
    • Conception simplifiée et un pas de plus vers le SRP.
    • Utilisation de références
    • const correctness
    • OK héritage multiple et virtuel
    • Appel au dispatcher en deux lignes.


    J'attends vos remarques avec impatience

    Vous trouverez ci-joint le fichier zip mis à jour.
    Ci-dessous un copier-coller du fichier header :

    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
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
     
    #ifndef Multiple_Dispatch_Via_Visitor_Wrapper_h
    #define Multiple_Dispatch_Via_Visitor_Wrapper_h
    /************************************************************************
     Project : Multiple Dispatch Wrappers
     File    : multiple_dispatch.h ,
               Single header, sans fonctionnalité avancée
     
     Author  : HARBULOT Julien
     
     Copyright (C) 2014 HARBULOT Julien Edmond René
     
     This program is free software: you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
     the Free Software Foundation, either version 3 of the License, or
     (at your option) any later version.
     
     This program is distributed in the hope that it will be useful,
     but WITHOUT ANY WARRANTY; without even the implied warranty of
     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     GNU General Public License for more details.
     
     You should have received a copy of the GNU General Public License
     along with this program.  If not, see <http://www.gnu.org/licenses/>.
     
     email : info [at] julienh.fr
     
     ************************************************************************
     * Système de multiple dispatch à N arguments (pour tout N > 0)
     *
     * Utilisation :
     *  Chaque classe dont on voudra reconnaître le type implémente le
     *  pattern visitor [aka : elle possède une methode accept(...) ]
     *  Les wrappers prennent un nombre quelconque d'arguments et une action.
     *  Ils s'occupent de retrouver le type de tous les arguments via leurs
     *  méthodes accept(...), puis apellent l'action demandée.
     *
     * Version : 5 - CircularBuffer
     
     ************************************************************************/
     
     
    #include <tuple>
     
    //========================================================================
    //================================ LIBRARY ===============================
    //========================================================================
    namespace dispatch{
    namespace multiple_dispatch_impl{
     
        //====================================================================
        //====================================================================
        //
        // === Family ===
        //
     
        // A partir du type BaseClass, un dispatcher doit retrouver le bon type dérivé.
        // Pour cela, il doit connaitre la liste des types dérivés (DerivedList)
        // Et il doit pouvoir visiter la hiérarchie en héritant de VisitorBase
        // Ces données constituent une famille :
     
        template <class VisitorBase, class BaseClass, class... DerivedList>
        class Family{
        public:
            template <class... Ts> struct L;
     
            using Visitor = VisitorBase;
            using Base = BaseClass;
            using Deriveds = L<DerivedList...>;
     
            Family(Base& b) : base_{b} {}
            operator Base& () const {return base_;}
            Base& base() const {return base_;}
     
        private:
            Base& base_;
        };
     
        //====================================================================
        //====================================================================
        //
        // === FamilyVisitor ===
        //
     
        // Etant donnée une famille,
        // un FamilyVisitor implémente une fonction visit(...) pour chaque
        // membre de la famille.
        template <class Action, class Family>
        class FamilyVisitor;
     
        // Recursion : Pour chaque dérivée, implémenter une fonction visit(...)
        // A chaque étape, le parameter_pack <DerivedTail...> diminue.
        template <class Action, class VisitorBase, class Base, class DerivedHead, class... DerivedTail>
        class FamilyVisitor<Action, Family<VisitorBase, Base, DerivedHead, DerivedTail...>>
        : public FamilyVisitor<Action, Family<VisitorBase, Base, DerivedTail...>>{
        private:
            using Parent = FamilyVisitor<Action, Family<VisitorBase, Base, DerivedTail...>>;
        public:
            using Parent::Parent;
            using Parent::visit;
            void visit(DerivedHead& derived){
                Parent::action(derived);
            }
        };
     
        // Cas d'arret : Il n'y a plus de dérivée.
        template <class Action, class VisitorBase, class Base >
        class FamilyVisitor< Action, Family<VisitorBase, Base> >
        : public VisitorBase
        {
        protected:
            Action action;
        public:
            FamilyVisitor(Action a): action{a} {}
            void visit(Base& b){b.accept(*this);}
        };
     
        //====================================================================
        //====================================================================
        //
        // === MultiDispatch ===
        //
     
        // Le MultiDispatch retrouve le bon type dérivé de chaque référence de CircularBuffer
        // en effectuant une récursion sur l'ensemble des familles à traiter.
        template <class Action, class CircularBuffer, class... Families>
        class MultiDispatch;
     
        // Recursion : Pour chaque famille, lancer une visite sur cette famille.
        // A chaque étape <OtherFamilies...> diminue.
        template <class Action, class CircularBuffer, class FamilyHead, class... OtherFamilies>
        class MultiDispatch<Action, CircularBuffer, FamilyHead, OtherFamilies...>{
        private:
            Action& action_finale;
            CircularBuffer deriveds;
     
        public:
            MultiDispatch(Action& a, CircularBuffer deriveds)
            : action_finale(a), deriveds{deriveds}
            {}
     
            void operator()() const{
                start_visit(deriveds);
            }
     
            template <class DerivedType>
            void operator()(DerivedType& to_insert) const{
                auto buffer = deriveds.template insert<DerivedType&>(to_insert);
                start_visit(buffer);
            }
        private:
            template <class Buffer>
            void start_visit(Buffer buffer) const{
                auto visitor = get_visitor(buffer);
                visitor.visit(buffer.top());
            }
     
            template <
            class Buffer,
            class NextVisitorAction = MultiDispatch<Action, Buffer, OtherFamilies...>,
            class NewVisitor = FamilyVisitor<NextVisitorAction, FamilyHead>
            >
            NewVisitor get_visitor(Buffer buffer) const{
                NextVisitorAction visitor_action{action_finale, buffer};
                NewVisitor visitor{visitor_action};
                return visitor;
            }
        };
     
        // Cas d'arret : Toutes les familles ont été traitées.
        template <class Action, class CircularBuffer>
        class MultiDispatch<Action, CircularBuffer>{
        public:
            Action& action_finale;
            CircularBuffer deriveds;
        public:
            MultiDispatch(Action& a, CircularBuffer deriveds)
            : action_finale(a), deriveds{deriveds}
            {}
     
            template <class DerivedType>
            void operator()(DerivedType& to_insert) const{
                auto buffer = deriveds.template insert<DerivedType&>(to_insert);
                buffer.apply(action_finale);
            }
        };
     
        //====================================================================
        //====================================================================
        //
        // === CircularBuffer ===
        //
        template <class T, class... Ts>
        class CircularBuffer{
            private :
            // Simplification d'écriture
            template <size_t... Ns> using Seq = std::index_sequence<Ns ...>;
            template <size_t N> using MakeSeq = std::make_index_sequence<N>;
            using Data_t = std::tuple<T, Ts...>;
     
            // Variables
            static constexpr size_t size = 1+sizeof...(Ts);
            const Data_t data;
     
        public:
            CircularBuffer(T item1, Ts... items)
            : data{item1, items...}
            {}
     
            T top() const { return std::get<0>(data);}
     
            template <class NewT>
            CircularBuffer<Ts..., NewT> insert(NewT newItem) const{
                return insert_impl<NewT>(newItem, MakeSeq<size>{});
            }
            template <class Func>
            void apply(Func& f) const{
                return apply_impl(f, MakeSeq<size>{});
            }
        private:
            // Implémentations :
     
            template <class NewT, size_t I0, size_t... Is>
            CircularBuffer<Ts..., NewT> insert_impl(NewT newItem, Seq<I0, Is...>) const{
                // omet le premier element d'indexe I0, newItem est ajouté en fin.
                return {std::get<Is>(data)..., newItem};
            }
     
            template <class Func, size_t... Is>
            void apply_impl(Func& f, Seq<Is...> ) const{
                f(std::get<Is>(data)...);
            }
        };
        //====================================================================
        //====================================================================
        //
        // === Proposition d'Interface ===
        //
        template <class Action, class... Families>
        void execute(Action& action, Families... items){
            using Data_t = CircularBuffer<typename Families::Base& ...>;
            using DispatcherImpl = MultiDispatch<Action, Data_t, Families...>;
            DispatcherImpl dispatch{action, Data_t{items.base() ...} };
            dispatch();
        }
     
    } //namespace implementation
     
    using multiple_dispatch_impl::Family;
    using multiple_dispatch_impl::execute;
     
    } //namespace public
     
    #endif // defined Multiple_Dispatch_Via_Visitor_Wrapper_h */
    On s'en sert comme ça :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    void operator()(Animal& lhs, Animal& rhs){
            using animal = dispatch::Family<AnimalVisitor, Animal, Chat, Chien>;
            dispatch::execute(*this, animal(lhs), animal(rhs));
        }
    Ou comme ça :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    void operator()(const Animal& lhs, const Animal& rhs){
            using animal = dispatch::Family<AnimalVisitor, const Animal, const Chat, const Chien>;
            dispatch::execute(*this, animal(lhs), animal(rhs));
        }
    Fichiers attachés Fichiers attachés

  17. #17
    En attente de confirmation mail

    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2004
    Messages
    1 391
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 33
    Localisation : France, Doubs (Franche Comté)

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

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Points : 3 311
    Points
    3 311
    Par défaut
    Alors :
    1. Tu pourrais utiliser directement std::tuple à la place de L je pense.
    2. Je trouve dommage que ton Family ai des données, son seul intérêt est de donner des informations sur le typage, tu devrais pouvoir l'enlever totalement en jouant avec le type std::tuple (sans l'instancier).
    3. Ton CircularBuffer devrait pouvoir se remplacer par un std::tuple avec des fonctions libres adaptées.
    4. Tu devrais nommer visit(Base& b) autrement, son rôle est quand même différent des autres visit
    5. Evite de nommer identiquement les données membres et les paramètres (même si ce n'est pas une erreur en soi).

    Plus les remarques de mon dernier messages.

  18. #18
    Invité
    Invité(e)
    Par défaut
    Voilà qui est déjà mieux même si, ce n'est pas encore parfais.

    Je vais sûrement utiliser ça, plus tard, pour mon gameplay.

  19. #19
    Membre du Club
    Homme Profil pro
    C++
    Inscrit en
    Janvier 2013
    Messages
    45
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : C++

    Informations forums :
    Inscription : Janvier 2013
    Messages : 45
    Points : 44
    Points
    44
    Par défaut
    Salut Flob90,
    Merci pour tes remarques constructives.

    Citation Envoyé par Flob90 Voir le message
    Alors :
    Tu pourrais utiliser directement std::tuple à la place de L je pense.
    Hum c'est une très bonne idée.
    Ceci dit, le code n'utilise pas la liste de dérivées L<Deriveds...> donc je pourrais tout aussi bien l'enlever. Je l'avais laissé pour aider la lecture, mais je me demande si c'est vraiment utile ?


    Citation Envoyé par Flob90 Voir le message
    Alors :
    Je trouve dommage que ton Family ai des données, son seul intérêt est de donner des informations sur le typage, tu devrais pouvoir l'enlever totalement en jouant avec le type std::tuple (sans l'instancier).
    J'aime bien la façon d'appeler dispatch::execute avec les family en tant que wrappers. Quelles sont les alternatives ? Si tu as une idée pour simplifier ça, je suis preneur.


    Citation Envoyé par Flob90 Voir le message
    Ton CircularBuffer devrait pouvoir se remplacer par un std::tuple avec des fonctions libres adaptées.
    Je ne sais pas. L'avantage d'avoir une classe séparée c'est que le Dispatcher n'est pas dépendant des détails d'implémentations. Ce n'est pas son rôle.
    A ce sujet : SRP et OCP, ( d'ailleurs il faut que je relise le header, pour le rendre plus clean sur ces points) .


    Citation Envoyé par Flob90 Voir le message
    Tu devrais nommer visit(Base& b) autrement, son rôle est quand même différent des autres visit
    Oui! D'ailleurs ça serait beaucoup plus sécuritaire en enlevant le risque de boucle infinie. Mais ça demande de revoir le cas d'arrêt de la récursion à cause du using Parent::visit. Quoique je pourrai aussi mettre une fonction visit vide... En attendant de me décider j'ai visé au milieu



    Citation Envoyé par Flob90 Voir le message
    Plus les remarques de mon dernier messages.
    J'ai déjà quelques proposition d'amélioration du pattern visitor en stock, mais je voudrai attendre de finir pour de bon la partie relative au multiple dispatch avant de publier à ce sujet. (En guise de teaser, une solution existe déjà avec de l'héritage multiple et un dynamic_cast.)

  20. #20
    En attente de confirmation mail

    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2004
    Messages
    1 391
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 33
    Localisation : France, Doubs (Franche Comté)

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

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Points : 3 311
    Points
    3 311
    Par défaut
    Pour l'implémentation, j'ai une petite solution en préparation (un segmentation fault à corriger avant de te la proposer). Mais l'idée est d'arriver à une utilisation du type :
    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
     
    #include<iostream>
     
    struct chat;
    struct chien;
     
    struct animal
    	: dispatch::accept_visitor<animal,chat,chien>
    { };
     
    struct chat
    	: dispatch::acceptable<animal,chat>
    { };
     
    struct chien
    	: dispatch::acceptable<animal,chien>
    { };
     
    struct attaquer : dispatch::dispatchable<animal>
    {
    	using dispatch::dispatchable<animal>::operator();
     
    	void operator()(chat&, chat&) const
    	{ std::cout <<  "griffe - griffe" << std::endl;}
    	void operator()(chat&, chien&) const
    	{ std::cout <<  "griffe - mord  " << std::endl;}
    	void operator()(chien&, chat&) const
    	{ std::cout <<  "mord   - griffe" << std::endl;}
    	void operator()(chien&, chien&) const
    	{ std::cout << "mord   - mord  " << std::endl;}
    };
     
    int main()
    {
    	chat c1;
    	chien c2;
     
    	animal& a1 =c1;
    	animal& a2 =c2;
     
    	attaquer()(a1,a2);
    }
    L'implémentation repose sur une unique classe (et quelques utilitaires) :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    template<
    	class To_Visit, class Visited, bool,
    	class Visitor, class Concrete,
    	class Fun
    >
    struct dispatcher
    Ou To_Visit/Visited/Concrete sont des std::tuple.

    Pour le SRP, tu considères std::tuple comme un détail d'implémentation, mais si je la considère comme une classe utilitaire, alors je suis couplé à std::tuple de la même manière que tu l'es à CircularBuffer.

Discussions similaires

  1. Votre avis sur ma bibliothèque
    Par Mikkkka dans le forum C++
    Réponses: 19
    Dernier message: 11/04/2014, 21h11
  2. Votre avis sur l'héritage multiple avec JavaFX
    Par guitariste dans le forum JavaFX
    Réponses: 12
    Dernier message: 01/09/2009, 23h59
  3. votre avis sur les bibliothèques Cappuccino et SproutCore
    Par ClarusAD dans le forum Autres langages pour le Web
    Réponses: 0
    Dernier message: 15/11/2008, 15h23
  4. Réponses: 14
    Dernier message: 12/05/2006, 08h20

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