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 :

Design composite et héritage multiple


Sujet :

Langage C++

  1. #1
    Membre du Club
    Profil pro
    Inscrit en
    Décembre 2006
    Messages
    58
    Détails du profil
    Informations personnelles :
    Localisation : Suisse

    Informations forums :
    Inscription : Décembre 2006
    Messages : 58
    Points : 52
    Points
    52
    Par défaut Design composite et héritage multiple
    Bonjour à tous !

    Je m'adresse à vous grand développeurs C++. Je sais que sur developpez.com, j'aurais toujours une réponse claire et précise, ce qui est extrêmement plaisant.

    Donc, j'ai un petit problème de conception.

    Imaginons :

    J'ai deux classes A et B abstraites.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class A
    {
       public:
          virtual void f() const = 0;
    };
     
    class B
    {
       public:
          virtual void g() const = 0;
    };
    Je possède aussi une classe AB qui se trouve être implémenté de cette manière.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    class AB : public A, public B
    {
       public:
          void f() const {cout << "Je suis la fonction f de AB." << endl;}
          void g() const {cout << "Je suis la fonction g de AB" << endl;}
    };
    La classe AB hérite publiquement de A et de B.

    Mais maintenant les choses se corsent. Je décide d'implémenter le design pattern "composite" (vous l'appelez ainsi sur ce site).

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    class ABs : private vector<A*>, private vector<B*>
    {
       public:
          void add(const A& a) {vector<A*>::push_back(a.clone());}
          void add(const B& b) {vector<B*>::push_back(b.clone());}
    };
    Où clone est la fonction qui fait une copie profonde de l'objet.

    Voilà voilà, je pense que vous cernez le problème. J'aimerais faire que quand j'ajoute un AB dans ma liste de A et de B, il ne s'ajoute qu'un seul et unique AB. De plus, je crois bien que le code ne compile pas ici. Arrivez-vous à le faire compiler? Si certaines parties sont encore floues merci de m'en informer.

    Merci beaucoup de votre aide !

  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,

    (Une petite remarque au passage: n'oublie pas de déclarer tes fonctions pures virtuelles, car, sans ca, ca n'ira jamais )

    D'abord, ce n'est pas nous qui nommons ce patron ainsi, c'est la traduction officielle francaise terme

    Ensuite, le pattern composite se ferait plutôt 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
    24
    class A
    {
    };
    class B
    {
    };
    /* Ce serait cette classe-ci qui sert d'interface commune
     * entre les noeuds et les feuilles
     */
    class AB: public A, public B
    {
    };
    /* la classe "feuille" */
    class ABLeaf : public AB
    {
    };
    /*  la classe "noeud" */
    class ABNode : public AB
    {
        private:
             std::vector<AB*> m_children;
             /* voire, mieux encore */
             std::vector<std::shared_ptr<AB*> > m_children;
    };
    car il n'est effectivement jamais bon de faire hériter une classe des conteneurs de la STL.

    De plus, les noeuds sont, effectivement, pour partie des A et des B, mais ce sont surtout des AB, avec tout ce que cela peut avoir de commun avec les feuilles, qui sont aussi des AB avant tout

    En outre, si les classes A et B sont utilisées par ailleurs, je ne saurais trop te conseiller de réfléchir sérieusement à la nécessité réelle de l'héritage multiple...

    Je ne dis pas qu'il posera problème dans ton cas, mais, dans bien des situations, l'aggrégation est préférable à l'héritage, surtout si cela t'emmène sur la voie semée de ronces qu'est l'héritage multiple
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  3. #3
    Membre du Club
    Profil pro
    Inscrit en
    Décembre 2006
    Messages
    58
    Détails du profil
    Informations personnelles :
    Localisation : Suisse

    Informations forums :
    Inscription : Décembre 2006
    Messages : 58
    Points : 52
    Points
    52
    Par défaut
    Vous êtes sûr que ça n'est JAMAIS bon de faire hériter de conteneur? Pourquoi donc?

    Vous pensez vraiment que c'est mieux d'avoir une classe intermédiaire? Pourquoi concrètement?

    Et sinon, pourquoi la collection de shared_ptr est meilleure?

    Merci encore beaucoup pour vos indications très précieuses. C'est très très très sympathique.

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par Xanto Voir le message
    Vous êtes sûr que ça n'est JAMAIS bon de faire hériter de conteneur? Pourquoi donc?
    Oui... du moins si le conteneur est issu de la STL (ce qui est le cas de std::vector).

    La raison de cette restriction vient du fait que les conteneurs de la STL sont des conteneurs dits "template" (raison pour laquelle il faut ajouter <untype> lorsqu'on les déclare).

    Or, lorsqu'on travaille avec une classe template, cela revient à dire au compilateur
    Je ne sais pas encore avec quel type de données tu va devoir travailler, mais je sais déjà te dire comment tu devra le faire
    Cela implique que le compilateur va attendre de savoir avec quel type il travaille effectivement pour créer le code exécutable correspondant.

    Mais cela se fait au moment de la compilation

    Nous avons donc bien un comportement qui s'adapte au type utilisé, mais, uniquement parce que le compilateur crée le code exécutable au moment où il est en mesure de déterminer le type réel utilisé: on parle d'un "polymorphisme statique".

    Par contre, lorsque tu utilise l'héritage, le type réellement manipulé est déterminé de manière dynamique lors de l'exécution.

    On parle alors de "polymorphisme dynamique".

    Et la grosse différence entre les deux tient au fait que, d'un coté (le polymorphisme dynamique), la classe mère doit préciser explicitement toutes les fonctions dont le comportement risque d'être modifié par les classes dérivées, dont, entre autres et principalement le destructeur, en utilisant le mot clé virtual, alors que de l'autre coté (le polymophisme statique), ben, l'adaptation du comportement a lieu beaucoup plus tôt...

    Dés lors, le destructeur des conteneurs de la stl n'est pas déclaré virtuel.

    Or, lorsque tu utilise l'héritage "normal", tu espère pouvoir écrire un code 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
    class Base
    {
        public:
            virtual ~Base();
    };
    class Derivee : public Base
    {
        public:
            virtual ~Derivee();
    };
    int main()
    {
       Base* obj=new Derivee;
       /* ... */
       delete obj; /*appelle à la base le destructeur de ... Base
                   * mais, grace au fait qu'il est déclaré virtuel, 
                   * le comportement est dynamiquement adapté pour 
                   * appeler le destructeur de Derivee
                   */
        return 0;
    }
    alors que si tu fais pareil avec un conteneur de la STL, tu te trouvera face à cette situation:
    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
    class Derivee::std::vector<Type>
    {
        /*...*/
    };
    int main()
    {
        Vector<type>* obj=new Derivee;
        /*...*/
        delete obj; /* ce sera effectivement le destructeur de std::vector<Type>
                     * qui sera appelé... Si, pour une raison ou une autre,
                     * le destructeurde Derivee doit libérer une ressource, 
                     * ce ne sera jamais fait car il n'y a rien qui attire l'attention
                     * sur le fait qu'il faille adapter le destructeur de
                     * std::vector<Type> en fonction du type réellement manipulé
                     */
    }
    (en outre, tout est fait pour éviter d'avoir à manipuler les conteneurs de la STL sous la forme de pointeurs, et il est généralement malsain de le faire )
    Vous pensez vraiment que c'est mieux d'avoir une classe intermédiaire? Pourquoi concrètement?
    En fait, le patron "composite" permet de créer une arborescence en forme d'arbre binaire / N-aire sur base de la distinction qui peut être faite entre les noeuds terminaux et non terminaux.

    Par "noeud terminal", je veux parler d'un noeud qui ne peut jamais avoir d'enfant (bref, une feuille), alors que par "noeud non terminal" je parle d'un noeud qui peut avoir un / des enfants, mais qui n'en a pas forcément à un instant T.

    C'est la raison pour laquelle le patron est normalement de la forme
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    /*  l'interface commune au feuilles et aux noeud */
    class Base
    {
    };
    /* les feuilles */
    class Leaf : public Base
    {
    };
    /* les noeuds */
    class Node : public Base
    {
    };
    De cette manière, tu peux stocker aussi bien des feuilles que des noeuds dans ... les noeuds existants, et, lors du parcours de ton arbre, tu travailles à la base avec l'interface commune aux feuilles et au noeud, quitte à ce que le comportement mis en oeuvre soit adapté à ce qu'une feuille peut faire et à ce qu'un noeud peut faire

    Or, ce que tu veux faire, ce n'est pas travailler avec ce qui ne vient que de A, mais bien avec un "mix" de ce qui vient de A et de ce qui vient de B...

    Il faut donc que l'interface commune aux noeuds et aux feuilles soit... le mix des deux classes (A et B)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    Et sinon, pourquoi la collection de shared_ptr est meilleure?
    J'ai peut être mal choisi le shared_ptr, mais, une chose est sure, il vaut mieux manipuler un type de "pointeur intelligent"...

    En effet, le gros problème lorsque tu travailles avec le patron composite, c'est que tu va être énormément tenté d'avoir recours à l'allocation dynamique pour créer tes instances de feuilles et de noeuds.

    Or, si une collection (le noeud en l'occurence) utilise comme membres des pointeurs dont la mémoire est allouée dynamiquement, il y a une série de problèmes à résoudre si l'on souhaite pouvoir garder la possibilité d'effectuer une copie (d'appeler le constructeur par copie) ou une affectation (d'appeler l'opérateur d'affectation "=") sur cette ressource ou si on souhaite "simplement" être en mesure d'accéder à ce pointeur en dehors de la classe.

    L'un des principaux problème à résoudre est le risque de double libération de la mémoire, qui survient lorsque un pointeur pointe sur une adresse mémoire qui a déjà été libérée.

    Comme ma réponse prend déjà des allures de roman, je ne vais pas m'étendre énormément sur le sujet, mais sache que les pointeurs intelligents permettent de palier à certains de ces problèmes
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  5. #5
    Membre du Club
    Profil pro
    Inscrit en
    Décembre 2006
    Messages
    58
    Détails du profil
    Informations personnelles :
    Localisation : Suisse

    Informations forums :
    Inscription : Décembre 2006
    Messages : 58
    Points : 52
    Points
    52
    Par défaut
    Quelle réponse fabuleuse ! Vraiment merci infiniment. La réponse est à la fois claire et exhaustive : magnifique !

    Et je pense donc travailler avec une classe intermédiaire, c'est beaucoup plus propre.

    Encore merci ! Et bonne continuation surtout.

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

Discussions similaires

  1. composants C++ Builder et héritage multiple
    Par vedrfolnir dans le forum C++Builder
    Réponses: 2
    Dernier message: 12/10/2005, 10h04
  2. [heritage][conception]héritage multiple en java!
    Par soulhouf dans le forum Langage
    Réponses: 9
    Dernier message: 25/08/2005, 20h03
  3. L'héritage multiple est-il possible en Delphi ?
    Par SchpatziBreizh dans le forum Langage
    Réponses: 8
    Dernier message: 30/06/2005, 11h30
  4. utilisez vous l'héritage multiple ?
    Par vodosiossbaas dans le forum C++
    Réponses: 8
    Dernier message: 13/06/2005, 20h25
  5. [XML Schemas]héritage multiple
    Par nicolas_jf dans le forum XML/XSL et SOAP
    Réponses: 2
    Dernier message: 10/06/2003, 12h55

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