Forum Programmation.c++ Autour de l'héritage multiple et des méthodes virtuelles.

Posté par  .
Étiquettes : aucune
0
21
fév.
2008
Bonjour à tous,

Débutant en C++ après avoir fait pas mal de java, je m'interroge sur le problème que voici :

j'ai une classe A virtuelle pure :

class A
{
public :

virtual bool property1() = 0;
virtual int property2() = 0;

}


une classe B virtuelle pure également mais qui implémente toutefois une des deux méthodes précédentes.


class B : public A
{
public :
bool property1()
{
return true;
}
}


une classe C héritant de A qui implémente les deux fonctions précédentes (mais différemment pour la première) :

class C : public A
{
public :
virtual bool property1() //virtual permettrait la redéfinition dans les sous-classes
{
return un_calcul ;
}
int property2()
{
return un_autre_calcul;
}
}


Enfin je veux faire une classe D qui hérite de B et C à la fois : en utilisant la fonction de B pour la propriété 1, la fonction de C pour l'autre. J'ai d'abord essayé de la façon suivante :

class D : virtual public B, virtual public C
{

}

en pensant que grâce au mot-clef virtual dans l'héritage les méthodes ne sont définies qu'une seule fois.

mais je suis jeté par le compilateur à l'instantiation de D car soit-disant la fonction property2 est virtuelle pure dans D !!!
Pouvez-vous m'expliquer pourquoi ? j'avoue avoir un peu de mal.

