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 :

templates et héritage


Sujet :

Langage C++

  1. #1
    Futur Membre du Club
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Janvier 2016
    Messages
    9
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 67
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

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

    Informations forums :
    Inscription : Janvier 2016
    Messages : 9
    Points : 5
    Points
    5
    Par défaut templates et héritage
    bonjour, c'est mon 1er post ici, alors bonne année à tous les codeurs !
    Je n'arrive pas à trouver de solution au problème suivant.

    J'ai une hiérarchie de classes. Je dit hiérarchie car chaque dérivation ajoute des services au parent, on est bien dans le "est-un".
    Toutes ces classes utilisent une std::list de structures, des méthodes de manipulation de la liste sont communes et serait bien placées dans la classe de base ancêtre.
    Mais chaque dérivée utilise sa propre struct, différente de celle qu'utilise son parent.
    J'ai pensé faire une classe de base abstraite telle que :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    class Base<T> {
      std::list<T> mList;
      // methods for manipulating the list
    };
    et créer une dérivée par :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    struct SA { ... };
    class A : public Base<SA> { ... }
    Le problème est que je ne peux plus dériver cette classe en lui faisant utiliser des struct SB à la place des SA.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    class B : public A { ... } // Base::mList est une list<SA>, moi je voulais SB
    class C : public Base<SC> { ... } // mList OK, mais je ne derive pas de A
    J'ai tourné ça dans tous les sens, essayé d'utiliser une classe annexe, utilisé l'héritage multiple, cherché sur le Net et je n'y arrive pas !
    Si quelqu'un voit comment faire...

    a+
    Gilles.

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Salut,

    Tu pars déjà d'un "bon point de vue" en réfléchissant à tes classes d'après les services qu'elles rendent plutôt qu'en y réfléchissant sous la forme des données qu'elles manipulent. Mais, voilà, ce n'est pas forcément parce que deux classes exposent les mêmes services (quitte à ce que l'une des deux en exposent quelques uns de plus) qu'il y a forcément une relation IS-A entre les deux (ni même que le LSP sera automatiquement respecté si on essaye de créer cette relation).

    Car les règles à respecter pour pouvoir envisager un héritage publique sont assez strictes : il faut que les invariants valides pour la classe de base soient également valides pour la classe dérivée. Or, si la classe de base utilise une donnée A en interne, c'est considéré comme "un invariant de la classe de base", qui doit donc être respecté dans la classe dérivée.

    Si bien que l'on peut très bien considérer que la classe dérivée manipule en interne des données de type B en plus des données de type A (que la classe de base manipule), et que le LSP est respecté, mais que, si la classe dérivée ne manipule que des données de type B, le LSP ne sera plus respecté

    A ce stade, la solution passe par le respect d'un autre des principes SOLID : l'ISP (pour Interface Segregation Principle ou principe de ségrégation des interfaces).

    L'idée est de veiller à chaque fois à garder les interfaces les plus simples possibles, si bien que ce que tu identifie ici comme ta classe "Base" deviendrait en réalité une "interface" te permettant "de manipuler une collection d'éléments dont on ignore tout jusqu'à présent", par exemple, en prenant une forme proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    /* Interface permettant de gérer les collections d'éléments
     *
     * utilise le CRTP pour éviter d'avoir des hiérarchies trop importantes
     * tparam CRTP type dérivé de l'interface (pour la facilité par la suite)
     * tparam DATA type des données à placer dans la collection
     */
    template<typename CRTP, typename DATA>
    class ICollectionHolder{
    public:
       /* les fonctions d'ajout, de suppression, de recherche,  etc
        * qui manipulent toutes plus ou moins toujours des données de type
        * DATA
        * peut être un ou deux alias(es) de type sur les itérateurs ?
        */
    protected:
        /* constructeur et destructeur (non virtuel) protégés
         * pour nous forcer à utiliser l'héritage
         */
        ICollectionHolder(CRTP & crtp):crtp_(crtp){}
        ~ICollectionHolder(){}
    private:
        /* nous permet, en cas de besoin, d'accéder à l'instance du type
         * réel qui implémente l'interface
         */
        CRTP & crtp_;
        /* les éléments qu'il faut maintenir ensembles */
        std::list<DATA> datas_;
    };
    De la même manière, tu pourrais regrouper les différents ensembles de fonctionnalités dans différentes interfaces, par exemple :
    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
     
    /* utilise le CRTP pour éviter les hiérarchies monolithiques
      */
    template <typename CRTP>
    class IDrawable{
    public:
        void draw() const; // fournir une spécialisation de cette fonction pour chaque
                                     // type susceptible d'être tracé
     
    protected:
        /* constructeur et destructeur (non virtuel) protégés
         * pour nous forcer à utiliser l'héritage
         */
        IDrawable(CRTP & crtp):crtp_(crtp){}
        ~IDrawable(){}
    private:
        /* nous permet, en cas de besoin, d'accéder à l'instance du type
         * réel qui implémente l'interface
         */
        CRTP & crtp_;
    };
    /* interface permettant le déplacement des éléments qui peuvent l'être
      * utilise aussi le CRTP
      */
    template <typename CRTP>
    class IMovable{
    public:
        /* à pmplémenter pour chaque type implémentant cette interface */
        void moveTo(Position const & newPos);
        // OU   - OU   -   OU
        void move(Type diffx, Type diffY /* , Type diffZ ??? */);
     
    protected:
        /* constructeur et destructeur (non virtuel) protégés
         * pour nous forcer à utiliser l'héritage
         */
        IMovable(CRTP & crtp):crtp_(crtp){}
        ~IMovable(){}
    private:
        /* nous permet, en cas de besoin, d'accéder à l'instance du type
         * réel qui implémente l'interface
         */
        CRTP & crtp_;
        Position pos_;
    };
    /* on pourrait encore envisager d'autres interfaces, par exemple, la possibilité de regrouper
      * IMovable et IDrawable
      */
    template <typename CRTP>
    class MoveAndDraw : public IDrawable<CRTP>,
                                     public IMovable<CRTP>{
    protected:
        MoveAndDraw(CRTP & crtp):IDrawable<CRTP>(crtp), IMovable<CRTP>(crtp){}
    };
    Enfin, si cela a du sens, tu peux envisager une hiérarchie de classe proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    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 Base : public ICollectionHolder<Base, SomeType>{
    public:
        Base():ICollectionHolder<Base, SomeType>(*this){
        }
        virtual ~Base();
    };
    /* un élément qui maintient une collection de SomeType, qui peut être tracé, mais pas déplacé
     */
    class Derived1 : public Base,
                             public IDrawable<Derived1>{
    public:
        Derived1():Base(),IDrawable<Derived1>(*this){
        }
    };
    /* un élément qui maintient une collection de SomeType qui peut être déplacé mais pas tracé
     */
    class Derived2 : public Base,
                             public IMovable<Derived2>{
    public:
        Derived2():Base(),IMovable<Derived2>(*this){
        }
    };
    /* un élément qui maintient une collection de SomeType qui peut être déplacé et tracé
     */
    class Derived3 : public Base,
                             public MoveAndDraw<Derived3>{
    public:
        Derived3():Base(), MoveAndDraw<Derived3>(*this){
        }
    };
    /* un toute autre hiérarchie de classes... tous les élément maintiennent une collection de OtherType!!!
     */
    class OTBase : public ICollectionHolder<OtherType>{
    public:
        OTBase():ICollectionHolder<OtherType>(*this){
        }
        virtual ~OTBase();
    };
     
    /* un élément qui maintient une collection de OtherType, qui peut être tracé, mais pas déplacé
     */
    class OTDerived1 : public OTBase,
                             public IDrawable<OTDerived1>{
    public:
        OTDerived1():Base(),IDrawable<OTDerived1>(*this){
        }
    };
    /* un élément qui maintient une collection de OtherType qui peut être déplacé mais pas tracé
     */
    class OTDerived2 : public OTBase,
                             public IMovable<OTDerived2>{
    public:
        OTDerived2 ():Base(),IMovable<OTDerived2 >(*this){
        }
    };
    /* un élément qui maintient une collection de OtherType qui peut être déplacé et tracé
     */
    class OTDerived3 : public OTBase,
                             public MoveAndDraw<OTDerived3>{
     
    public:
        OTDerived3  ():Base(),MoveAndDraw<OTDerived3 >(*this){
        }
    };
    /* un élément qui peut être tracé, mais qui ne contient aucune collection d'objet et qui 
     * ne peux pas être déplacé
     */
    class NoItemD : public IDrawable<NoItemD>{
    public:
        NoItemD():IDrawable<NoItemD>(*this){
       }
     
    };
    /* etc */
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  3. #3
    Futur Membre du Club
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Janvier 2016
    Messages
    9
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 67
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

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

    Informations forums :
    Inscription : Janvier 2016
    Messages : 9
    Points : 5
    Points
    5
    Par défaut
    merci pour cette réponse détaillée.
    Bon, je ne vois pas trop comment l'appliquer à mon cas, je n'ai pas l'habitude de ces pirouettes :-)
    Et il y a quelque chose qui me gêne : je ne peux pas faire une dérivée d'une dérivée.

    Ma classe de base est une AudioSource, elle est abstraite. Elle fournit des blocs d'audio à un moteur audio.
    Je la dérive d'abord en SeekableAudioSource, capable de se "déplacer" dans un fichier audio ( SetPosition(), Rewind()...)
    Je la dérive ensuite en CyclableAudioSource, permettant de lire le fichier audio en boucle sur une plage donnée ( SetLimits(), SetCycle(on/off)...)
    Enfin je la dérive en StretchableAudioSource, capable de changer la vitesse de lecture (SetSpeed()...)

    Chaque niveau apporte de nouveaux membres. Si StretchableAudioSource ne dérive pas de CyclableAudioSource, je dois recopier les méthodes du Cyclable dans le Stretchable ?

    J'ai omis de préciser que les struct mises en liste ne sont pas quelconques : une dérivée utilise la struct de son parent, augmentée de nouveaux membres.
    Ainsi la classe Seekable (la + haute) utilise
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    struct SeekableEvent {
      double time;
      int position;
    }
    le Cyclable, dérivé de Seekable, utilise
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    struct CyclableEvent {
      double time;
      int position;
      int position_min;
      int position_max;
    }
    Il apparaît que les struct utilisées par la hiérarchie des classes présentent elles-mêmes une hiérarchie. Ne peut-on tirer parti de cela ?
    (j'en tire déjà profit en ayant une seule méthode template pour, par ex., supprimer les éléments de la liste dont le champ "time" est trop vieux)

  4. #4
    Rédacteur/Modérateur


    Homme Profil pro
    Network game programmer
    Inscrit en
    Juin 2010
    Messages
    7 115
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : Canada

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 115
    Points : 32 967
    Points
    32 967
    Billets dans le blog
    4
    Par défaut
    Pourquoi compliquer autant ?
    Pourquoi ne pas avoir une seule classe AudioSource avec toutes ces fonctionnalités ?
    Quand j'utilisais FMOD, il n'y a qu'une seule classe Audio qui possède tout ça (position, rewind, loop ..), je vois pas bien l'intérêt de découper ainsi.

    Avec de telles structures y'a bien des solutions à base de void* et cast, à l'ancienne en C, mais la question sur l'intérêt de la chose l'emporte dans mon esprit.

    Mais un truc comme
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    template<class STRUCT> class Base {};
    template<class SEEK_STRUCT = SeekableEvent> class Seekable : public Base<SEEK_STRUCT> {];
    template<class CYCLABLE_STRUCT = CyclableEvent> class Cyclable : public Seekable<CYCLABLE_STRUCT> {};
    pourrait marcher ?
    Mais j'ai l'impression que tu compliques inutilement trop alors qu'un son possède toutes ses cractéristiques en général.
    Pensez à consulter la FAQ ou les cours et tutoriels de la section C++.
    Un peu de programmation réseau ?
    Aucune aide via MP ne sera dispensée. Merci d'utiliser les forums prévus à cet effet.

  5. #5
    Expert confirmé
    Homme Profil pro
    Étudiant
    Inscrit en
    Juin 2012
    Messages
    1 711
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juin 2012
    Messages : 1 711
    Points : 4 442
    Points
    4 442
    Par défaut
    @Bousk, le problème serait avec une fonction prenant un Seekable en argument. On pourrait pas lui donner un Cyclable vu qu'ils n'ont pas de base commune.

    Sinon, oui une seule classe contenant tout pourrait être une solution.

  6. #6
    Rédacteur/Modérateur


    Homme Profil pro
    Network game programmer
    Inscrit en
    Juin 2010
    Messages
    7 115
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : Canada

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 115
    Points : 32 967
    Points
    32 967
    Billets dans le blog
    4
    Par défaut
    Citation Envoyé par Iradrille Voir le message
    @Bousk, le problème serait avec une fonction prenant un Seekable en argument. On pourrait pas lui donner un Cyclable vu qu'ils n'ont pas de base commune.
    En ce cas, pourquoi ces structures n'ont pas une vraie hiérarchie en héritage ? Plutôt que de recopier les membres du "parent".
    Pensez à consulter la FAQ ou les cours et tutoriels de la section C++.
    Un peu de programmation réseau ?
    Aucune aide via MP ne sera dispensée. Merci d'utiliser les forums prévus à cet effet.

  7. #7
    Futur Membre du Club
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Janvier 2016
    Messages
    9
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 67
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

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

    Informations forums :
    Inscription : Janvier 2016
    Messages : 9
    Points : 5
    Points
    5
    Par défaut
    Citation Envoyé par Bousk Voir le message
    Pourquoi compliquer autant ?
    Pourquoi ne pas avoir une seule classe AudioSource avec toutes ces fonctionnalités ?
    En fait, j'écris une librairie que je compte publier en open-source, comme composant wxCode.
    La librairie impose la classe virtuelle AudioSource, ce sera à l'utilisateur de ma librairie d'écrire sa classe dérivée de AudioSource.

    Avant de publier ça, d'une part je voudrais tester le concept, d'autre part fournir quelques classes AudioSource comme exemple.
    C'est mal barré s'il faut utiliser des templates récursifs ou autres constructions de haut niveau, les utilisateurs (s'il y en a ) seront découragés...
    Je pourrais me contenter de fournir la SeekableAudioSource de 1er niveau, qui gère tranquillement sa liste de SeekableEvent, sans templates. Si l'utilisateur part de cet exemple, il ne pourra pas dériver cette classe, c'est pas sympa.

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par biggil Voir le message
    merci pour cette réponse détaillée.
    Bon, je ne vois pas trop comment l'appliquer à mon cas, je n'ai pas l'habitude de ces pirouettes :-)
    Et il y a quelque chose qui me gêne : je ne peux pas faire une dérivée d'une dérivée.
    Bien sur que si, que tu peux faire une dérivée de dérivée :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    template <typename CRTP
    class ISomeOtherInterface{
    public:
       void doSomething();
    protected:
        ISomeOtherInterface(CRTP & crtp):crtp_(crtp){}
        ~ISomeOtherInterface(){}
    private:
        CRTP & crtp_;
    };
    class DerivedX : public Derived1,
                             public ISomeOtherInterface<DerivedX>{
    };
    Ma classe de base est une AudioSource, elle est abstraite. Elle fournit des blocs d'audio à un moteur audio.
    Je la dérive d'abord en SeekableAudioSource, capable de se "déplacer" dans un fichier audio ( SetPosition(), Rewind()...)
    Je la dérive ensuite en CyclableAudioSource, permettant de lire le fichier audio en boucle sur une plage donnée ( SetLimits(), SetCycle(on/off)...)
    Enfin je la dérive en StretchableAudioSource, capable de changer la vitesse de lecture (SetSpeed()...)
    OK, au moins, maintenant, on sait de quoi l'on parle

    Chaque niveau apporte de nouveaux membres. Si StretchableAudioSource ne dérive pas de CyclableAudioSource, je dois recopier les méthodes du Cyclable dans le Stretchable ?
    Ben, si tous les élément "cyclable" sont "seekable" et qu'il présentent les mêmes services, autant faire en sorte que cyclable dérive de seekable
    J'ai omis de préciser que les struct mises en liste ne sont pas quelconques : une dérivée utilise la struct de son parent, augmentée de nouveaux membres.
    Ainsi la classe Seekable (la + haute) utilise
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    struct SeekableEvent {
      double time;
      int position;
    }
    le Cyclable, dérivé de Seekable, utilise
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    struct CyclableEvent {
      double time;
      int position;
      int position_min;
      int position_max;
    }
    AAhhh, voilà ton erreur!!! Tu crées deux structures qui contiennent des données similaires et qui t'obligent donc à choisir entre ces deux structures...

    Pourquoi ne pas, simplement, avoir la structure SekableEvent telle qu'elle est maintenant et faire en sorte que ta structure CyclableEvent ne rajoute que les données manquantes pour être en mesure de fournir les services attentus
    tu aurais donc:
    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
    struct SeekableEvent {
      double time;
      int position;
    };
    struct CyclableEvent {
      int position_min;
      int position_max;
    };
    class AudioSource{
        /*...*/
    };
    class SeekableAudioSource : public AudioSource{
    /*...*/
    private:
        SeekableEvent seek_;
    };
    class CyclableAudioSource : public SeekableAudioSource{
    /* ...*/
    private:
        CyclableEvent  cycle_;
    };
    class StretchableAudioSource : public CyclableAudioSource{
    /* ... */
    private:
        Type speed_;
    };
    On pourrait même prévoir les interfaces sous la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    template <typename CRTP>
    class ISeekable {
    /* ... */
    private:
        SeekableEvent seek_;
    };
    template <typename CRTP>
    class ICyclable {
        static_assert(std::is_base_of<ISeekable<CRTP>,CRTP>,
                             "Cyclable items have to be seekable");
        /*...*/
    private:
        CyclableEvent  cycle_;
    };
    template <typename CRTP>
    class ISpeedable{
     
        static_assert(std::is_base_of<ICyclable<CRTP>,CRTP>,
                             "speedable items have to be cyaclable");
    /* ... */
    private:
        Type speed_;
    };
    Ceci dit, je suis d'accord avec les autres: tu n'as besoin que d'une seule classe : AudioSource qui expose l'ensemble des services que tu es en droit d'attendre de la part de ce type de donnée (autrement dit : les services exposés par l'ensemble des interfaces que je viens de présenter). Il n'y a absolument aucune raison pour essayer de créer des classes qui en dérivent

    Par contre, je peux effectivement comprendre que cela t'embête plus ou moins d'avoir une classe unique se présentant sous une forme proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class AudioSource{
    public:
        /* tout ce qui a trait au chargement des données */
        /* tout ce qui a trait au déplacement dans la source */
        /* tout ce qui a trait au passage en boucle */
        /* tout ce qui a trait au changement de vitesse*/
        /* ... */
    private:
        double time;
        int position;
        int position_min;
        int position_max;
        Type speed;
    };
    car je peux comprendre que cela risquerait de faire énormément de fonctions, et que l'on puisse "avoir peur d'en oublier". C'est, d'une certaine manière, tout l'intérêt des interfaces : on peut se concentrer sur un nombre de services particulièrement restreint, et il n'y a absolument rien qui nous interdise de décider qu'une unique classe puisse implémenter toutes les interfaces

    Il faut juste arriver à répondre en toute honnêteté à la question : n'est on pas occupés à basculer dans l'over-engeeniering
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par biggil Voir le message
    En fait, j'écris une librairie que je compte publier en open-source, comme composant wxCode.
    La librairie impose la classe virtuelle AudioSource, ce sera à l'utilisateur de ma librairie d'écrire sa classe dérivée de AudioSource.
    J'ai envie de dire : raison de plus !!! Tu rends ta classe AudioSource abstraite si tu veux, mais tu dois absolument veiller à ce qu'elle expose tous les services que l'on est en droit d'attendre de la part d'une source audio!!!

    Au final, si l'on prend la hiérarchie à base de AudioSource que j'ai présentée dans mon intervention précédente, la seule classe dont l'utilisateur de ta bibliothèque aura besoin (quitte à en faire une classe abstraite, encore une fois) correspond à StretchableAudioSource, qui devrait être "simplement" connue sous le nom de AudioSource
    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

  10. #10
    Membre chevronné

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Avril 2013
    Messages
    610
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

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

    Informations forums :
    Inscription : Avril 2013
    Messages : 610
    Points : 1 878
    Points
    1 878
    Billets dans le blog
    21
    Par défaut
    Juste une remarque en passant, et sans prétendre planer à la hauteur du CRTP:

    Code C++ : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    tempate <class T>
    class Base { ... };
     
    template <class U>
    class TA : public Base<U> { ... };
    using A = TA<SA>;
     
    template <class V>
    class TB : public TA<V> { ... };
    using B = TB<SB>;

    Est-ce que cela ne permettrait pas de résoudre ton problème? Il me faudrait un peu plus de code pour être sûr...

  11. #11
    Rédacteur/Modérateur


    Homme Profil pro
    Network game programmer
    Inscrit en
    Juin 2010
    Messages
    7 115
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : Canada

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 115
    Points : 32 967
    Points
    32 967
    Billets dans le blog
    4
    Par défaut
    En fait tu devrais avoir une seule classe AudioSource mais chaque service diffère selon le format du fichier audio.
    Donc un héritage, ou template, pour le format (parce qu'on ne charge/rewind pas du tout de la même manière un ogg qu'un mp3, etc)
    Pensez à consulter la FAQ ou les cours et tutoriels de la section C++.
    Un peu de programmation réseau ?
    Aucune aide via MP ne sera dispensée. Merci d'utiliser les forums prévus à cet effet.

  12. #12
    Futur Membre du Club
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Janvier 2016
    Messages
    9
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 67
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

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

    Informations forums :
    Inscription : Janvier 2016
    Messages : 9
    Points : 5
    Points
    5
    Par défaut
    Citation Envoyé par koala01 Voir le message
    Ceci dit, je suis d'accord avec les autres: tu n'as besoin que d'une seule classe : AudioSource qui expose l'ensemble des services...

    J'ai envie de dire : raison de plus !!! Tu rends ta classe AudioSource abstraite si tu veux, mais tu dois absolument veiller à ce qu'elle expose tous les services que l'on est en droit d'attendre de la part d'une source audio!!!
    Là je suis pas d'accord. Il y a des tas de sources audio possibles:
    - Un synthétiseur ne fait que des calculs, il n'est pas "seekable", il ne gère pas de l'audio préexistant,
    - Un Loopback recopie l'entée audio sur la sortie, avec éventuellement des calculs DSP entre les deux,
    - Une radio Internet n'est pas "seekable" ...
    La classe AudioSource que j'impose est volontairement minimale, elle rend tous les services qu'une source audio quelconque attend pour passer son audio à la carte son, pas pour élaborer l'audio lui-même.
    Les classes dérivées dont j'ai parlé ont en commun
    - de travailler sur un "gros" bloc d'audio préexistant, qu'elles passent par petits bouts à la carte son.
    - de vouloir connaître le décalage (variable) entre l'instant où elle passent un buffet d'audio au moteur et l'instant où ce buffet est "rendu" par la carte son, de façon à afficher correctement la position courante.

    J'ai déjà une StretchableAudioSource qui marche, avec tout dedans, ce n'est pas le problème. Il y a au coeur un mécanisme compliqué de gestion de liste pour connaître le décalage, et j'aurais voulu fournir avec ma librairie une classe dérivée de AudioSource qui implémente ce mécanisme, juste ce mécanisme, et laisser le reste à l'utilisateur.
    Mais voilà, les dérivées doivent, pour utiliser ce mécanisme, lui faire gérer leurs données propres. Et c'est pas simple.

    Bon, je vais manger beaucoup de poisson...

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par Bousk Voir le message
    En fait tu devrais avoir une seule classe AudioSource mais chaque service diffère selon le format du fichier audio.
    Donc un héritage, ou template, pour le format (parce qu'on ne charge/rewind pas du tout de la même manière un ogg qu'un mp3, etc)
    En fait, la classe AudioSource doit représenter une abstraction correspondant à "n'importe quel type de donnée audio" (quel que soit le format d'origine de celle-ci), si bien que la distinction entre les différents formats utilisables doit se faire au niveau de l'abstraction qui permet... d'acquérir une instance de AudioSource (sans doute en chargeant un fichier dans un format connu).

    Dés lors on aurait:
    AudioSource : "format indépendant"
    AudioLoader et 1 niveau d'héritage : classe "format dépendant" permettant de charger les différents formats, 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
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    class AudioLoader{
    public:
        virtual AudioSource load(std::string const & filename) const;
    };
    class OggLoader : public AudioLoader{
    public:
        AudioSource load(std::string const & filename) const{
            // traitement spécifique au format ogg
        }
    };
    class WavLoader : public AudioLoader{
    public:
        AudioSource load(std::string const & filename) const{
            // traitement spécifique au format Wav
        }
    };
    class Mp3Loader : public AudioLoader{
    public:
        AudioSource load(std::string const & filename) const{
            // traitement spécifique au format mp3
        }
    };
    /* peut être encore d'autres ;) */
    voire, même, nous pourrions sans doute envisager d'autres solutions qui auraient toutes pour objectif de distinguer les différentes procédures de chargement du fichier, mais une chose est sure : la classe AudioSource n'a, a priori, absolument pas à s'inquiéter des procédures suivies pour récupérer les données (SRP oblige )
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par biggil Voir le message
    Là je suis pas d'accord. Il y a des tas de sources audio possibles:
    - Un synthétiseur ne fait que des calculs, il n'est pas "seekable", il ne gère pas de l'audio préexistant,
    - Un Loopback recopie l'entée audio sur la sortie, avec éventuellement des calculs DSP entre les deux,
    - Une radio Internet n'est pas "seekable" ...
    Attends, parce que je viens de me rendre compte que l'on ne parle peut être pas forcément de la même chose...

    Je considérais la classe AudioSource comme étant la classe qui représentait les données audio. Au vu de ce que tu as écrit, j'ai l'impression que tu considère la classe AudioSource comme étant... la source des données audio.

    Mais ce sont deux choses différentes, qui doivent être modélisées par deux classes totalement différentes!!! D'un coté, tu as une classe qui doit modéliser les données audio et qui doit fournir tous les services dont on a parlé jusqu'à présent en s'en foutant pas mal de l'origine de ces données, de l'autre, tu as plusieurs classes (dans une hiérarchie) qui doivent... fournir les données audio et qui se foutent pas mal de ne pas forcément avoir l'utilité de tous les services que la première classe peut rendre
    La classe AudioSource que j'impose est volontairement minimale, elle rend tous les services qu'une source audio quelconque attend pour passer son audio à la carte son, pas pour élaborer l'audio lui-même.
    Cela nous oblige à nous poser quelques questions importantes, comme :
    • sous quelle forme l'utilisateur de ta bibliothèques va-t-il gérer les différentes sources
    • Est-ce que l'utilisateur de ta bibliothèque pourra -- ce n'est qu'un exemple -- faire cohabiter une radio (qui ne doit pas permettre de retourner en arrière ou de passer en boucle) et un lecteur de CD (qui permet les deux)


    Si ta classe AudioSource est minimaliste, la réponse à la première question est "sous la forme d'une instance du type réel (le plus dérivé possible) de la source et la réponse à la deuxième question est, tout simplement "non".

    C'est un choix qui peut s'avérer cohérent, mais comment l'utilisateur de ta bibliothèque pourra-t-il faire s'il veut créer un "lecteur de média" qui permette de choisir entre une radio, un flux réseau, un lecteur de CD ou un lecteur de cassettes Tu ne vas pas vraiment lui faciliter la vie

    D'un autre coté, faire en sorte que AudioSource expose tous les services concernés ne rendrait pas vraiment les choses plus faciles, vu que -- effectivement -- une radio ne devrait pas exposer des services comme "rewind" ou "cycle"
    Les classes dérivées dont j'ai parlé ont en commun
    - de travailler sur un "gros" bloc d'audio préexistant, qu'elles passent par petits bouts à la carte son.
    - de vouloir connaître le décalage (variable) entre l'instant où elle passent un buffet d'audio au moteur et l'instant où ce buffet est "rendu" par la carte son, de façon à afficher correctement la position courante.

    J'ai déjà une StretchableAudioSource qui marche, avec tout dedans, ce n'est pas le problème. Il y a au coeur un mécanisme compliqué de gestion de liste pour connaître le décalage, et j'aurais voulu fournir avec ma librairie une classe dérivée de AudioSource qui implémente ce mécanisme, juste ce mécanisme, et laisser le reste à l'utilisateur.
    Mais voilà, les dérivées doivent, pour utiliser ce mécanisme, lui faire gérer leurs données propres. Et c'est pas simple.
    Je crois que tout le problème vient de là : il te manque une abstraction essentielle : celle qui représente les données audio en elle-mêmes sous la forme d'un "flux" que tu puisse manipuler ... A vrai dire, comme je l'ai déjà signalé, j'étais parti sur l'idée que c'était ce flux que tu essayais de modéliser, et non les outils qui permettent de créer ce flux
    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

  15. #15
    Futur Membre du Club
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Janvier 2016
    Messages
    9
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 67
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

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

    Informations forums :
    Inscription : Janvier 2016
    Messages : 9
    Points : 5
    Points
    5
    Par défaut
    bon, voilà ma classe AudioSource de base :
    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
    class AudioFormat {
    public:
              //! blank constructor
              AudioFormat ();
              //! full constructor
              AudioFormat ( int a_type, int nb_channels );
              //! The sample encoding. (AE_FORMAT_INT16, AE_FORMAT_INT24, AE_FORMAT_FLOAT...)
         int  type;
              //! The number of audio channels.
         int  nbchannels;
    };
    class AudioEngine;
    class AudioSource {
    public:
                   //! With this constructor, SetFormat() must be called before use.
                   AudioSource ();
                   //! Please give a valid format.
                   AudioSource ( const AudioFormat& format );
                   //! destructor.
         virtual  ~AudioSource ();
                   //! Read the format
    const AudioFormat& GetFormat () const { return mFormat; }
                   //! Set the format
             void  SetFormat ( const AudioFormat& format) { mFormat = format; }
                   //! Connected
     virtual bool  Connected ( const AudioEngine* engine );
     
                   // --- Hooks
                   //! Callback used to produce audio
     virtual  int  Produce ( void* where, int frames, double tDAC, double tcurr ) = 0;
                   //! Callback used when no audio is needed.
     virtual void  DoNotProduce ( int frames, double tDAC, double tcurr );
     
    protected:
      AudioFormat  mFormat;
    };
    Son rôle est :
    1 - de définir le format du flux audio, c-à-d l'encodage des samples (sur un int16, un float..) et un nombre de canaux,
    2 - de fournir une méthode Produce() qui sera appelée par le moteur (AudioEngine) à intervalles + ou - réguliers.
    D'où vient l'audio ? C'est volontairement non-précisé, pour être le + large possible.

    Les classes Seekable, Cyclable... dont j'ai parlé ne se préoccupent en aucun cas de lire des fichiers ogg ou mp3 !
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    class SeekableAudioSource : public AudioSource {
    public:
      SeekableAudioSource ( const AudioFormat& format, int nbFrames, const void* audio );
    Comme on peut voir, l'audio (venant d'un fichier) est chargé préalablement à la construction de l'objet, et son adresse et sa taille sont passées au ctor. Bin oui, je charge tout le WAV en mémoire d'un coup, ça pèse 10MO/minute, dans les Gigas de RAM d'aujourd'hui c'est pas grand chose.

    Donc oui, une AudioSource est la source des données audio ... du point de vue du moteur audio.
    Si l'utilisateur veut gérer plusieurs types de media, il peut créer tout autant de classes dérivées, sachant qu'il ne pourra en utiliser qu'une seule à la fois. S'il veut mixer deux fichiers, il créera une AudioSource adéquate, je lui conseillerais de charger ses fichiers en mémoire avant d'instancier sa classe. Pour capter une radio, je lui conseillerais de lancer un thread qui va chercher l'audio sur le réseau.
    Dans tous les cas je conseille de décharger l'AudioSource dérivée de la tâche "d'aller chercher" un audio préexistant.
    En effet Produce() est appelée par un thread "système" a forte priorité et ne doit jamais bloquer. Il faut donc toujours approvisionner de l'audio en avance.

    Dans le cas particulier de la lecture d'un fichier, on besoin d'un truc en plus, c'est de connaître la position dans le fichier de ce qu'on entend. Produce() reçoit en argument le temps courant tcurr, et tDAC le temps où la 1ere frame du buffer d'audio qu'elle va fournir sera entendue. (tDAC - tcurr) est plus long que la durée d'un buffer. Il faut mémoriser une petite liste d'événements (position, TDAC) pour s'y retrouver. C'est le "mécanisme compliqué" dont je parlais. Il est spécifique aux classes qui ont un concept de "position".

    Ca me paraît bien de fournir une classe SeekableAudioSource qui rend ce service. A condition que l'utilisateur puisse facilement la dériver pour ajouter des propriétés. Malheureusement, il apparaît que les dérivées ont besoin d'ajouter des données dans les événements de la liste, et c'est là que mon C++ coince.

    Ca ne sert à rien de fournir une classe avec plein d'autres fonctionnalités (cyclable, stretchable...) car à coup sûr l'utilisateur aura besoin d'autre chose - je peux pas tout imaginer. Et il aura besoin de créer une dérivée.

    Donc je dois créer une SeekableAudioSource - classe de base pour tout audio avec notion de position - qui permette à ses dérivées d'insérer leurs propres événements dans la liste.

  16. #16
    Membre émérite
    Avatar de white_tentacle
    Profil pro
    Inscrit en
    Novembre 2008
    Messages
    1 505
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2008
    Messages : 1 505
    Points : 2 799
    Points
    2 799
    Par défaut
    Vu ce que tu veux faire, personnellement, je partirai sur du polymorphisme statique (à base de templates, donc). Tu peux t’en sortir avec du polymorphisme dynamique, comme l’a suggéré Bousk, mais attends-toi alors à un enfer de « unimplemented exceptions » au runtime (genre, l’utilisateur essaie d’accéder à position_min sur un évènement d’une source non seekable).

    Avec du polymorphisme statique, tu procèderas de la sorte :
    - pas de hiérarchie au niveau des sources
    - chaque source définit un type Event. Tu auras donc SeekableAudioSource::Event

    Ensuite, pour chaque source tu définis ses propriétés (seekable, cyclable, etc) au moyen de traits :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
    template<typename T>
    struct is_seekable<T> {
        static constexpr bool const value = false;
    };
     
    template<>
    struct is_seekable<AudioSourceToto> {
        static constexpr bool const value = true;
    }
    Une source qui est seekable doit fournir, les méthodes permettant d’accéder aux informations relatives sur ses évènements (position_min et position_max, ainsi que position et time), ni plus, ni moins. Plutôt via des fonctions libres que via un accès direct au membre public, car après tout, pour certaines sources la valeur pourrait être le résultat d’un calcul.

    Et après, tu écris tes algorithmes avec en entrée un beau static_assert(is_seekable<Source>) s’ils ne peuvent s’appliquer que pour une source qui est seekable.

    Et si tu as besoin de généricité runtime (par exemple, pour stocker une liste de sources, pouvant être de types différents), tu peux toujours utiliser un variant (ou any si tu veux que ce soit extensible) et des visiteurs. L’avantage que tu as à utiliser le polymorphisme statique est que tu déportes de l’exécution vers la compilation tout un tas d’erreurs.

Discussions similaires

  1. Template et héritage
    Par Ulmo dans le forum Langage
    Réponses: 16
    Dernier message: 08/03/2009, 23h41
  2. Template et héritage
    Par rooger dans le forum Langage
    Réponses: 5
    Dernier message: 22/07/2008, 13h48
  3. Foncteur, classes templates et héritage
    Par Floréal dans le forum C++
    Réponses: 8
    Dernier message: 17/06/2007, 21h56
  4. Template ou héritage
    Par bolhrak dans le forum Langage
    Réponses: 6
    Dernier message: 22/12/2006, 11h22
  5. patron, templates et héritages!
    Par kleenex dans le forum C++
    Réponses: 4
    Dernier message: 05/06/2006, 22h57

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