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 :

Une étrange histoire d'héritage


Sujet :

C++

  1. #1
    Candidat au Club
    Profil pro
    Inscrit en
    Mai 2011
    Messages
    2
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2011
    Messages : 2
    Points : 4
    Points
    4
    Par défaut Une étrange histoire d'héritage
    Bonjour

    j'ai ete recemment confronte au morceau de code ci-dessous et je peine a comprendre ce qui se passe reellemment.

    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
    #include <iostream>
     
    using namespace std;
     
    class A {
    protected:
    	int m_x, m_y;
    public:
    	A(int x, int y) : m_x(x), m_y(y) {
    		cout << "Constructeur de A : " << m_x << " " << m_y << endl;
    	}
    };
     
    class B : public virtual A {
    public:
    	B(int x, int y) : A(2*x+1, 2*y+1) {
    		cout << "Constructeur de B : " << m_x << " " << m_y << endl;
    	}
    };
     
    class C : public virtual A {
    public:
    	C(int x, int y) : A(2*x+3, 2*y+3) {
    		cout << "Constructeur de C : " << m_x << " " << m_y << endl;
    	}
    };
     
    class D : public B, public C {
    public:
    	D(int x, int y) : B(x+1, y+1), C(x+2, y+2), A(x, y) {
    		cout << "Constructeur de D : " << m_x << " " << m_y << endl;
    	}
    };
     
    int main() {
    	D d(1, 2);
    	system("pause");
    	return 0;
    }
    La sortie est la suivante :

    Constructeur de A : 1 2
    Constructeur de B : 1 2
    Constructeur de C : 1 2
    Constructeur de D : 1 2
    Appuyez sur une touche pour continuer...
    tout se passe comme si seul le constructeur de A etait appele mais, pourtant, on passe bien dans les constructeurs de B, C et D comme le montrent les sorties

    quelqu'un peut m'eclairer sur la question ?

    merci d'avance

  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,

    Il faut savoir que l'héritage virtuel est très particulier...

    Son principe est que, si tu utilise l'héritage multiple et que, pas de bol, les classes mères ont elles-même une classe de base commune (bref, que tu te retrouves dans la situaion que tu nous expose), la base commune devrait normalement être présente deux fois (ou plusieurs fois car il n'y a pour ainsi dire pas de limite au nombre de classes mère lors de l'héritage multiple ) dans ta classes dérivée : une fois pour chaque classe héritant de la classe de base commune.

    Nous nous trouverions alors avec des valeurs dupliquées (en gros, tous les membres de la classe de base commune ) potentiellement différentes (et donc incohérentes) en fonction de la classe mère au départ de laquelle on essayerait d'y accéder, avec tout le lot de bizareries que tu peux imaginer obtenir de ce fait

    Il est donc important d'éviter cette duplication de données (à moins bien sur que tu ne juge cohérent d'avoir effectivement cette duplication des données, mais c'est à tes risques et périls ) .

    C'est la raison pour laquelle on rajoute le mot clé virtual dans la définition de l'héritage: afin de faire comprendre au compilateur que l'héritage se fait de manière un peu particulière.

    Mais cela ne résout qu'une partie du problème, car la règle générale de construction d'objets dérivés consiste à appeler le constructeur de la classe mère directe, de manière à ce que ce qui vient de la classe mère soit correctement initialisé avant d'en arriver à initialiser ce qui appartient spécifiquement à la classe dérivée.

    Il faut, à ce sujet, être bien conscient du fait que, outre les listes d'initialisation (que l'on ne conseillera jamais assez d'utiliser), le constructeur est susceptible de faire bien plus que de simplement initialiser les membres de la classe: il peut aussi effectuer une série de vérifications spécifiques. C'est, d'une certaine manière ce que tu fais en demandant l'affichage

    Le problème auquel on est confronté au niveau de l'héritage multiple, c'est que la manière dont la classe de base est initialisée pourrait surprendre énormément de gens: à titre personnel, je me dis que son initialisation devrait (si l'on n'avait pas prévu le comportement actuel) correspondre à l'initialisation de la dernière classe ayant appelé le constructeur de la classe de base, mais d'autres pourraient plutôt préférer que ce soit l'initialisation de la première qui soit prise en compte, et, pour d'autres enfin, ou dans certaines circonstances, on peut souhaiter que ce soit l'initialisation d'une autre classe mère encore qui soit prise en compte.

    Bref, tu te rend bien compte qu'il est particulièrement difficile de faire ressortir une règle simple et "universelle" quant à la manière dont l'intialisation de la classe de base devrait s'effectuer, et ce, d'autant plus que l'ordre dans lequel tu déclarerais l'héritage prendrait toute son importance, faisant que la classe serait différente de la classe ... un comble, non

    Cela devrait déjà te faire comprendre la raison pour laquelle, lorsque tu utilise l'héritage virtuel, il faut que tu appelle explicitement le constructeur de la classe de base dans chaque classe qui en hérite de manière directe ou indirecte.

    Et, quant au fait que le constructeur des classes intermédiaires est également appelé, c'est tout à fait normal: d'abord, tu auras remarqué que le compilateur t'insultera de tous les noms d'oiseaux si tu ne le fais pas, mais, en plus, il faut effectivement faire en sorte que les classes intermédiaires soient correctement initialisées.

    La seule chose, c'est que ton exemple actuel ne montre pas trop cette réalité. Par contre, l'exemple suivant devrait pouvoir t'aider:
    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
    class A
    {
        public:
            A(int i): i_(i){}
        private:
            int i_;
    };
    class B : virtual public A
    {
        public:
            B(std::string const & n):A(n.size(),name_(n){}
        private:
            std::string name_;
    };
    class C : virtual public A
    {
        public:
            C(float f):A(f),f_(f){}
    };
    class PI : public B, public C
    {
        public:
            PI(): /* pour B, pas de problème */ B("pi"),
                  /* pour c, non plus        */ C(3,1415926),
                  /* mais pour A, faut il utiliser 2, ou 3 ? 
                   * ben non, faut utiliser 5 :P */ A(5){}
    };
    Ceci dit, je en suis pas loin de considérer que, de prime abord du moins, le besoin de recourir à l'héritage virtuel lorsque l'on envisage l'héritage multiple est le principal symptôme d'un problème de conception.

    N'oublie pas que l'héritage est la relation la plus forte qui puisse exister entre deux classes, et donc qu'il est indispensable de veiller à ne l'utiliser que lorsque c'est réellement utile: elle peut (relativement) facilement être remplacée par une relation plus "souple" telle que la composition ou l'agrégation, et elle se doit impérativement de suivre certaines règles, dont le respect du principe de substitution de Liskov (une recherche sur le forum te permettra d'en savoir plus là dessus )

    • Peut être n'as tu pas suffisemment respcté ce principe
    • Peut être as tu donné trop de responsabilités à ton objet de base
    • Peut être souhaites tu créer des collections d'objets trop différents
    • Peut être la programmation générique (CRTP en tête) pourrait-elle venir à ton secours
    • Peut être devrais tu envisager la composition ou l'aggrégation pour l'une des classes intermédiaires, en lieu et place de l'héritage
    Comme tu peux le voir, les possibilités et les pistes à explorer pour arriver à éviter l'écueil de l'héritage en diamant sont multiples, mais il faudrait que nous en sachions plus sur le projet pour t'indiquer celle(s) qui est (sont) les plus apaptée(s) à ta situation
    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 émérite
    Profil pro
    Inscrit en
    Novembre 2004
    Messages
    2 764
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2004
    Messages : 2 764
    Points : 2 705
    Points
    2 705
    Par défaut
    Tiens, tu testeras ce qui se passe si tu ne mets en virtuel qu'une des deux classes. :-)

Discussions similaires

  1. Une terrible histoire d'ampoules
    Par Glopsos dans le forum Algorithmes et structures de données
    Réponses: 20
    Dernier message: 09/01/2008, 18h49
  2. Réponses: 1
    Dernier message: 16/07/2007, 10h13

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