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 :

Problème d'héritage multiple


Sujet :

C++

  1. #1
    Membre confirmé
    Homme Profil pro
    Ingénieur systèmes embarqués
    Inscrit en
    Janvier 2006
    Messages
    81
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 38
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Ingénieur systèmes embarqués
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Janvier 2006
    Messages : 81
    Par défaut Problème d'héritage multiple
    Bonjour
    Mon projet comporte 4 classes liées de la manière suivante :
    D -----> C ----- > A
    D -----> B ----- > A
    où une flèche signifie : héritage public de

    Premier problème :
    Quand je crée un objet D, comment faire pour qu'il n'appelle pas deux fois le constructeur de la classe A ?

    Ensuite j'ai le code suivant :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    A b = B();
    A c = C();
    b.afficher();
    c.afficher();
    Second problème :
    Comment faire pour que lorsque je fais b.afficher() ça appelle la fonction B::afficher() (alors que dans ce cas cela appelle A::afficher()) ?

    Merci d'avance pour votre aide

  2. #2
    Expert éminent
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 395
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France

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

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 395
    Par défaut
    Premier problème: Cas classique d'héritage en losange. Voir "classes de base virtuelles".

    Second problème: Tu dois travailler par référence (ou pointeur) et non par valeur. Avec ce code, tu ne fais que recopier la partie A de tes objets dans de nouveaux objets de type A.

    Pour éviter cela, tu devrais déclarer protected le constructeur de copie de A.
    SVP, pas de questions techniques par MP. Surtout si je ne vous ai jamais parlé avant.

    "Aw, come on, who would be so stupid as to insert a cast to make an error go away without actually fixing the error?"
    Apparently everyone.
    -- Raymond Chen.
    Traduction obligatoire: "Oh, voyons, qui serait assez stupide pour mettre un cast pour faire disparaitre un message d'erreur sans vraiment corriger l'erreur?" - Apparemment, tout le monde. -- Raymond Chen.

  3. #3
    Membre averti
    Profil pro
    Inscrit en
    Février 2009
    Messages
    28
    Détails du profil
    Informations personnelles :
    Localisation : Canada

    Informations forums :
    Inscription : Février 2009
    Messages : 28
    Par défaut
    Je peux peut-être répondre à ta seconde question. L'héritage ainsi que le polymorphisme permet aux classes de remonter la hiérarchie si elle ne trouve pas la méthode demandé de sa classe. Donc, pour que B appel sa méthode à lui Afficher(), tu dois la créer dans la classe A ainsi que dans la classe B. De ce fait, lorsque tu appel la fonction afficher par b.afficher(), il va trouver la méthode afficher de la classe B, si elle n'existe pas, elle va donc remonté pour choisir la méthode afficher de la classe A.

  4. #4
    Membre confirmé
    Homme Profil pro
    Ingénieur systèmes embarqués
    Inscrit en
    Janvier 2006
    Messages
    81
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 38
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Ingénieur systèmes embarqués
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Janvier 2006
    Messages : 81
    Par défaut Toujours des problèmes
    J'ai donc fait mes classes de la manière suivante :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    class A {};
    class B : public virtual A {};
    class C : public virtual A {};
    class D : public virtual B,public C {};
    Je veux ensuite tester mon code mais toujours des erreurs :
    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
    int main(void)
    {
    A *tab[3];
     
    B b = B();
    tab[0] = &b;
     
    C c = C();
    tab[1] = &c;
     
    D d = D();
    tab[2] = &d;
     
    tab[0]->affiche();
    // Appel à A::affiche()
     
    tab[1]->affiche();
    // Appel à A::affiche()
     
    tab[2]->affiche();
    // Appel à A::affiche()
     
    return 0;
    }

  5. #5
    Expert éminent
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 395
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France

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

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 395
    Par défaut
    Pour ta première erreur, c'est bizarre (je ne pensais pas avoir ça avec l'héritage virtuel).
    Pour la seconde, A::affiche() doit être déclarée virtual.
    SVP, pas de questions techniques par MP. Surtout si je ne vous ai jamais parlé avant.

    "Aw, come on, who would be so stupid as to insert a cast to make an error go away without actually fixing the error?"
    Apparently everyone.
    -- Raymond Chen.
    Traduction obligatoire: "Oh, voyons, qui serait assez stupide pour mettre un cast pour faire disparaitre un message d'erreur sans vraiment corriger l'erreur?" - Apparemment, tout le monde. -- Raymond Chen.

  6. #6
    Membre confirmé
    Homme Profil pro
    Ingénieur systèmes embarqués
    Inscrit en
    Janvier 2006
    Messages
    81
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 38
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Ingénieur systèmes embarqués
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Janvier 2006
    Messages : 81
    Par défaut Petit oubli!!!
    Oui effectivement j'avais déclarée cette fonction virtuelle au départ mais j'ai oublié de le remettre. Donc j'ai remis cela, ça appelle bien les bonnes fonctions maintenant.
    Cependant un autre problème est arrivé entre temps
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
     
    class A {};
    class B : public virtual A {};
    class C : public virtual A {};
    class D : public virtual B,public C {};
     
    D d = D();
    tab[2] = &d;
    /* Appel au constructeur B()
     * Appel au constructeur C()
     * Appel au constructeur D()
     ** Il n'y a plus d'appel au constructeur A()
     */

  7. #7
    Expert éminent
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 395
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France

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

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 395
    Par défaut
    C'est bizarre, normalement, le constructeur de A est appelé (mais tous les appels suivants sont ignorés).
    SVP, pas de questions techniques par MP. Surtout si je ne vous ai jamais parlé avant.

    "Aw, come on, who would be so stupid as to insert a cast to make an error go away without actually fixing the error?"
    Apparently everyone.
    -- Raymond Chen.
    Traduction obligatoire: "Oh, voyons, qui serait assez stupide pour mettre un cast pour faire disparaitre un message d'erreur sans vraiment corriger l'erreur?" - Apparemment, tout le monde. -- Raymond Chen.

  8. #8
    Membre confirmé
    Homme Profil pro
    Ingénieur systèmes embarqués
    Inscrit en
    Janvier 2006
    Messages
    81
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 38
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Ingénieur systèmes embarqués
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Janvier 2006
    Messages : 81
    Par défaut Peut être une solution ...
    En fait j'arrive à faire fonctionner mon code si je fais appel au constructeur A lors de l'appel à celui de D. Peut être je ne m'exprime pas bien alors voici le code :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    D::D(int a,int b) : A(a),B(b),C(b)
    Bon, ça fonctionne mais je me demande juste si c'est bien correct de procéder comme tel ? ou si je risque d'avoir des problèmes ensuite ?

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    Salut,

    Quand tu utilise l'héritage multiple, et que les classes "mères" héritent toutes d'une classe "ancêtre" de base, tu dois non seulement recourir à un héritage virtuel pour la classe ancêtre, mais aussi appeler le constructeur de cette classe dans toutes les classes dérivées (quel que soit le niveau de dérivation atteint), avant d'appeler le constructeur des classes "directement parentes"

    La seule exception étant le fait que le constructeur de la classe ancêtre rende la classe défaut constructible (ne nécessitant aucun argument).

    La raison est simple: en déclarant l'héritage virtuel, cela revient - entre autres -à dire au compilateur quelque chose comme
    Attention, ce n'est pas forcément la classe qui hérite directement qui devra se charger de l'initialisation de la classe de base
    Or, les classes sont naturellement paresseuses, et il suffit qu'on leur dise que "quelqu'un d'autre" se charge peut-être de faire leur travail (ici, invoquer le constructeur de la classe de base) pour qu'elle décide... de ne pas le faire si elle n'y sont pas obligées

    Du coup, lorsque tu as B et C qui héritent de A et D qui hérite de B et de C, B se dit "pas besoin d'invoquer le constructeur de A, C ou D s'en chargeront" et C se dit la même chose concernant B et D...

    Au final, il n'y a plus que D pour accepter de faire le travail

    Ce comportement est - sommes toutes - assez logique: comme si B et C venaient à invoquer le constructeur de A, il y aurait au mieux double construction (dont une inutile), au pire, une construction qui ne correspond peut être pas au "plus petit commun" entre les besoins de B et ceux de C et qui puisse s'utiliser dans D... Donc, pour éviter ces deux problèmes, il faut que ce soit D qui se charge d'invoquer le constructeur de A
    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 confirmé
    Homme Profil pro
    Ingénieur systèmes embarqués
    Inscrit en
    Janvier 2006
    Messages
    81
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 38
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Ingénieur systèmes embarqués
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Janvier 2006
    Messages : 81
    Par défaut Remerciements
    Super merci pour cet éclaircissement koala01 ... Et merci à Médinoc et 0ColdZero0 pour m'avoir aider à résoudre mes problèmes.

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

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Par défaut
    Citation Envoyé par koala01 Voir le message
    Quand tu utilise l'héritage multiple, et que les classes "mères" héritent toutes d'une classe "ancêtre" de base, tu dois non seulement recourir à un héritage virtuel pour la classe ancêtre, mais aussi appeler le constructeur de cette classe dans toutes les classes dérivées (quel que soit le niveau de dérivation atteint), avant d'appeler le constructeur des classes "directement parentes"
    Salut,
    Il me semble qu'on n'est pas obligé d'appeler explicitement le constructeur de cette première classe de base dans toute la chaîne d'héritage.
    Le constructeur de la classe virtuelle est appelé dès le début soit implicitement (constructeur par défaut de A) soit explicitement. Les appels présents pour le constructeur virtuel ancêtre dans les classes parentes ne sont pas appelés :
    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
    class A {
    public:
       A(){std::cout<<"A"<<std::endl;}
       A(std::string org)
       {
          std::cout<<"A depuis "<<org<<std::endl;
       }
    };
    class B : public virtual A {
    public:
       B():A("B")
       {std::cout<<"B"<<std::endl;}
     
    };
    class C : public virtual A {
       public:
       C():A("C")
       {std::cout<<"C"<<std::endl;}
    };
    class D : public B,public C {
       public:
       D()
       {std::cout<<"D"<<std::endl;}
       D(int):A("D")
       {
          std::cout<<"D explicit"<<std::endl;
       }
    };
     
    int main()
    {
    std::cout<<"Un b"<<std::endl;
    B b;
     
    std::cout<<"Un C"<<std::endl;
    C c;
     
    std::cout<<"Un D"<<std::endl;
    D d;
     
    std::cout<<"Un D explicite "<<std::endl;
    D d2(0);
       return 0;
    }
    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
    Un b
    A depuis B
    B
    Un C
    A depuis C
    C
    Un D
    A
    B
    C
    D
    Un D explicite
    A depuis D
    B
    C
    D explicit

  12. #12
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    Tu as arrêté ta lecture une ligne trop tôt: juste après, j'ai présenté l'exception des classes défaut-constructibles (dont le constructeur ne nécessite pas d'argument)

    Mais il reste le problème que le constructeur ne nécessitant pas d'argument, s'il existe, peut s'avérer moins "intéressant" que celui qui en nécessite (des arguments) pour les classes dérivées et qu'il n'est pas forcément toujours opportun de rendre la classe défaut constructible

    C'est la raison pour laquelle la règle de base est plutôt d'appeler explicitement le constructeur de la classe "ancêtre" dans le constructeur de toutes les classes qui en dérivent et que l'exception est le cas d'une classe ancêtre défaut constructible
    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

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

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Par défaut
    Citation Envoyé par koala01 Voir le message
    Tu as arrêté ta lecture une ligne trop tôt: juste après, j'ai présenté l'exception des classes défaut-constructibles (dont le constructeur ne nécessite pas d'argument)
    Oups Faut dire que vu la longueur, je lis souvent en diagonal, ce qui est, le preuve en est, un tort.

    Citation Envoyé par koala01 Voir le message
    Mais il reste le problème que le constructeur ne nécessitant pas d'argument, s'il existe, peut s'avérer moins "intéressant" que celui qui en nécessite (des arguments) pour les classes dérivées et qu'il n'est pas forcément toujours opportun de rendre la classe défaut constructible

    C'est la raison pour laquelle la règle de base est plutôt d'appeler explicitement le constructeur de la classe "ancêtre" dans le constructeur de toutes les classes qui en dérivent et que l'exception est le cas d'une classe ancêtre défaut constructible
    Pour abonder dans ton sens je dirais 2 choses:
    1/ Il faut bien comprendre avec l'héritage virtuel que l'appel au constructeur de la classe ancêtre se fait au niveau de la classe effectivement instanciée et pas sur une des classes intermédiaires (dans mon exemple, au niveau de D et ni au niveau de B ou C),
    2/ Si, dans la hiérarchie, à un moment, on a décidé qu'il fallait appeler un constructeur spécialisé, il y a peu de chance qu'en descendant les raisons aient changées...

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    Citation Envoyé par 3DArchi Voir le message
    Oups Faut dire que vu la longueur, je lis souvent en diagonal, ce qui est, le preuve en est, un tort.
    Ne t'en fais pas... ca m'arrive aussi
    Pour abonder dans ton sens je dirais 2 choses:
    <snip>
    2/ Si, dans la hiérarchie, à un moment, on a décidé qu'il fallait appeler un constructeur spécialisé, il y a peu de chance qu'en descendant les raisons aient changées...
    Le fait est que, dans l'exemple, B pourrait (après tout pourquoi pas) utiliser le constructeur de A ne prenant pas d'argument et C celui qui en prend un (ou plusieurs), ou l'inverse...

    Dés lors, seul le développeur est en mesure de déterminer quel sera le constructeur de A le plus adapté à D qui hérite à la fois de B et de C

    Il n'existe en effet aucune règle pour permettre au compilateur de le décider tout seul
    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

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

Discussions similaires

  1. L'épineux problème de l'héritage multiple
    Par syntaxerror dans le forum C#
    Réponses: 3
    Dernier message: 17/08/2010, 14h24
  2. Réponses: 8
    Dernier message: 03/03/2009, 19h58
  3. problème d'héritage multiple
    Par thiouwz dans le forum Langage
    Réponses: 3
    Dernier message: 28/10/2006, 16h35
  4. Réponses: 19
    Dernier message: 13/07/2006, 13h35
  5. Réponses: 6
    Dernier message: 25/03/2002, 21h11

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