un exemple concret : A désigne une classe générique représentant un anneau (mathématique), B désigne un corps (générique, c'est donc un anneau), C est un certain type d'anneau (par exemple Z/nZ) et D le corps Z/pZ quand p est premier. C'est donc à la fois un corps et l'anneau Z/nZ.
Property1 est la propriété "c'est un anneau intègre ?" (c'est tout le temps vrai dans un corps)
Property2 est la propriété "Quelle est la caractéristique ?" (ça dépend de l'anneau qu'on considère).
  • # héritage multiple en diamant

    Posté par  (site web personnel) . Évalué à 3.

    Tu veux faire un héritage en "diamant", c'est l'horreur à gérer.

    En gros, tu dois jouer sur les espaces de nom des méthodes pour savoir quel branche tu empruntes lors d'un appel de fonction.

    D.qqch si qqch existe dans B et C, il faut dire au compilo lequel tu veux utiliser.

    Il y a des docs plus précises sur internet sur ce point.

    Par contre, d'un point de vue conception de soft, c'est une pure horreur. Tu ferais mieux de rester dans le cas de classe virtuelle qui représentent des "interfaces" comme en Java.

    "La première sécurité est la liberté"

    • [^] # Tes "virtual" sont mals placés

      Posté par  (site web personnel) . Évalué à 2.

      Essaye :

      class C : public virtual A
      [...]

      class B : public virtual A
      [...]

      class D : public B, public C
      • [^] # Re: Tes "virtual" sont mals placés

        Posté par  . Évalué à 1.

        Merci beaucoup de m'avoir répondu.

        Les virtual sont en effet plus logiques comme ça. Mais malheureusement ça ne marche pas il ne réussit toujours pas à résoudre property1.

        En fait c'est logique, même si cette méthode est déclarée virtuelle et donc redéfinissable dans B, cela n'implique pas qu'elle doive nécessairement être redéfinie vu qu'elle n'est pas virtuelle pure.

        Je pense donc modifier le diagramme d'héritage pour éviter le diamant en question, auriez-vous des conseils pour l'exemple que je donnais à la fin ?
        • [^] # Re: Tes "virtual" sont mals placés

          Posté par  . Évalué à 2.

          Nicolas a raison, il faut que B et C héritent "virtuellement" de A. Le problème sur lequel tu bloque maintenant, c'est que le compilateur ne sait pas, dans la classe D si property1() doit utiliser B::property1() ou C::property1(), il faut donc lui dire explicitement :

          class D : public B, public C {
          public:
          /* Ici on est obligé de définir property1(), sinon le
          * compilateur ne peut savoir si c'est property1 de B ou de C
          * qu'il veut
          */
          virtual bool property1()
          {
          return B::property1();
          }
          };


          Etienne
        • [^] # Re: Tes "virtual" sont mals placés

          Posté par  . Évalué à 3.

          En fait c'est logique, même si cette méthode est déclarée virtuelle et donc redéfinissable dans B, cela n'implique pas qu'elle doive nécessairement être redéfinie vu qu'elle n'est pas virtuelle pure.

          Pas tout-à-fait : une méthode, quelle qu'elle soit, n'a pas besoin d'être virtuelle (pure ou non) pour pouvoir être redéfinie. En fait, tu n'as même pas besoin de la déclarer dans les classes dérivées dès lors que celles-ci en héritent de la classe supérieure. Ca veut dire notamment que tu n'as pas besoin de déclarer une méthode virtuelle (pure) à la racine de ton graphe simplement parce que tu comptes la faire apparaître un peu plus loin. C'est même fondamentalement le contraire : le procédé est fait pour t'éviter d'avoir à y penser à l'avance.

          D'autre part, être confronté à des héritages en diamant est une chose relativement fréquente en C++, mais ils sont généralement dus à une erreur de conception dans le modèle et pas à une limitation technique. Au pire, ils traduisent un conflit, au mieux, ils mettent généralement en évidence des notions jusque là implicites.

          Mais ton erreur d'analyse, dans ce cas précis, est de considérer chaque classe dérivée comme un sous-ensemble d'une classe plus générale (ce que N est à R, par exemple), alors qu'il s'agit au contraire d'une extension de la classe précédente (comme Z par rapport à N). Chaque classe contient implicitement la classe mère en entier, plus d'autres choses.

          Il est humainement facile de trancher entre une classe qui définit property1() et une autre qui ne se prononce pas, mais c'est plus dur de le faire si elles ont des opinions antagonistes : tu pourrais très bien définir une classe D qui, héritant de deux classes, soit à la fois un anneau intègre et un anneau non-intègre, ce qui n'a pas de sens.

          Si ce problème se résout parce qu'on sait qu'il y a prédominance ou récessivité, alors celles-ci doivent être définies. Mais ici, on voit que c'est le modèle objet même qui doit être revu. Le Java ne permettant pas d'hériter de plusieurs classes à la fois, c'est un problème qui est automatiquement contourné (donc jamais rencontré), mais pas résolu.

          , auriez-vous des conseils pour l'exemple que je donnais à la fin ?

          J'en ai un : Contrairement à Java, le C++ n'impose pas que tu définisses tes méthodes à l'intérieur de ta classe. C'est même mieux si tu sépares tes déclarations et définitions dans des fichiers .h et .c distincts. Ainsi, le .h peut être directement utilisé par les programmes sources externes qui utiliseront ta classe.

          D'autre part, toute fonction membre définie dans la déclaration de la classe est automatiquement considérée inline, et c'est rarement souhaitable.
          • [^] # Re: Tes "virtual" sont mals placés

            Posté par  . Évalué à 2.

            Merci pour ce post très détaillé, notamment "considérer chaque classe dérivée comme une extension de la classe précédente". C'est la tendance à raisonner parfois comme "B est un A"... Notamment en mathématiques où les objets ayant plus de propriétés (corps par rapport à anneau) sont en fait ceux dont les possibilités sont les plus réduites.


            (et juste au sujet des déclarations, c'était à titre d'exemple, j'ai appris à séparer les headers assez vite ;-) )
    • [^] # Re: héritage multiple en diamant

      Posté par  . Évalué à 1.

      Par contre, d'un point de vue conception de soft, c'est une pure horreur. Tu ferais mieux de rester dans le cas de classe virtuelle qui représentent des "interfaces" comme en Java.

      En même temps, si l'on passe au C++, c'est un peu pour profiter de sa puissance. Gérer les différentes branches n'est pas si compliqué. Le problème du Java, c'est que des pans entier du paradigme objet implémenté dans le C++ ont été sacrifiés pour que ce soit plus simple et parce que ça couvre toujours 90% des besoins habituels. En soi, ce n'était pas une mauvaise idée mais à présent, tous les étudiants se précipitent d'emblée sur le Java parce que c'est plus facile et ils ont ensuite un mal fou à remonter la pente.

      La bonne attitude à avoir, à mon avis, c'est essayer de s'entraîner tout-de-suite à maîtriser le C++ en entier, avant d'entamer de gros projets.
      • [^] # Re: héritage multiple en diamant

        Posté par  . Évalué à 3.

        Update après relecture : ce n'est pas un troll Java/C++, même si ça en a l'air. Je voulais dire dans ma dernière phrase qu'il valait mieux essayer de vaincre dès le départ les difficultés d'un langage plutôt que d'appliquer les modèles d'un autre. Ce n'est pas un plaidoyer en faveur d'un langage plus que de l'autre ...
        • [^] # Re: héritage multiple en diamant

          Posté par  (site web personnel) . Évalué à 2.

          Le concept des interfaces existaient avant Java... C'est très utilisé en SystemC par exemple.

          C'est la seul utilisation "propre" que j'ai vu de l'héritage multiple. Le problème des diamants devient énorme

          "La première sécurité est la liberté"

          • [^] # Re: héritage multiple en diamant

            Posté par  . Évalué à 4.

            Le concept des interfaces existaient avant Java... C'est très utilisé en SystemC par exemple.

            Je n'ai pas dit le contraire.

            C'est la seul utilisation "propre" que j'ai vu de l'héritage multiple. Le problème des diamants devient énorme

            Là, je ne suis pas d'accord. Personne n'oblige un programmeur C++ à coder en diamant et réciproquement, personne ne devrait en être empeché. Ce n'est pas parce que c'est difficile que ce n'est pas propre. Ce qui l'est, c'est la manière de contourner le problème sans le résoudre.

            La seule raison, à mon avis, pour laquelle Java ne propose pas l'héritage multiple à ses programmeurs est l'obligation de dériver de Object, parce que là, on aurait systématiquement un diamant à la moindre classe dérivée multiple.

            Il n'empêche qu'il y a réellement des classes qui dérivent de plusieurs objets de même importance (genre : Camion-Benne = Camion + Benne + dispositif d'arrimage prope) et que c'est très chiant de devoir faire le choix d'une classe "majeure" dont héritera l'objet quand les autres ne seront que réimplémentés. Et trancher en faveur du tout-interface (autrement dit, renoncer à l'héritage) ne m'amuse pas plus.

            Si les ambigüités du diamant posent problème, alors il suffit de toutes les redéfinir dans la classe héritante en une seule fois, ce qui est en substance ce qu'une interface oblige à faire. Mais se repalucher le même code 15 fois de suite, ça va bien cinq minutes ...
            • [^] # Re: héritage multiple en diamant

              Posté par  (site web personnel) . Évalué à 2.

              Tu aurais un vrai exemple ?

              Car dans ton cas de Camion+benne+arrimage, j'ai du mal à croire que Camion+benne se comporte toujours exactement comme un camion, comme une benne et comme l'arrimage.

              J'ai plus l'impression que c'est juste 3 inclusions.

              "La première sécurité est la liberté"

              • [^] # Re: héritage multiple en diamant

                Posté par  . Évalué à 2.

                Car dans ton cas de Camion+benne+arrimage, j'ai du mal à croire que Camion+benne se comporte toujours exactement comme un camion, comme une benne et comme l'arrimage.

                Ah bon, et pourquoi ?

                - Un camion-benne est un camion. Même chassis, même nécessité d'immatriculation, et il se conduit de la même façon par un conducteur de poids lourds (notion de compatibilité et d'interface, justement).

                - Un camion-benne est une benne puisqu'il s'agit d'une benne traditionnelle fixée à un chassis. Un camion-benne qui reste au parking a toutes les propriétés d'une benne ordinaire.

                Les quelques différences qui vont apparaître se gèrent alors avec la redéfinition des membres concernés. C'est tout l'intérêt de l'héritage objet, d'ailleurs : ne redéfinir que le diff. Dans l'exemple qui nous intéresse, "l'arrimage" était justement les modifications et ajouts nécessaires à la nouvelle classe (pas une troisième classe héritée).


                D'une manière générale, si tu vois l'intérêt d'implémenter de plusieurs interfaces à la fois, alors il y a un intérêt à dériver de plusieurs classes à la fois également. Ca ne se voit pas beaucoup dans la Javadoc parce qu'il ont écrit cela de manière élégante, à l'aide de "*Model" et compagnie qui présentent toujours la chose comme si elle était complètement abstraite.

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.