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 :

Visiteur dynamique basé sur le RTTI


Sujet :

Langage C++

  1. #1
    Membre expérimenté Avatar de vikki
    Profil pro
    Inscrit en
    Mai 2007
    Messages
    292
    Détails du profil
    Informations personnelles :
    Âge : 40
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations forums :
    Inscription : Mai 2007
    Messages : 292
    Par défaut Visiteur dynamique basé sur le RTTI
    Bonjour à tous,
    Dans un gros projet que je développe actuellement, j'utilise en masse le pattern visiteur pour étendre les fonctionnalités de mes hiérarchies classes. Outre le coté laborieux de la chose lorsque je rajoute une classe dans ma hiérarchie (modifier tous les visiteurs dépendant de la classe de base), je dois maintenant implémenter un système de plugin permettant d'ajouter dynamiquement de nouvelles classes. Et donc impossible d'étendre les fonctionnalités de ces classes avec mes visiteurs classiques.

    Je cherche donc une solution de remplacement et je suis tombé sur cette article décrivant un visiteur dynamique basé sur le RTTI. Le principe et d'enregistrer dynamiquement les fonctions de visitation (si ca se dit) pour chaque type. J'ai implémenté une première version de ce visiteur et je trouve ca plutôt pratique à utiliser, je peux même l'utiliser pour étendre mes précédents visiteurs (par héritage) sans foutre en l'air tout mon code.
    J'aimerais avoir votre avis sur ce visiteur dynamique et ces éventuelles limitations (utilisation des fonctions de RTTI, faible vitesse d'exécution, etc...), voir s'il est totalement à proscrire.

    Pour vous donner une idée plus précise, je poste également une première implémentation de ce visiteur dynamique.

    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
     
    #include <boost/function.hpp>
    #include <boost/bind.hpp>
    #include <map>
    #include <iostream>
    #include <typeinfo.h>
     
    //conversion du parametre de type BASE_CLASS_TYPE vers CLASS_TYPE
    template<class CLASS_TYPE, class BASE_CLASS_TYPE, class VISITOR_TYPE>
    void call(void (*f)( VISITOR_TYPE &, CLASS_TYPE & ),  VISITOR_TYPE & visitor, BASE_CLASS_TYPE & obj)
    {
            f(visitor, static_cast<CLASS_TYPE&>(obj));
    }
     
    //modification du prototype de la fonction de visitation de void(VISITOR_TYPE&, BASE_CLASS_TYPE&) vers void(VISITOR_TYPE&, CLASS_TYPE&)
    template<class CLASS_TYPE, class BASE_CLASS_TYPE, class VISITOR_TYPE>
    boost::function<void(VISITOR_TYPE &, BASE_CLASS_TYPE &)> bind_function(void (*f)(VISITOR_TYPE &, CLASS_TYPE & ))
    {
        return boost::function<void(VISITOR_TYPE &, BASE_CLASS_TYPE &)>( boost::bind( call<CLASS_TYPE,BASE_CLASS_TYPE,VISITOR_TYPE>, f, _1 , _2) );
    }
     
    //map static de const type_info* vers boost::function<void(VISITOR_TYPE &,BASE_CLASS_TYPE &)
    template< class VISITOR_TYPE, class BASE_CLASS_TYPE>
    static std::map<const type_info*,boost::function<void(VISITOR_TYPE &,BASE_CLASS_TYPE &)>> & functions()
    {
        static std::map<const type_info*,boost::function<void(VISITOR_TYPE &,BASE_CLASS_TYPE &)>> static_map;
        return static_map;
    }
     
    //Le visiteur de base
    template<class VISITOR_TYPE, class BASE_CLASS_TYPE>
    class BaseVisitor
    {
    public:
     
        typedef boost::function<void(VISITOR_TYPE &,BASE_CLASS_TYPE &)> function_type;
     
        //enregistrement d'une fonction de visitation
        template< class CLASS_TYPE >
        static void Register(void (*fn)(VISITOR_TYPE & ,CLASS_TYPE & ) )
        {
            functions<VISITOR_TYPE,BASE_CLASS_TYPE>()[&typeid(CLASS_TYPE)] = bind_function<CLASS_TYPE,BASE_CLASS_TYPE,VISITOR_TYPE>(fn);
        }
     
        //renvoie la fonction de visitation, pas indispensable
        template< class CLASS_TYPE >
        static function_type GetFunction()
        {
            std::map<const type_info*,function_type>::iterator it = functions<VISITOR_TYPE,BASE_CLASS_TYPE>().find(&typeid(CLASS_TYPE));
            if(it != functions<VISITOR_TYPE,BASE_CLASS_TYPE>().end())
                return it->second;
            else
                return function_type();
        }
     
        //Visite un objet
        virtual void Visit(BASE_CLASS_TYPE & obj)
        {
            std::map<const type_info*,function_type>::iterator it = functions<VISITOR_TYPE,BASE_CLASS_TYPE>().find(&typeid(obj));
            if(it != functions<VISITOR_TYPE,BASE_CLASS_TYPE>().end())
                it->second(static_cast<VISITOR_TYPE&>(*this),obj);
            else
                DefaultBehavior(obj);
        }
     
        //Comportement par default pour les classes qui ne sont pas enregistrées
        virtual void DefaultBehavior(BASE_CLASS_TYPE & )
        {}
    };
     
     
     
    //Hiérarchie de classe bidon, avec fonction virtuelle obligatoire pour l'utilisation du RTTI
    struct A{
        int virtual get(){return 0;}
    };
     
    struct B : public A{
     
    };
     
    struct C : public A{};
     
     
    //Un visiteur quelconque
    class visitor : public BaseVisitor<visitor,A>
    {};
     
    //Les fonctions de visitation
    void testA(visitor & v, A & a)
    {
        std::cout<<"A"<<std::endl;
    }
     
    void testB(visitor & v, B & b)
    {
        std::cout<<"B"<<std::endl;
    }
     
    void testC(visitor & v, C & b)
    {
        std::cout<<"C"<<std::endl;
    }
     
     
    int main()
    {
        A *a=new A;
        A *b=new B;
        C *c=new C;
     
        //enregistrement des fonctions
        visitor::Register(&testA);
        visitor::Register(&testB);
     
        visitor v;
        v.Visit(*a);
        v.Visit(*b);
        v.Visit(*c);
     
        std::system("pause");
    }

  2. #2
    Membre Expert

    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 : 35
    Localisation : France, Doubs (Franche Comté)

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

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Par défaut
    J'ai pas encore pris le temps de regarder ton code ni l'article.

    Qu'entend tu par "ajouter dynamiquement une classe" ? Je vois pas comment tu pourrais ajouter une classe à l'execution d'un programme, ca me semble vraiment impossible.

    Pour ce qui est d'utiliser le RTTI pour un dispatcher, effectivement c'est le plus efficase, mais c'est aussi le plus intruisif.

    Je te conseil de regarder comment Loki implémente ce pattern, il propose divers stratégié (RTTI notamenr).

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

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Par défaut
    salut,
    Pareil que flob, je n'ai pas pris le temps de lire l'article et le code Mais je me souvient d'un billet d'Emanuel Deloget intéressant sur le visiteur : V comme visiteur

  4. #4
    Membre expérimenté Avatar de vikki
    Profil pro
    Inscrit en
    Mai 2007
    Messages
    292
    Détails du profil
    Informations personnelles :
    Âge : 40
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations forums :
    Inscription : Mai 2007
    Messages : 292
    Par défaut
    Hello, merci pour ta réponse!

    Qu'entend tu par "ajouter dynamiquement une classe" ? Je vois pas comment tu pourrais ajouter une classe à l'execution d'un programme, ca me semble vraiment impossible.
    Je me suis mal exprimé. Il s'agit de définir une classe B dans une librairie dynamique héritant d'une classe A connue par le programme principale. La libraire fournie simplement une fonction permettant de récupérer un A* pointant sur une instance de B. La classe B n'est pas connue par le programme et les visiteurs n'y ont donc pas accès.
    Pour ce qui est d'utiliser le RTTI pour le visiteur, effectivement c'est le plus efficace, mais c'est aussi le plus intrusif.
    Le visiteur auquel je pense est (je crois) un des moins intrusif possible. Il ne nécessite pas que les classes visitées possèdent une fonction Accept(visitor &) (elles doivent cependant posséder une fonction virtuelle, ne serait-ce que par héritage, pour faire fonctionner le RTTI).

    Je te conseil de regarder comment Loki implémente ce pattern, il propose divers stratégié (RTTI, cyclic, acyclic, ...), et il me semble que ca represente un peu près tout les visiteurs possible (execption faite pour ceux devant gérer des hiérarchie profonde d'une certaine manière)
    Je viens de regarder brièvement loki, et il semble que, quel que soit le visiteur, tous les types visitables doivent être connus à la déclaration du visiteur, donc à la compilation (à vérifier).

  5. #5
    Membre expérimenté Avatar de vikki
    Profil pro
    Inscrit en
    Mai 2007
    Messages
    292
    Détails du profil
    Informations personnelles :
    Âge : 40
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations forums :
    Inscription : Mai 2007
    Messages : 292
    Par défaut
    Pareil que flob, je n'ai pas pris le temps de lire l'article et le code Mais je me souvient d'un billet d'Emanuel Deloget intéressant sur le visiteur : V comme visiteur
    J'étais effectivement tombé sur ce billet en cherchant une solution, mais le visiteur présenté utilisant le RTTI impose également la connaissance de tous les types visitables à la compilation. D'où la prise de tête avec ce système de plugins

    EDIT: pas besoin de lire tout l'article en lien, seul la dernière partie (intitulée Indirect Visitor) concerne la description de ce visiteur dynamique (heu... visiteur indirect en fait, j'aurais peut être du mettre ce mot dans l'intitulé du post)

  6. #6
    Membre Expert

    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 : 35
    Localisation : France, Doubs (Franche Comté)

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

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Par défaut
    Tu as regarder la partie dispatch de Loki ? Car c'est en réalité ce que tu fais.

    J'ai lu en diagonal ton article, et c'est bien dans l'optique d'un dispatcher qu'il propose ceci, pas vraiment d'un simple visiteur.

    Le dispatcher de Loki qui te concerne est le <edit> BasicDispatcher </edit>

    (Je vais étider mon premier message, une partie porte à confusion).

  7. #7
    Membre expérimenté Avatar de vikki
    Profil pro
    Inscrit en
    Mai 2007
    Messages
    292
    Détails du profil
    Informations personnelles :
    Âge : 40
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations forums :
    Inscription : Mai 2007
    Messages : 292
    Par défaut
    Effectivement, le BasicDispatcher de Loki ressemble beaucoup à ce que je souhaite faire. Je vais regarder ca plus en profondeur, merci pour l'info!

  8. #8
    Membre expérimenté Avatar de vikki
    Profil pro
    Inscrit en
    Mai 2007
    Messages
    292
    Détails du profil
    Informations personnelles :
    Âge : 40
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations forums :
    Inscription : Mai 2007
    Messages : 292
    Par défaut
    En cherchant avec le mot clé "dispatch", les résultats sont plus probants . Il existe une lib de dispatch basé sur le RTTI et utilisant boost, qui d'ailleurs a pour but d'être intégré prochainement dans boost. Je pense que je vais continuer avec elle, ca évitera d'ajouter une nouvelle dépendance au projet (Loki).

  9. #9
    Membre Expert

    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 : 35
    Localisation : France, Doubs (Franche Comté)

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

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Par défaut
    C'est toujours plus simple quand on sais ce qu'on cherche Le pattern visiteur est une solution au problème du dispatch dans une certaine mesure, mais c'est loin d'être le cas général.

    Tu rajoutes quand même une dépendance ...

    Loki est une assez bonne librairie pour se permettre d'y dépendre à mon avis. (à part les foncteurs qui se trouvent dans la bibliothèque standard, la prochaine du moins, et les typelist qui se trouve en plus complet dans MPL, les autres outils fournie méritent le coup d'oeil)

  10. #10
    Membre expérimenté Avatar de vikki
    Profil pro
    Inscrit en
    Mai 2007
    Messages
    292
    Détails du profil
    Informations personnelles :
    Âge : 40
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations forums :
    Inscription : Mai 2007
    Messages : 292
    Par défaut
    Quand je parlais d'ajouter une dépendance, je trouvais juste dommage d'ajouter une lib aussi complète que Loki pour une unique fonctionnalité (que je peux facilement réécrire moi même), sachant que presque tout le reste est disponible dans boost. Mais bon, finalement je vais peut être revenir à Loki, la lib dont j'ai parlé au dessus n'étant pas vraiment ce que j'espérais .

  11. #11
    Membre Expert

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

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

    Informations forums :
    Inscription : Septembre 2007
    Messages : 1 895
    Par défaut
    Citation Envoyé par vikki Voir le message
    J'étais effectivement tombé sur ce billet en cherchant une solution, mais le visiteur présenté utilisant le RTTI impose également la connaissance de tous les types visitables à la compilation. D'où la prise de tête avec ce système de plugins
    Je crois qu'il y a confusion là. Un visiteur dans une application ne peut pas savoir comment visiter un objet défini dans un plugin. C'est compréhensible, et relativement logique. L'idée d'un système de plugin, c'est de permettre l'extension des fonctionnalités du programme hôte, par de les prévoir toutes.

    Par contre, un visiteur dans un plugin peur parcourir des éléments définis dans l'application. La, c'est logique.

    Finalement, un visiteur dans un plugin peut visiter un objet de ce plugin.

    Le visiteur acyclique présenté dans mon article, de même que celui présenté dans l'article cité par le PO :
    * s'il est défini dans un plugin, il peut visiter les types de l'application ; il lui suffit de dériver des classes définies dans l'application.
    * s'il est défini dans l'application, il ne peut pas visiter les types visitables définis dans le plugin.
    * en même temps, si le type visitable est défini dans le plugin, alors son visiteur l'est aussi - sinon ça n'a guère d'intérêt.

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

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

  12. #12
    Membre expérimenté Avatar de vikki
    Profil pro
    Inscrit en
    Mai 2007
    Messages
    292
    Détails du profil
    Informations personnelles :
    Âge : 40
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations forums :
    Inscription : Mai 2007
    Messages : 292
    Par défaut
    Je m'étais mal exprimé au début, ce n'est pas vraiment un visiteur que je cherche mais un dispatcher. Et ce dispatcher doit être capable de s'appliquer sur une classe définie dans un plugin via une fonction que l'on enregistre dans le dispatcher. Si je prends un exemple un peu simpliste:

    L'application définie une classe de base A et quelques classes dérivées, ainsi qu'un dispatcher permettant de modifier des éléments d'interface graphique en fonction du type de la classe appelée (un peu comme un visiteur). Le plugin suivant défini une classe B ainsi qu'un nouveau comportement lorsque le dispatcher prend en argument un objet de type B:

    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
    #include "Classe_A.h"
    #include "GUI_Dispatcher.h"
     
    class B: public A
    {
    // do some stuff
    };
     
    //fonction definissant le comportement du dispatcher pour la classe B
    void modify_GUI_B( B & b)
    {
       //modification de l'interface graphique
    }
     
    //fonction d'initialisation du plugin
    void initPlugin()
    {
         //on enregistre modify_GUI_B dans le dispatcher
         GUI_Dispatcher::Register(&modify_GUI_B);
    }
    Dans l'application je peux faire:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    A * a = une_Instance_Quelconque
    GUI_Dispatcher dispatcher;
    dispatcher.dispatch(*a);
    Bien sur il s'agit ici de simple dispatch dont pas grand intérêt mais c'est le même principe pour du double (ou plus) dispatch. Loki propose d'ailleurs ce type de double dispatcher.

  13. #13
    Membre expérimenté Avatar de vikki
    Profil pro
    Inscrit en
    Mai 2007
    Messages
    292
    Détails du profil
    Informations personnelles :
    Âge : 40
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations forums :
    Inscription : Mai 2007
    Messages : 292
    Par défaut
    Je fais remonté le message pour une possible solution à mon problème de dispatch, solution faisant évidement apparaitre d'autres pépins liés à un gestionnaire de plugin dont je parle sur ce post.

    J'ai donc implémenté un multi-dispatcher directement basé sur l'utilisation du RTTI via la classe type_info. En gros, la classe de dispatch contient une map statique de array<type_info> (type de tous les arguments des fonctions enregistrées) vers une boost.function. En fonction du type des arguments passés en paramètre au dispatcher, la bonne fonction (si elle existe dans la map) est appelée.
    Je post le code à titre informatif, pas forcement besoin de le lire, c'est assez indigeste (j'utilise boost.preprocessor pour gérer le nombre variable d'arguments, je m'y perd moi même).

    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
     
    #ifndef BOOST_PP_IS_ITERATING
     
    #  ifndef DISPATCHER_HPP_INCLUDED
    #    define DISPATCHER_HPP_INCLUDED
     
    //do something 1 time (first include)
    #	 include <boost/array.hpp>
    #	 include <boost/bind.hpp>
    #	 include <boost/function.hpp>
    #    include <boost/preprocessor/repetition.hpp>
    #    include <boost/preprocessor/arithmetic/add.hpp>
    #    include <boost/preprocessor/punctuation/comma_if.hpp>
    #    include <boost/preprocessor/iteration/iterate.hpp>
    #	 include <typeinfo>
    #	 include <map>
     
    #    ifndef DISPACHER_MAX_SIZE
    #      define DISPACHER_MAX_SIZE 5  // default maximum size is 5
    #    endif
     
    //Special macro
    #define MY_COUNT(z,n,macro) macro(BOOST_PP_ADD(n,1)) //internal use only (MY_ENUM)
    #define MY_ENUM(n,macro) BOOST_PP_ENUM(n, MY_COUNT , macro) //call n times macro(i) with comma separator and i going from 1 to n
     
     
    //default mapper
    template< int, class dispatcher_type, class signature >
    struct mapper
    {};
     
    template< int, class dispatcher_type, class signature >
    class basic_dispatcher
    {};
     
    // generate specializations
    #    define BOOST_PP_ITERATION_LIMITS (1, DISPACHER_MAX_SIZE )
    #    define BOOST_PP_FILENAME_1       "dispatcher.hpp" // this file
    #    include BOOST_PP_ITERATE()
     
    #  endif // DISPATCHER_HPP_INCLUDED
     
    #else // BOOST_PP_IS_ITERATING
     
     
     
    #define n BOOST_PP_ITERATION()
    #if n > 0
     
    	#define ENUM_TEMPLATE(n) class BOOST_PP_CAT(T,n)																			//class Tn
    	#define ENUM_ARGS(n) BOOST_PP_CAT(T , n) BOOST_PP_CAT(a , n)																//T1 a1
    	#define ENUM_CAST(n) BOOST_PP_CAT(BOOST_PP_CAT(static_cast<ff::arg , n) , _type>) ( BOOST_PP_CAT(a , n) )					//static_cast<ff::arg1_type>(a1)
    	#define ENUM_BIND(n) BOOST_PP_CAT( BOOST_PP_CAT(ff::arg , n) ,_type )														//ff::arg1_type
    	#define ENUM_BIND_ARG(n) BOOST_PP_CAT(_ , n)																				//_1
    	#define ENUM_FUNCTION_ARGS(n) BOOST_PP_CAT(a,n)																				//a1
    	#define ENUM_BOOST_FUNCTION_ARGS(n) BOOST_PP_CAT(BOOST_PP_CAT(typename function_type::arg , n) , _type) BOOST_PP_CAT(a , n)	//typename function_type::arg1_type a1
    	#define ENUM_BOOST_FUNCTION_TEMPLATE_ARGS(n) BOOST_PP_CAT(BOOST_PP_CAT(typename function_type::arg , n) , _type)			//typename function_type::arg1_type
    	#define ENUM_TYPEID_TYPE(n) &typeid(BOOST_PP_CAT( BOOST_PP_CAT(ff::arg , n) ,_type ))										//&typeid(ff::arg1_type)
    	#define ENUM_TYPEID_OBJECT(n) &typeid(BOOST_PP_CAT(a,n))																	//&typeid(a1)
     
     
     
    	//template specialization of the basic dispatcher.
    	template< class dispatcher_type ,class signature_type>
    	class basic_dispatcher<n,dispatcher_type,signature_type>
    	{
     
    	public:
     
    		typedef typename boost::function<signature_type>	function_type;
    		typedef typename function_type::result_type			result_type;
     
                    virtual ~basic_dispatcher()
                    {}
     
    		template< class signature , class arg>
    		static void register_function( arg fun )
    		{
    			typedef typename boost::function<signature> ff;
    			boost::array<const std::type_info*,n> tmp = {MY_ENUM(n,ENUM_TYPEID_TYPE)};
    			functions()[ tmp ] = bindfun_<signature_type,signature>(ff(fun));
    		}
     
    		virtual result_type default_function(MY_ENUM(n,ENUM_BOOST_FUNCTION_ARGS)) = 0;
     
    		result_type operator()(MY_ENUM(n,ENUM_BOOST_FUNCTION_ARGS))
    		{
    			boost::array<const std::type_info*,n> tmp = {MY_ENUM(n,ENUM_TYPEID_OBJECT)};
    			std::map<boost::array<const std::type_info*,n>,function_type > & mymap = functions();
    			std::map<boost::array<const std::type_info*,n>,function_type >::iterator it = mymap.find(tmp);
    			if(it != mymap.end())
    				return it->second(MY_ENUM(n,ENUM_FUNCTION_ARGS));
    			else
    				return default_function(MY_ENUM(n,ENUM_FUNCTION_ARGS));
    		}
     
    	private:
     
    		//call a boost::function with n arguments, and cast them to match sign_from signature
    		template <class sign_from , MY_ENUM(n,ENUM_TEMPLATE)>										
    		static typename boost::function<sign_from>::result_type call_ ( boost::function<sign_from > f , MY_ENUM(n,ENUM_ARGS) )	
    		{																									
    			typedef boost::function<sign_from> ff;															
    			return f( MY_ENUM(n,ENUM_CAST) );																
    		}																									
     
    		//cast a function object with signature sign_from to a function of type sign_to
    		template< class sign_to,class sign_from>															
    		static boost::function<sign_to> bindfun_ (boost::function<sign_from> f)									
    		{																									
    			typedef boost::function<sign_to> ff;															
    			return boost::function<sign_to>(boost::bind(call_ <sign_from , MY_ENUM(n,ENUM_BIND)>,f , MY_ENUM(n,ENUM_BIND_ARG) ));	
    		}	
     
    		static std::map< boost::array<const std::type_info*,n> , function_type > & functions()
    		{
    			static std::map< boost::array<const std::type_info*,n> , boost::function<signature_type> > static_map;
    			return static_map;
    		}
    	};
     
    	#undef ENUM_TEMPLATE
    	#undef ENUM_ARGS
    	#undef ENUM_CAST
    	#undef ENUM_BIND
    	#undef ENUM_BIND_ARG
    	#undef ENUM_FUNCTION_ARGS
    	#undef ENUM_BOOST_FUNCTION_ARGS
    	#undef ENUM_TYPEID_TYPE
    	#undef ENUM_TYPEID_OBJECT
     
    #endif //n > 0
    #undef n
     
    #endif // BOOST_PP_IS_ITERATING

    Plus important, un exemple d'utilisation concret (compile sous Visual C++):

    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
     
    #include <iostream>
     
    //definition du nombre max d'arguments des fonctions enregistrées
    #define DISPACHER_MAX_SIZE 12
    #include "dispatcher.hpp"
     
    //hierarchie bidon
    struct A{
    	virtual ~A(){}
    };
     
    struct B : public A{};
     
    struct C : public A{};
     
    //les fonctions à enregistrer dans le dispatcher
    int test1(A &, A&)
    {
    	return 1;
    }
     
    int test2(B&, A&)
    {
    	return 2;
    }
     
    int test3(B&, C&)
    {
    	return 3;
    }
     
    //Une structure de dispatch derivant du type basic_dispatcher
    struct dispatcher : public basic_dispatcher<2,dispatcher,int(A&,A&)>
    {
    	//surcharge obligatoire definissant le comportement si le prototype n'est pas enregistré dans le dispatcher
    	virtual int default_function(A&,A&)
    	{
    		return -1;
    	}
     
    };
     
    int main()
    {
    	A *a = new A();
    	A *b = new B();;
    	A *c = new C();
     
    	//on enregistre dynamiquement les fonctions vers lesquels dispatcher les appels à l'operateur()
    	dispatcher::register_function<int(A&,A&)>(test1);
    	dispatcher::register_function<int(B&,A&)>(test2);
    	dispatcher::register_function<int(B&,C&)>(test3);
     
    	dispatcher d;
    	std::cout<< d(*a,*a) <<std::endl; //ecrit 1 (fonction test1)
    	std::cout<< d(*b,*a) <<std::endl; //ecrit 2 (fonction test2)
    	std::cout<< d(*b,*c) <<std::endl; //ecrit 3 (fonction test3)
    	std::cout<< d(*b,*b) <<std::endl; //ecrit -1 (pas de prototype enregistré, appel de la fonction default_function)
     
    	std::system("pause");
            return 0;
    }
    Ce code produit ce que je souhaitais initiallement, c.a.d permettre d'enregistrer dynamiquement des fonctions (en fait tout ce qui peut être passé à une boost::function) dans un dispatcher. Ces enregistrements se feront au sein des plugins définissant de nouveau types héritant de mes abstractions. Cela permet d'introduire de nouveaux comportements sans connaitre les définissions des nouvelles classes.
    Donc gros problème rejoignant le post mentionné au dessus, dans quel cas les variables globales (le dispatcher ici) définies dans une lib (liée statiquement à l'exe et aux plugins) est-elle partagée par toutes les dll?

    Et de manière générale, n'hésitez pas à poster tous les inconvénient/avantages/améliorations de ce type d'approche (pour info, je n'est pas encore calculé le (gros) overhead introduit par l'utilisation des type_info, la recherche dans une map et l'appel à une boost.function).

  14. #14
    Rédacteur/Modérateur
    Avatar de JolyLoic
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2004
    Messages
    5 463
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 50
    Localisation : France, Yvelines (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 5 463
    Par défaut
    Quand une bibliothèque statique définit une variable globale, et qu'on link cette bibliothèque statique à un programme principal et à 1 ou plusieurs DLL, cette variable se trouve dupliquée (c'est du mois le comportement sous windows, ça peut être différent ailleurs). La solution que j'avais trouvé à ce problème, c'est de définir un singleton qui se crée dans une zone de mémoire partagée nommée.

    Ce qui m'intéressait en l'occurrence n'était pas l'aspect partagé de cette mémoire (d'ailleurs, je me débrouillais pour la créer avec un nom unique, mais connu de l'exe et des DLL, par exemple en utilisant l'ID du process dans le nom), mais le fait que grâce à son nom, l'OS nous retourne la même zone de mémoire si on y fait appel depuis plusieurs endroits différents.

    Une alternative serait que cette variable globale soit référencée uniquement dans une troisième DLL (du coup, elle ne serait plus dupliquée), DLL qui exposerait des fonctions pour y accéder.
    Ma session aux Microsoft TechDays 2013 : Développer en natif avec C++11.
    Celle des Microsoft TechDays 2014 : Bonnes pratiques pour apprivoiser le C++11 avec Visual C++
    Et celle des Microsoft TechDays 2015 : Visual C++ 2015 : voyage à la découverte d'un nouveau monde
    Je donne des formations au C++ en entreprise, n'hésitez pas à me contacter.

  15. #15
    Membre Expert

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

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

    Informations forums :
    Inscription : Septembre 2007
    Messages : 1 895
    Par défaut
    Par définition sous Windows, une DLL ne contient pas de symboles inconnus - tous les symboles sont définis dans un module dont dépend la DLL ou dans la DLL elle même. Tu vois bien que dans ce cas, la DLL ne peut pas utiliser un symbole qui est défini dans l'executable qui la charge (*). Du coup, si un symbole est défini pour un plugin, alors il le sera pour tous les plugins.

    Une solution simple est d'utiliser la préconisation de Loic. Une autre manière est de fournir à la DLL le mécanisme d'enregistrement (via une fonction exportée de la DLL). Une troisième manière est de laisser le host faire tout le travail (ce qui peut t'amener à revoir l'interface des plugins).

    --
    (*) à moins d'être tordu : l'exe peur exporter des symboles, et la DLL peut se brancher sur ces symboles exportés. C'est relativement aisé au runtime (avec GetProcAddress()à puisque le HMODULE de l'exe est censé être connu de la DLL (via GetModuleHandle(NULL) si je ne m'abuse). Il me semble, mais c'est à vérifier (et le fait que je puisse taper uname -a sur ma console m'en empêche à l'heure actuelle), qu'un tel lien peut être automatique (il faut linker la DLL avec le .lib produit par la compilation de l'EXE qui exporte le symbole).

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

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

  16. #16
    Membre expérimenté Avatar de vikki
    Profil pro
    Inscrit en
    Mai 2007
    Messages
    292
    Détails du profil
    Informations personnelles :
    Âge : 40
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations forums :
    Inscription : Mai 2007
    Messages : 292
    Par défaut
    Effectivement, j'ai lu un peu partout (et vous le confirmez) que chaque dll possède sa propre instance de la variable. Et pourtant, je suis sur d'avoir observé le comportement inverse avec boost.python (voir dernier message de cette discussion)! La lib possède en interne un dictionnaire python des classes exportées, celui ci étant partagé dans l'exe et les dll (.pyd) créée via boost.python lorsque les différents binaires sont compilés avec les même paramètres.

    Tout bien réfléchi, il est possible que le mecanisme sous-jacent soit celui décrit par JolyLoic, à savoir que ce dictionnaire python est uniquement exporté dans la dll de boost.python (boost_python-vc80-mt-gd-1_39.dll dans mon cas). Ce qui m'obligerait à utiliser la version dynamique de la lib.

Discussions similaires

  1. [MySQL] Menu déroulant dynamique basé sur mysql
    Par Stéph utilisateur d'acces dans le forum PHP & Base de données
    Réponses: 4
    Dernier message: 21/07/2010, 18h18
  2. [2.2.2] Datacube basé sur dataset dynamique
    Par elbrujo2323 dans le forum BIRT
    Réponses: 0
    Dernier message: 08/10/2009, 11h20
  3. Instance dynamique basé sur nom de variable
    Par Chromozome dans le forum C#
    Réponses: 3
    Dernier message: 15/02/2009, 20h39
  4. Menu dynamique basé sur une table
    Par Anto03 dans le forum ASP.NET
    Réponses: 5
    Dernier message: 09/07/2008, 12h24
  5. Réponses: 2
    Dernier message: 07/10/2007, 16h59

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