IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

 C++ Discussion :

Formaliser une "interface" commune pour différentes classes templates


Sujet :

C++

  1. #1
    Membre averti Avatar de Seabirds
    Homme Profil pro
    Post-doctoral fellow
    Inscrit en
    Avril 2015
    Messages
    294
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Post-doctoral fellow
    Secteur : Agroalimentaire - Agriculture

    Informations forums :
    Inscription : Avril 2015
    Messages : 294
    Points : 341
    Points
    341
    Par défaut Formaliser une "interface" commune pour différentes classes templates
    Salut à toutes et à tous !

    Une question de débutant... mais que je n'arrive pas à trouver sur mes moteurs de recherche préférés...

    Une classe Factory est templatée sur le type T d'objets à fabriquer : Factory<T>, et elle construit T petit à petit en appelant certains services.
    En l'état, elle marche avec une première classe A, et Factory<A>::build() marche très bien.
    Au moment de coder une deuxième classe B pour pouvoir instancier une Factory<B>, je me vois obligé de re-parcourir l'implémentation de la Factory pour lister les services de D qu'elle nécessite pour la construction. C'est faisable parce que ma classe Factory n'est pas non plus démente, mais c'est assez pénible à faire pour que je me doute que les vrais développeurs s'y prendraient autrement. Ma question était de savoir comment

    Bien cordialement
    Le débutant, lui, ignore qu'il ignore à ce point, il est fier de ses premiers succès, bien plus qu'il n'est conscient de l'étendue de ce qu'il ne sait pas, dès qu'il progresse en revanche, dès que s'accroît ce qu'il sait, il commence à saisir tout ce qui manque encore à son savoir. Qui sait peu ignore aussi très peu. [Roger Pol-Droit]
    Github
    Mon tout premier projet: une bibliothèque de simulation de génétique des populations

  2. #2
    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
    Je ne comprends pas ton problème précis, mais il me semble que tu devrai te renseigner sur "interface segregation principles", le I de SOLID

    Les hiérarchies OO ne sont pas toujours la solution, mais pour en dire plus, il faut un contexte plus précis.
    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

  3. #3
    Membre averti Avatar de Seabirds
    Homme Profil pro
    Post-doctoral fellow
    Inscrit en
    Avril 2015
    Messages
    294
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Post-doctoral fellow
    Secteur : Agroalimentaire - Agriculture

    Informations forums :
    Inscription : Avril 2015
    Messages : 294
    Points : 341
    Points
    341
    Par défaut
    OK merci de ta réponse
    J'ai édité le message précédent. Est-ce plus clair ?
    J'avais déjà lu sur l'ISP. D'ailleurs je crois bien que quelque part mon ennui est que j'ai poussé l'ISP à l'extrême en ne regroupant aucun service
    Le débutant, lui, ignore qu'il ignore à ce point, il est fier de ses premiers succès, bien plus qu'il n'est conscient de l'étendue de ce qu'il ne sait pas, dès qu'il progresse en revanche, dès que s'accroît ce qu'il sait, il commence à saisir tout ce qui manque encore à son savoir. Qui sait peu ignore aussi très peu. [Roger Pol-Droit]
    Github
    Mon tout premier projet: une bibliothèque de simulation de génétique des populations

  4. #4
    Expert éminent sénior
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2005
    Messages
    5 071
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Conseil

    Informations forums :
    Inscription : Février 2005
    Messages : 5 071
    Points : 12 116
    Points
    12 116
    Par défaut
    Une "Factory" n'est pas forcement template, c'est même assez rarement le cas.
    Si A et B sont constructibles via Factory<X>, c'est qu'ils partagent assez de point commun pour qu'un template (avec quelques spécialisations si nécessaire) puisse construire indifféremment l'un ou l'autre.

    Vous prenez le problème dans le mauvais sens.

    Vous voulez que A et B soient constructibles via un DP Factory, vous devez commencer par spécifier l'API que A et B (et les autres classes constructibles) doivent implémenter pour être constructible.
    Une fois cette API spécifiée, l'implémentation de Factory<X> l'utilise.

    Et les concepteurs des classes A, B et autres, n'ont cas implémenter cette API de construction.

  5. #5
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Salut,
    Citation Envoyé par bacelar Voir le message
    Une "Factory" n'est pas forcement template, c'est même assez rarement le cas.
    Si A et B sont constructibles via Factory<X>, c'est qu'ils partagent assez de point commun pour qu'un template (avec quelques spécialisations si nécessaire) puisse construire indifféremment l'un ou l'autre.

    Vous prenez le problème dans le mauvais sens.

    Vous voulez que A et B soient constructibles via un DP Factory, vous devez commencer par spécifier l'API que A et B (et les autres classes constructibles) doivent implémenter pour être constructible.
    Une fois cette API spécifiée, l'implémentation de Factory<X> l'utilise.

    Et les concepteurs des classes A, B et autres, n'ont cas implémenter cette API de construction.
    Je ne peux que plussoyer!!!

    En fait, si tu veux faire une fabrique template, il faut non pas un paramètre template mais deux, sous une forme qui serait sans doute proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    template<typename Type, typename Policy>
    class Factory{
        std::unique_ptr<A> create() const{
            return Policy().create();
        }
    /* ... */
    };
    où type correspond au "type de base renvoyé par la fabrique" et où polycy correspond à "tout ce qui doit être fait pour construire l'élément demandé".

    Ainsi, le créateur de la classe A devrait fournir non seulement la classe A, mais aussi la politique qui permet la création de cette classse, sous une forme qui pourrait être proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class A{
        /* tout ce que le createur de la classe peut souhaiter */
    };
    struct
    TheMainingfullCreatorForA{
        std::unique_ptr<A> create() const{
            /* tout ce qui doit être fait */
            return std::make_unique<A>(/* paramètres éventuels spécifiques à A */ );
        }
    };
    Maintenant, on pourrait sans doute également envisager de fournir l'interface spécifique pour Policy, sous une forme qui serait sans doute proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    template <typename T>
    ObjectCreator{
        template <typename DERIVED>
        std::unique_ptr<T> create() const;
    };
    De cette manière, il deviendrait possible de fournir une spécialisation (complète) de la ObjectCreator::create renvoyant un std::unique_ptr sur n'importe que type de base (spécialisation de T), mais dont le type réel correspondrait à n'importe quelle classe dérivée (spécialisation de DERIVED), et notre classe Factory pourrait alors prendre la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    template <typename T, typename Policy = OBjectCreator<T>>
    class Factory{
    public:
        template <typename DERIVED
        std::unique_ptr<T> create() const{
            return Policy()::create<DERIVED>();
        }  
    };
    Mais bon, si on en arrive à ce point, on se demande quel est l'intérêt de la fabrique par rapport à celui de la classe que j'ai appelée ObjectCreator... Même si l'on pourrait estimer que le deuxième s'occupera surtout de rassembler et de regrouper des données utiles à la création (DP builder inside )
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  6. #6
    Membre averti Avatar de Seabirds
    Homme Profil pro
    Post-doctoral fellow
    Inscrit en
    Avril 2015
    Messages
    294
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Post-doctoral fellow
    Secteur : Agroalimentaire - Agriculture

    Informations forums :
    Inscription : Avril 2015
    Messages : 294
    Points : 341
    Points
    341
    Par défaut
    Merci pour vos réponses et l'échantillon de code, ça m'a servi pour tout ré-écrire !
    En fait, même si je semble être parvenu à mes fins, peut-être que je n'ai pas vraiment compris ce que je voulais faire.
    Je voulais dire à un objet "je te donne telle stratégie de simulation et telles données, fabrique moi ce dont j'ai besoin".
    Voilà dessous le code que j'ai fait :

    Ce que j'ai appellé "Factory" (peut être à tort) :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    class Factory
    {
    public:
    	template<typename simulation_trait>
    	auto create(typename simulation_trait::data_type const& data) const {
    		return Object<simulation_trait>(data);
    	}
    };
    Ce que j'ai appelé simulation_traits c'est des petites classes de traits dans un namespace :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    namespace simulation_traits
    {
    	struct strategy1 {	using data_type = double; };
    	struct strategy2 { using data_type = unsigned int; };
    }
    L'objet à fabriquer, qui contient en fait deux sous objets dont les types sont dépendants :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    template<typename simulation_trait>
    class Object
    {
    private:
    	using A_type = A<simulation_trait>;
    	using B_type = typename A_type::B_type;
    	using data_type = typename simulation_trait::data_type;
    	A_type m_A;
    	B_type m_B;
    public:
            Object(data_type const& data) : m_A(data), m_B(data) {}
    };
    Et enfin des spécialisations pour les différentes implémentations qui varient selon les stratégies de simulation:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    template<typename Trait>
    class A{};
     
    template<>
    class A<simulation_traits::strategy1> { // implémentation 1};
     
    template<>
    class A<simulation_traits::strategy2> { // implémentation 2};
     
    // et pareil pour B
    Qu'en pensent les experts ? A quel point c'est une usine à gaz ? Pourquoi est-ce que manifestement le nom de Factory est mal choisi ?
    Et enfin, la question initialement posée (même si les choses ont un peu évoluées entre temps) : est-ce que je me plante à ne pas faire hériter les différents A et B d'une même interface ?
    Le débutant, lui, ignore qu'il ignore à ce point, il est fier de ses premiers succès, bien plus qu'il n'est conscient de l'étendue de ce qu'il ne sait pas, dès qu'il progresse en revanche, dès que s'accroît ce qu'il sait, il commence à saisir tout ce qui manque encore à son savoir. Qui sait peu ignore aussi très peu. [Roger Pol-Droit]
    Github
    Mon tout premier projet: une bibliothèque de simulation de génétique des populations

  7. #7
    Membre expert
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2011
    Messages
    739
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hérault (Languedoc Roussillon)

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

    Informations forums :
    Inscription : Juin 2011
    Messages : 739
    Points : 3 627
    Points
    3 627
    Par défaut
    Pour moi, une factory est plus intelligente que ça et retourne un type de base d'un ensemble de classe dérivé. Par exemple, un type Image qui est en fait un Png, Gif, etc. Là, c'est tellement "basique" que my_factory.create = Object.

    Je ne suis pas non plus certain de la pertinence de simulation_traits. Si chaque A est spécialisé sur un trait, les traits ne servent pas, ou alors A est lui-même un trait.

    Citation Envoyé par Seabirds Voir le message
    Est-ce que je me plante à ne pas faire hériter les différents A et B d'une même interface ?
    Interface, c'est plutôt large, cela peut être une interface sur les identifiants et la catégorie des types (avoir tel membre, respecte tel concept ; comme dans la programmation générique) ou une interface sur les types (polymorphisme classique).

    Si les spécialisations de A on une interface publique avec les mêmes types d'entrées et de sorties, tu peux envisager une interface à base de fonctions virtuelles.
    Si au contraire, les types d'entrées et de sorties diffèrent et qu'on ne peut décemment pas les mettre en commun, des templates sont à envisager.

    La frontière est beaucoup plus ténue et rien n'empêche les mélanges, il faudrait plus d'information sur les différents acteurs pour se décider.

  8. #8
    Expert éminent sénior

    Avatar de dragonjoker59
    Homme Profil pro
    Software Developer
    Inscrit en
    Juin 2005
    Messages
    2 031
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 42
    Localisation : France, Bas Rhin (Alsace)

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

    Informations forums :
    Inscription : Juin 2005
    Messages : 2 031
    Points : 11 379
    Points
    11 379
    Billets dans le blog
    10
    Par défaut
    Pour la factory non template, je suis perplexe, j'ai ça chez moi:

    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
     
    template< class Obj
        , class Key
        , class PtrType = std::shared_ptr< Obj >
        , class Creator = std::function< PtrType() >
        , class Predicate = std::less< Key > >
    class Factory
    {
    protected:
        using ObjPtr = PtrType;
        using CreatorMap = std::map< Key, Creator, Predicate >;
     
    public:
        Factory() = default;
        ~Factory() = default;
     
        void Register( Key const & p_key, Creator p_creator )
        {
            m_registered[p_key] = p_creator;
        }
     
        void Unregister( Key const & p_key )
        {
            auto l_it = m_registered.find( p_key );
     
            if ( l_it != m_registered.end() )
            {
                m_registered.erase( p_key );
            }
        }
     
        template< typename ... Parameters >
        ObjPtr Create( Key const & p_key, Parameters && ... p_params )const
        {
            ObjPtr l_return;
            auto l_it = m_registered.find( p_key );
     
            if ( l_it != m_registered.end() )
            {
                l_return = l_it->second( std::forward< Parameters >( p_params )... );
            }
            else
            {
                throw std::runtime_error{ "Unknown object type" };
            }
     
            return l_return;
        }
     
    protected:
        CreatorMap m_registered;
    };
    Et je n'ai pas encore eu besoin de plus.
    Si vous ne trouvez plus rien, cherchez autre chose...

    Vous trouverez ici des tutoriels OpenGL moderne.
    Mon moteur 3D: Castor 3D, presque utilisable (venez participer, il y a de la place)!
    Un projet qui ne sert à rien, mais qu'il est joli (des fois) : ProceduralGenerator (Génération procédurale d'images, et post-processing).

  9. #9
    Membre averti Avatar de Seabirds
    Homme Profil pro
    Post-doctoral fellow
    Inscrit en
    Avril 2015
    Messages
    294
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Post-doctoral fellow
    Secteur : Agroalimentaire - Agriculture

    Informations forums :
    Inscription : Avril 2015
    Messages : 294
    Points : 341
    Points
    341
    Par défaut
    Merci pour vos réponses !

    Si les spécialisations de A on une interface publique avec les mêmes types d'entrées et de sorties, tu peux envisager une interface à base de fonctions virtuelles.
    Si au contraire, les types d'entrées et de sorties diffèrent et qu'on ne peut décemment pas les mettre en commun, des templates sont à envisager.
    Et quand on sait pas trop et qu'on navigue à vue, j'imagine qu'on opte aussi pour les templates ?

    Pour la factory non template, je suis perplexe, j'ai ça chez moi:
    Oh, merci pour le parameter pack, j'ai du mal avec la syntaxe, ça fait du bien d'avoir une piqure de rappel de temps à autres
    Je n'ai pas trop bien compris le type Creator : dans le std::function, y'a quoi ? Une fonction libre ou le constructeur ?
    Le débutant, lui, ignore qu'il ignore à ce point, il est fier de ses premiers succès, bien plus qu'il n'est conscient de l'étendue de ce qu'il ne sait pas, dès qu'il progresse en revanche, dès que s'accroît ce qu'il sait, il commence à saisir tout ce qui manque encore à son savoir. Qui sait peu ignore aussi très peu. [Roger Pol-Droit]
    Github
    Mon tout premier projet: une bibliothèque de simulation de génétique des populations

  10. #10
    Expert éminent sénior

    Avatar de dragonjoker59
    Homme Profil pro
    Software Developer
    Inscrit en
    Juin 2005
    Messages
    2 031
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 42
    Localisation : France, Bas Rhin (Alsace)

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

    Informations forums :
    Inscription : Juin 2005
    Messages : 2 031
    Points : 11 379
    Points
    11 379
    Billets dans le blog
    10
    Par défaut
    Généralement j'utilise une fonction membre statique, et je mets le constructeur en protégé
    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
    class A
    {
    protected:
      A()
      {
      }
     
    public:
      virtual void hello()const = 0;
    };
     
    class B
      : public A
    {
    private:
      B()
        : A{}
      {
      }
     
    public:
      void hello()const override
      {
        std::cout << "B" << std::endl;
      }
     
      static std::shared_ptr< A > Create()
      {
        return std::make_shared< B >();
      }
    };
     
    class C
      : public A
    {
    private:
      C()
        : A{}
      {
      }
     
    public:
      void hello()const override
      {
        std::cout << "C" << std::endl;
      }
     
      static std::shared_ptr< A > Create()
      {
        return std::make_shared< C >();
      }
    };
     
    int main()
    {
      Factory<int, A> factory;
      factory.Register( 1, &B::Create );
      factory.Register( 2, &C::Create );
      auto value = factory.Create( 1 );
      value->hello();
    }
    Si vous ne trouvez plus rien, cherchez autre chose...

    Vous trouverez ici des tutoriels OpenGL moderne.
    Mon moteur 3D: Castor 3D, presque utilisable (venez participer, il y a de la place)!
    Un projet qui ne sert à rien, mais qu'il est joli (des fois) : ProceduralGenerator (Génération procédurale d'images, et post-processing).

  11. #11
    Membre averti Avatar de Seabirds
    Homme Profil pro
    Post-doctoral fellow
    Inscrit en
    Avril 2015
    Messages
    294
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Post-doctoral fellow
    Secteur : Agroalimentaire - Agriculture

    Informations forums :
    Inscription : Avril 2015
    Messages : 294
    Points : 341
    Points
    341
    Par défaut
    Ok je comprends mieux !
    J'ai mis la discussion comme résolue, avec tous les éléments qu'il y a là j'ai de quoi faire pour le prochain polissage de code
    Merci à tous pour vos contributions !
    Le débutant, lui, ignore qu'il ignore à ce point, il est fier de ses premiers succès, bien plus qu'il n'est conscient de l'étendue de ce qu'il ne sait pas, dès qu'il progresse en revanche, dès que s'accroît ce qu'il sait, il commence à saisir tout ce qui manque encore à son savoir. Qui sait peu ignore aussi très peu. [Roger Pol-Droit]
    Github
    Mon tout premier projet: une bibliothèque de simulation de génétique des populations

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

Discussions similaires

  1. [MySQL] afficher une seule fois un titre pour différents clients qui ont le même titre
    Par Prisss dans le forum PHP & Base de données
    Réponses: 11
    Dernier message: 01/12/2010, 10h52
  2. Réponses: 5
    Dernier message: 29/11/2010, 15h49
  3. Réponses: 9
    Dernier message: 08/01/2008, 10h55

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