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 privé et virtuel


Sujet :

C++

  1. #1
    Membre habitué
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Avril 2007
    Messages
    142
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 42
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Avril 2007
    Messages : 142
    Points : 185
    Points
    185
    Par défaut héritage privé et virtuel
    Bonjour,

    j'ai une question qui me turlupine. Un de mes collègues a fait le test suivant :
    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
    class cA
    {
    public:
    	cA(std::string _caller = "defaut")
       {
    	   std::cout << "cA construit par " << _caller.c_str() << std::endl;
       }
    };
     
    class cB : private virtual cA
    {
    public:
     cB():cA("cB"){}
    };
     
    class cC : private virtual cA
    {
    public:
       cC(){}
    };
     
    class cD : public cB, public cC
    {
    public:
       cD():cB::cA("cD") {}
    };
    Et... ça compile !!

    Et si l'on enlève le fait que l'héritage soit virtuel entre cA et cB et cC, alors ça ne compile plus puisque le ctor de cA passe privé dans cB et est donc inaccessible dans cD.

    On suppose donc que le virtual rend automatiquement l'héritage public ou protected ?

    Et vous me direz surement que ça n'a pas de sens de faire un héritage privé virtuel puisque le virtuel est là pour éviter que les attributs de cA soient en double dans cD, et donc avec un héritage privé ils ne seront même pas accessibles dans cD.
    C'est vrai, mais on se demandait juste ce qu'avait prévu la norme dans ce cas bizarroïde...

    Merci d'avance pour vos lumières.
    "Le problème du monde, c'est que les imbéciles sont présomptueux et les gens intelligents bourrés de doutes" B. Russell

  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,

    Ma première réaction est de penser à un sérieux problème de conception

    En effet, l'héritage privé ne prend pas la sémantique "est-un" mais bien la sémantique "est implémenté en terme de", qui revient, en gros, à une agrégation plus ou moins cachée.

    Il semble donc cohérent d'estimer que la partie "cA" héritée de "cB" est bel et bien différente (selon ton exemple) de la partie cA héritée de cC.

    Il suffit donc d'appeler le constructeur adéquat de cA dans cB ET dans cC, le tout, sans héritage virtuel (mais en gardant l'héritage privé)

    Si tu t'es simplement trompé de visibilité pour l'héritage, le problème de conception est peut être situé au niveau de ta décision de faire hériter cB et cC de cA, car l'une ou l'autre des classes dérivées semble décidément enfreindre le principe de substitution de Liskov.

    Mais c'est peut-être (ou plus tôt sans doute) du au fait que cA est à la base une classe beaucoup trop générique
    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 habitué
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Avril 2007
    Messages
    142
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 42
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Avril 2007
    Messages : 142
    Points : 185
    Points
    185
    Par défaut
    Merci pour la réponse.

    Pour le problème de conception, je suis tout à fait d'accord, mais comme je l'avais écrit, c'est juste pour tester ce qu'on peut faire et ne pas faire en terme de syntaxe, ce code n'as absolument pas vocation à être intégré à un programme. ^^

    J'ai refait quelques tests pour essayer d'y voir plus clair, et une fois de plus les résultats me surprennent:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class cA
    {
    public:
    	cA():a(0) {}
    	int a;
    };
     
    class cB : public cA
    {
    public:
    	cB() {a = 1;}
    };
    Ce code compile, sans problème, normal.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class cA
    {
    public:
    	cA():a(0) {}
    	int a;
    };
     
    class cB : public cA
    {
    public:
    	cB():a(1) {}
    };
    Et là ça ne compile plus, alors qu'en substance c'est le même code... je crois qu'il y a une histoire d'ordre qui diffère entre les 2 codes, l'initialisation de a n'est pas faite au même moment, ce qui peut peut-être expliquer la chose ?

    Et enfin, lorsque je fais :
    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
    class cA
    {
    public:
    	cA():a(0) {}
    	int a;
    };
     
    class cB : public cA
    {
    public:
    	cB():cA() {}
    };
     
    class cC : public cB
    {
    public:
    	cC():cA() {}
    };
    Ca ne compile pas (error C2614: 'cC'*: initialisation de membre non conforme*: 'cA' n'est ni une base ni un membre), ce qui parait logique, c'est la même erreur qu'au dessus quand je veux initialiser a avec a(1).

    Mais quand je fais :
    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
    class cA
    {
    public:
    	cA():a(0) {}
    	int a;
    };
     
    class cB : public virtual cA
    {
    public:
    	cB():cA() {}
    };
     
    class cC : public cB
    {
    public:
    	cC():cA() {}
    };
    Alors ça fonctionne !! Je sais bien que le mot-clef virtual n'a rien à faire ici, mais j'aimerais comprendre ce qu'il se passe...
    "Le problème du monde, c'est que les imbéciles sont présomptueux et les gens intelligents bourrés de doutes" B. Russell

  4. #4
    Membre actif
    Profil pro
    Inscrit en
    Mars 2010
    Messages
    188
    Détails du profil
    Informations personnelles :
    Âge : 37
    Localisation : France

    Informations forums :
    Inscription : Mars 2010
    Messages : 188
    Points : 248
    Points
    248
    Par défaut
    Citation Envoyé par Lawyer666 Voir le message
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class cA
    {
    public:
    	cA():a(0) {}
    	int a;
    };
     
    class cB : public cA
    {
    public:
    	cB() {a = 1;}
    };
    Ce code compile, sans problème, normal.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class cA
    {
    public:
    	cA():a(0) {}
    	int a;
    };
     
    class cB : public cA
    {
    public:
    	cB():a(1) {}
    };
    Et là ça ne compile plus, alors qu'en substance c'est le même code... je crois qu'il y a une histoire d'ordre qui diffère entre les 2 codes, l'initialisation de a n'est pas faite au même moment, ce qui peut peut-être expliquer la chose ?
    quand tu fais cB():a(1) {} tu initialise la variable "a" à 1 au moment de sa création. Alors que lorsque tu fais a = 1; tu assigne juste une valeur à une variable déjà créer, chose que tu peux faire même en dehors d'un constructeur.
    Cependant ça n'est pas à la classe cB de s'occuper de la création de "a" c'est le boulot de la classe cA. c'est pourquoi le compilo t'envoie dans les roses .

    Alors ça fonctionne !! Je sais bien que le mot-clef virtual n'a rien à faire ici, mais j'aimerais comprendre ce qu'il se passe...
    J'avoue ne pas comprendre non plus

  5. #5
    Membre émérite
    Avatar de white_tentacle
    Profil pro
    Inscrit en
    Novembre 2008
    Messages
    1 505
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2008
    Messages : 1 505
    Points : 2 799
    Points
    2 799
    Par défaut
    Citation Envoyé par Lawyer666 Voir le message
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class cA
    {
    public:
    	cA():a(0) {}
    	int a;
    };
     
    class cB : public cA
    {
    public:
    	cB():a(1) {}
    };
    Et là ça ne compile plus, alors qu'en substance c'est le même code... je crois qu'il y a une histoire d'ordre qui diffère entre les 2 codes, l'initialisation de a n'est pas faite au même moment, ce qui peut peut-être expliquer la chose ?
    Oui. a est construit dans le constructeur cA. La première chose que va faire cB, c'est appelé cA. Ensuite, il va construire ses membres avec la liste d'initialisation. Enfin, il va exécuter le code entre {}. Ici, comme a appartient à cA, tu ne peux pas l'initialiser dans la liste d'initialisation de cB, parce que c'est cA qui le "crée". Par contre, tu peux modifier sa valeur dans cB (ce qui est différent).

    Mais quand je fais :
    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
    class cA
    {
    public:
    	cA():a(0) {}
    	int a;
    };
     
    class cB : public virtual cA
    {
    public:
    	cB():cA() {}
    };
     
    class cC : public cB
    {
    public:
    	cC():cA() {}
    };
    Alors ça fonctionne !! Je sais bien que le mot-clef virtual n'a rien à faire ici, mais j'aimerais comprendre ce qu'il se passe...
    C'est explicitement autorisé d'initialiser les virtual base class, même non directes. Il me semble que c'est parce que l'initialisation de Ca demandée par Cb ne sera pas effectuée quand on construit un Cc (c'est la classe tout en bas de la hiérarchie qui est responsable de créer les base-class virtuelles, ça ne peut pas marcher autrement).

  6. #6
    Membre habitué
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Avril 2007
    Messages
    142
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 42
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Avril 2007
    Messages : 142
    Points : 185
    Points
    185
    Par défaut
    Merci pour vos réponses, je comprends mieux...
    "Le problème du monde, c'est que les imbéciles sont présomptueux et les gens intelligents bourrés de doutes" B. Russell

  7. #7
    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 white_tentacle Voir le message
    C'est explicitement autorisé d'initialiser les virtual base class, même non directes. Il me semble que c'est parce que l'initialisation de Ca demandée par Cb ne sera pas effectuée quand on construit un Cc (c'est la classe tout en bas de la hiérarchie qui est responsable de créer les base-class virtuelles, ça ne peut pas marcher autrement).
    Pire, encore, il est indispensable d'initialiser les classes de base virtuelles séparément.

    Imaginons une classe de base dont le constructeur prend un argument (quel qu'en soit le type):
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    class Base
    {
        public:
            Base( un_type const & t):t_(t){}
            /* obligatoire pour permettre la destruction des classes dérivées */
            virtual ~Base(){}
        private:
            un_type t_;
    };
    Pour les classe qui en héritent directement, il n'y a pas de problème: on appelle, tout simplement, le constructeur de la classe de base dans la liste d'initialisation du constructeur:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class Derviee1 : virtual public Base
    {
        public:
            Derivee1(un_type const & t) : Base(t){}
    };
    class Derivee2 : virtual public Base
    {
        public:
            Derivee2(un_type const & t) : Base(t){}
    };
    Jusque là, il n'y a pas de problème: l'héritage ne serait pas virtuel, nous n'agirions pas différemment (ni s'il était privé, d'ailleurs )

    Les choses se compliquent lorsque l'on a une classe qui hérite de Derivee1 et de Derivee2, situation qui justifie justement l'héritage virtuel:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    class Derivee3 : public Derivee1, public Derivee2
    {
        public:
            /* ce que l'on aurait tendance à faire */
            Derivee3(un_type const & t1, un_type const & t2): 
                Derivee1(t1),Derivee2(t2){}
    };
    Ce serait suffisant si nous n'avions pas un héritage virtuel entre Derivee1 (ou Derivee2) et Base, parce que l'instance de Base issue de Derivee1 serait initialisée avec t1 et l'instance de Base issue de Derivee2 serait initialisée avec t2, et ce serait logique.

    Seulement, du fait de l'héritage virtuel, il n'y a pas deux instances de Base, vu que c'est justement ce que l'on souhaite éviter grâce à lui.

    Du coup, comment le compilateur pourrait il déterminer si la seule instance de Base est initialisée à partir de Derivee1 ou si elle l'est à partir de Derivee2

    En fait, ce n'est ni l'un, ni l'autre: il faut initialiser Base de manière tout à fait séparée, et, pourquoi pas, grâce au résultat d'une opération quelconque entre t1 et t2 (pour autant que l'opération soit en accord avec la sémantique de un_type)
    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

  8. #8
    En attente de confirmation mail

    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2004
    Messages
    1 391
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 33
    Localisation : France, Doubs (Franche Comté)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Points : 3 311
    Points
    3 311
    Par défaut
    C'est d'ailleurs l'objectif de virtual (pour l'héritage) : délégué la création d'un classe de base à la classe la plus dérivé. C'est directement relié au fait que l'héritage virtuel sert à éviter d'hériter deux fois d'une même base, sans ca comment la classe la plus dérivés saurait comment initialisé la classe de base virtuel ? En reprenant l'exemple de koala, comme le compilateur determinerait si il doit utiliser t1 ou t2 ?

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

Discussions similaires

  1. Héritage et fonction virtuelle
    Par gzeus dans le forum C++
    Réponses: 5
    Dernier message: 05/07/2008, 00h42
  2. héritage et méthodes virtuelles ?
    Par Didine981 dans le forum C++
    Réponses: 4
    Dernier message: 08/12/2007, 13h43
  3. Réponses: 16
    Dernier message: 21/05/2007, 01h04
  4. Exceptions, héritage et méthodes virtuelles
    Par Nuwanda dans le forum C++
    Réponses: 13
    Dernier message: 23/05/2006, 12h06
  5. [héritage privé] appel du constructeur de base
    Par PINGOUIN_GEANT dans le forum C++
    Réponses: 4
    Dernier message: 19/10/2004, 14h05

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