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 :

Héritage en diamant foireux...


Sujet :

C++

  1. #1
    Membre à l'essai
    Inscrit en
    Décembre 2004
    Messages
    49
    Détails du profil
    Informations forums :
    Inscription : Décembre 2004
    Messages : 49
    Points : 19
    Points
    19
    Par défaut Héritage en diamant foireux...
    Bonjour,

    J'ai plusieurs classes qui s'intitulent ainsi :
    ECBEntity, ECBArmee, ECEntity, ECArmee.

    ECBArmee est dérivée de ECBEntity. Toutes deux contiennent surtout l'interface, sachant que ECBArmee n'est *pas* abstraite et a du code.

    ECEntity est dérivée de ECBEntity et reste abstraite, avec des variables et un peu de code en plus.

    Enfin, ECArmee est censée etre dérivée de ECEntity et de ECBArmee, pour garder ce qui a été mis donc dans ECBArmee mais pour avoir les elements en plus de ECEntity.

    Alors je ne sais pas si je suis très clair.
    Je ne sais pas comment gérer avec les ": public virtual" ou "public", et pour le moment j'ai ceci :

    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
    class ECBEntity
    {
    public:
    	ECBEntity() {}
    	ECBEntity(const Entity_ID _name, ECBPlayer* _owner, ECBCase* _case, e_type type);
    	virtual ~ECBEntity() {}
     /*...*/
    };
     
    /* Note: Implémentation des classes virtuelles pures de ECBEntity */
    class ECBArmee : public virtual ECBEntity
    {
    public:
    	ECBArmee(const Entity_ID _name, ECBPlayer* _owner, ECBCase* _case, uint _nb)
    		: ECBEntity(_name, _owner, _case, E_ARMEE), nb(_nb)
    	{}
     /*...*/
     };
     
    /* Note: ECEntity n'implémente pas les fonctions virtuelles pures de ECBEntity et donc reste une classe abstraite */
    class ECEntity : public virtual ECBEntity
    {
    public:
    	ECEntity(const Entity_ID _name, ECBPlayer* _owner, ECBCase* _case, e_type _type)
    		: ECBEntity(_name, _owner, _case, _type)
    	{}
     
    	virtual ~ECEntity() {}
     /*...*/
    };
     
    /* Est donc basée sur ECBArmee et ECEntity */
    class ECArmee : public ECBArmee, public ECEntity
    {
    public:
    	ECArmee(const Entity_ID _name, ECBPlayer* _owner, ECase* _case, uint _nb)
    		: ECBArmee(_name, _owner, _case, _nb), ECEntity(_name, _owner, _case, E_ARMEE)
    	{}
    };
    Le problème, c'est que lors de l'instantiation d'une classe ECArmee, ben ça bug un rien :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    const char *e_name = Sender->Channel()->FindEntityName((*cai)->Country()->Owner() ?
                                     dynamic_cast<ECPlayer*>((*cai)->Country()->Owner()->Player()) : 0);
     
    printf("nia %s\n", e_name);
     
    ECArmee *armee = new ECArmee(e_name,
                                                       (*cai)->Country()->Owner() ?
    	                                        (*cai)->Country()->Owner()->Player() : 0,
    				         *cai, sender->Channel()->Map()->NbSoldats());
    printf("nia %s\n", armee->ID());
    e_name est placée dans ID de armee, et ça imprime cela par exemple :

    nia AA
    nia
    ou

    nia AA
    nia ô£ ü³ Ž
    µ ¹
    En outre, ça plante un peu plus loins lors d'appels à des membres de la classe.
    Alors il semblerait qu'il y a un tout petit problème et je ne sais pas trop quoi faire.

    Alors je vous remercie si vous pouviez résoudre ce facheux problème.

    Progs.

    P.S.: Comme vous pouvez le voir, je dois appeler un constructeur dans ECArmee pour chaques classes mères, et c'est ennuyeux car ECBArmee::ECBArmee() appel ECBEntity::ECBEntity() qui fait un peu la meme chose que ECEntity::ECEntity(). Le soucis c'est que si j'appel un constructeur de ECBArmee() vide, et ben je n'ai pas accès à la variable, protected pourtant, intitulée "nb" :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    	ECArmee(const Entity_ID _name, ECBPlayer* _owner, ECase* _case, uint _nb)
    		: /*ECBArmee(_name, _owner, _case, _nb), */ECEntity(_name, _owner, _case, E_ARMEE), nb(_nb)
    	{}
    Units.h: In constructor 'ECArmee::ECArmee(const char*, ECBPlayer*, ECase*, uint)':
    Units.h:32: erreur: class 'ECArmee' does not have any field named 'nb'
    Edit: Note que si je mets "nb = _nb" dans le corps du constructeur plutot que dans la liste d'initialisatino ça marche... pourquoi ? Bien sur mettre le code ci-dessus dans le constructeur n'arrange rien.

  2. #2
    Membre averti
    Profil pro
    Responsable technique
    Inscrit en
    Février 2006
    Messages
    363
    Détails du profil
    Informations personnelles :
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Responsable technique

    Informations forums :
    Inscription : Février 2006
    Messages : 363
    Points : 353
    Points
    353
    Par défaut
    Laisse tomber le code il sert a rien.
    ------ECEntity
    | |
    | |
    | ECBArme
    | |
    | |
    ------ECArme

    Si j'ai bien compris ca donne ca. Le pb c'est que comme ECArme derive de ECEntity et de ECBArme tu recopie 2 fois les données de ECEntity donc ca peut pas marcher voila

  3. #3
    Membre à l'essai
    Inscrit en
    Décembre 2004
    Messages
    49
    Détails du profil
    Informations forums :
    Inscription : Décembre 2004
    Messages : 49
    Points : 19
    Points
    19
    Par défaut
    Ben apparament avec l'utilisation de "public virtual" pour ECEntity et ECBArmee il y aurra la *meme* base ECBEntity.

    Et le schema c'est :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
          ECBEntity
         /        \
    ECEntity   ECBArmee
         \        /
           ECArmee

  4. #4
    Membre à l'essai
    Inscrit en
    Décembre 2004
    Messages
    49
    Détails du profil
    Informations forums :
    Inscription : Décembre 2004
    Messages : 49
    Points : 19
    Points
    19
    Par défaut
    Petit rajout: Après quelques recherches de ma part, je conviendrai que c'est une structure en diamant.

  5. #5
    Rédacteur/Modérateur
    Avatar de JolyLoic
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2004
    Messages
    5 463
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Yvelines (Île de France)

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

    Informations forums :
    Inscription : Août 2004
    Messages : 5 463
    Points : 16 213
    Points
    16 213
    Par défaut
    J'ai un peu de mal à lire ton code rapidement. pourrais-tu en faire un ECM : Exemple Complet et Minimal. Complet dans le sens où l'exemple peut compiler, ce qui permet de reproduire le problème chez nous, minimal dans le sens où tu en as enlevé ce qui n'était pas pertinent (par exemple, les ECBPlayer...).

    Ensuite, je te conseille vivement d'utiliser les string et les iostream plutôt que les char* et printf. Ils sont plus simples et plus surs à utiliser.

    Enfin, dans le cas d'héritage virtuel, il faut dans le constructeur de la classe la plus dérivé appeller le constructeur de la classe de base(1). Ici, ta classe est construite avec le constructeur par défaut. Et comme tu utilises des char *, et que tu n'a pas du blinder ton constructeur par défaut (d'ailleur, mérite-t-il d'exister ?), tu te retouve avec une valeur non définie dans ton champ name.

    (1) Pourquoi ? Dans le cas :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    class A {};
    class B1 : public virtual A {};
    class B2 : public virtual A {};
    class C : public B1, public B2 {};
    Dans le constructeur de B1, on appelle un constructeur de A. Dans celui de B2 aussi, éventuellement un autre constructeur, ou bien avec d'autres valeurs pour ses paramètres.
    Dans le constructeur de C, vu qu'il n'y a qu'un seul sous objet de type A, le constructeur ne doit être appelé qu'une fois. Doit-on le faire en passant par B1 ou par B2 ? Pour lever cette ambiguïté, il a été décidé de ne le faire ni par l'intermédiaire du constructeur de B1, ni par celui de B2, mais d'expliciter l'appel de ce constructeur dans la class C.
    Ma session aux Microsoft TechDays 2013 : Développer en natif avec C++11.
    Celle des Microsoft TechDays 2014 : Bonnes pratiques pour apprivoiser le C++11 avec Visual C++
    Et celle des Microsoft TechDays 2015 : Visual C++ 2015 : voyage à la découverte d'un nouveau monde
    Je donne des formations au C++ en entreprise, n'hésitez pas à me contacter.

  6. #6
    Membre à l'essai
    Inscrit en
    Décembre 2004
    Messages
    49
    Détails du profil
    Informations forums :
    Inscription : Décembre 2004
    Messages : 49
    Points : 19
    Points
    19
    Par défaut
    Alors, pour commencer, pour les petits debugs j'ai l'habitude d'utiliser printf() plutot que std::cout << ... << std::endl qui est bien plus lent à taper.

    Pour le fait de donner un char* en argument au Entity_ID (qui se trouve être un char[3]), je ne vois pas en quoi ça pourrait être un problème. Sachant d'avance que la taille de la chaine sera de 3 chars (2 vrais chars et un zero), je préfers utiliser les char* que les std::string qui sont probablement plus lentes pour une telle utilisation.

  7. #7
    Rédacteur/Modérateur
    Avatar de JolyLoic
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2004
    Messages
    5 463
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Yvelines (Île de France)

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

    Informations forums :
    Inscription : Août 2004
    Messages : 5 463
    Points : 16 213
    Points
    16 213
    Par défaut
    Citation Envoyé par Progs
    Alors, pour commencer, pour les petits debugs j'ai l'habitude d'utiliser printf() plutot que std::cout << ... << std::endl qui est bien plus lent à taper.
    Une fois que tu as défini des opérateurs << pour tes classes, je pense qu'utiliser les iostream devient bien plus rapide que les printf...

    A part ça, le reste de ma réponse a-t-il résolu ton problème ?
    Ma session aux Microsoft TechDays 2013 : Développer en natif avec C++11.
    Celle des Microsoft TechDays 2014 : Bonnes pratiques pour apprivoiser le C++11 avec Visual C++
    Et celle des Microsoft TechDays 2015 : Visual C++ 2015 : voyage à la découverte d'un nouveau monde
    Je donne des formations au C++ en entreprise, n'hésitez pas à me contacter.

  8. #8
    Membre à l'essai
    Inscrit en
    Décembre 2004
    Messages
    49
    Détails du profil
    Informations forums :
    Inscription : Décembre 2004
    Messages : 49
    Points : 19
    Points
    19
    Par défaut
    Alors non je pense que pour un tel petit message de debug à interposer entre deux lignes de code il est plus simple de mettre un printf qui sera de toute façon viré une fois le problème résolu que de devoir surcharger l'opérateur <<...

    En outre j'ai trouvé une solution d'adaptation en prenant le schema suivant :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
       ECBEntity
             \
              ECBArmee
                     \
    ECEntity  ---  ECArmee
    Ça me permet d'éviter le problème meme si je trouve ça moins elegant qu'une hierarchie en diamant *fonctionnelle*.

  9. #9
    Rédacteur/Modérateur
    Avatar de JolyLoic
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2004
    Messages
    5 463
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Yvelines (Île de France)

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

    Informations forums :
    Inscription : Août 2004
    Messages : 5 463
    Points : 16 213
    Points
    16 213
    Par défaut
    Et as tu essayé de faire comme je t'ai dit, à savoir appller explicitement la constructeur de la classe la plus de base dans la classe la plus dérivée ? C'est directement la cause de ton problème premier, et je n'ai pas l'impression que tu l'ai tenté...
    Ma session aux Microsoft TechDays 2013 : Développer en natif avec C++11.
    Celle des Microsoft TechDays 2014 : Bonnes pratiques pour apprivoiser le C++11 avec Visual C++
    Et celle des Microsoft TechDays 2015 : Visual C++ 2015 : voyage à la découverte d'un nouveau monde
    Je donne des formations au C++ en entreprise, n'hésitez pas à me contacter.

  10. #10
    Membre à l'essai
    Inscrit en
    Décembre 2004
    Messages
    49
    Détails du profil
    Informations forums :
    Inscription : Décembre 2004
    Messages : 49
    Points : 19
    Points
    19
    Par défaut
    Je vais tenter, mais je ne bénéficie plus dans ce cas là des constructeurs des deux classes dérivées de la classe mère.

  11. #11
    Membre à l'essai
    Inscrit en
    Décembre 2004
    Messages
    49
    Détails du profil
    Informations forums :
    Inscription : Décembre 2004
    Messages : 49
    Points : 19
    Points
    19
    Par défaut
    Alors je viens de tester en appelant explicitement la classe de base, et tu avais raison, je ne rencontre plus de problèmes.
    Je te remercie, mon schema est bien plus clair ainsi.

  12. #12
    Expert éminent

    Inscrit en
    Novembre 2005
    Messages
    5 145
    Détails du profil
    Informations forums :
    Inscription : Novembre 2005
    Messages : 5 145
    Points : 6 911
    Points
    6 911
    Par défaut
    Citation Envoyé par Progs
    Je vais tenter, mais je ne bénéficie plus dans ce cas là des constructeurs des deux classes dérivées de la classe mère.
    Tu n'as pas l'air d'avoir compris: le constructeur des classes de bases indirectement virtuelles est appelé à partir de la classe instanciée; donc s'il n'y a pas d'appel visible, c'est le constructeur par défaut qui est appelé, et pas celui d'une des classes parents (laquelle d'ailleurs?).

    Au fait, ça n'a pas l'air d'avoir un sens que d'appeler le constructeur par défaut pour ECBEntity, pourquoi en as-tu déclaré et défini un?
    Les MP ne sont pas là pour les questions techniques, les forums sont là pour ça.

Discussions similaires

  1. Héritage en diamant
    Par DavidleVrai dans le forum Langage
    Réponses: 6
    Dernier message: 21/10/2012, 04h15
  2. Héritage en diamant et méthodes virtuelles
    Par Neckara dans le forum C++
    Réponses: 9
    Dernier message: 06/10/2012, 17h15
  3. Question sur l'héritage en diamant
    Par f56bre dans le forum Langage
    Réponses: 3
    Dernier message: 26/09/2011, 20h31
  4. Héritage multiple & diamant de la mort
    Par oodini dans le forum C++
    Réponses: 7
    Dernier message: 16/06/2010, 14h51
  5. Héritage en diamant
    Par Chatbour dans le forum Langage
    Réponses: 5
    Dernier message: 15/08/2008, 19h36

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