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 virtuel et constructeur.


Sujet :

C++

  1. #1
    Membre expert
    Avatar de Klaim
    Homme Profil pro
    Développeur de jeux vidéo
    Inscrit en
    Août 2004
    Messages
    1 717
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur de jeux vidéo
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 1 717
    Points : 3 344
    Points
    3 344
    Par défaut Héritage virtuel et constructeur.
    Voici une petite histoire d'exploration du language, avec quelques questions et suggestions.
    Ces informations pourraient je pense être utiles à mettre dans la FAQ :

    Ma question initiale était :

    Est-ce qu'un constructeur de class héritée virtuellement est appelée autant de fois qu'il y a d'héritage virtuel de cette classe où bien seulement autant de fois qu'il y a d'occurences de cette classe dans les données de l'occurence fille?




    A partir de là, tout s'enchaine...


    Une recherche dans la FAQ m'indique qu'il n'y a aucune information concernant l'héritage virtuel qui je le rappelle s'effectue sous cette forme aproximative :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
     
    class A {}; // classe mère
     
    class B : virtual public A { } ; // classe fille avec héritage virtuel 
     
    class C : virtual public A { } ; // classe fille avec héritage virtuel
     
    class X : public B , public C { }; // class fille dont les données de A seront "partagées" entre B et C , ce qui fait qu'il n'y a "pas" de problème de losange.
    (plus de détails sur la MSDN ou autre doc C++ j'imagine).

    Je ne connais cette feature du language moi-même que depuis quelques mois, mais j'en ai une utilisation a priori adequate sur le système sur lequel je travail actuellement. Je trouve que c'est une feature super interessante dans certains cas. Par contre, j'ai vu très très très peu de doc là dessus, dont dans la FAQ.

    Donc :

    1) Une entrée concernant cette feature dans la FAQ serait je pense interessante, principalement parceque http://cpp.developpez.com/faq/cpp/in...NITION_virtual sous entends que le mot clé "virtual" ne sert qu'a définir des fonctions virtuelles, or c'est faux.

    2) Est-ce qu'il y a une raison, une conséquence de la feature, qui la rends impopulaire? Ou bien est-ce simplement le manque d'interet habituel pour cette feature qui fait qu'on en parle jamais? Par exemple, apparamment ça semble résoudre le problème classique du losange. Est-ce correct? Si oui, pourquoi n'est-ce pas une solution plus connue? Y-t-il réellement une raison?



    Arrivé a ce questionnement, je me dit qu'une petite preuve serait facile à mettre en place. Je fais un programme de test avec VS2008Pro (VC9).
    Voici le programme exact initial :

    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
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
     
    #include <iostream>
     
    #define nullptr NULL
     
    class TestA
    {
    public:
    	TestA()
    	{
    		std::cout << "-> TEST A" << std::endl;
    	}
    	virtual ~TestA(){}
     
    	virtual void doIt() = nullptr;
     
    };
     
    class TestB : virtual public TestA
    {
    public:
     
    	TestB()
    		: TestA()
    	{
    		std::cout << "-> TEST B" << std::endl;
    	}
     
    	virtual ~TestB(){}
    };
     
    class TestC : virtual public TestA
    {
    public:
     
    	TestC()
    		: TestA()
    	{
    		std::cout << "-> TEST C" << std::endl;
    	}
     
    	virtual ~TestC(){}
    };
     
    class FinalTest 
    	: virtual public TestB
    	, virtual public TestC
    {
    public:
    	FinalTest()
    		: TestB()
    		, TestC()
    	{
    		std::cout << "-> FINAL TEST!!" << std::endl;
    	}
     
    	~FinalTest()
    	{
     
    	}
     
    	void doIt()
    	{
    		std::cout<< "JUST DO IT!!!" << std::endl;
    	}
    };
     
     
    int main()
    {
     
    	FinalTest finalTest;
    	finalTest.doIt();
     
    	std::system( "pause" );
     
    	return 0;
    }
    Avec ce compilo, j'obtiens le résultat suivant :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    -> TEST A
    -> TEST B
    -> TEST C
    -> FINAL TEST!!
    JUST DO IT!!!
    Appuyez sur une touche pour continuer...
    3) a) Est-ce bien le résultat que je suis censé obtenir d'après le standard? J'ai apris a me méfier un peu des entourloupes planquées que peuvent faire les compilos avec les parties du languages qui ne sont pas beaucoup utilisées (ou dont j'entends peu parler). Donc, est-ce correct d'un point de vue du standard ou est-ce qu'il y a du VS spécific là dedans?


    Ma première interprétation du résultat était que la partie TestA de l'occurence était initialisé donc une seule et unique fois via l'un des autres constructeurs (soit TestB, soit TestC).

    C'est alors que je me suis demandé quel pouvait être le constucteur précisément qui était à l'origine de l'appel du constructeur de TestA, car dans le cas où TestA prends un paramettre au constructeur il faudrait que l'auteur du système sache exactement quelle classe fille va fournir le paramettre.

    Je modifie donc mon test pour que TestA utilise un paramettre qu'il affiche, indiquant l'origine de l'appel :

    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
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    #include <iostream>
    #include <string>
     
    #define nullptr NULL
     
    class TestA
    {
    public:
    	TestA( const std::string& msg )
    	{
    		std::cout << "-> TEST A [ " << msg << " ]" << std::endl;
    	}
    	virtual ~TestA(){}
     
    	virtual void doIt() = nullptr;
     
    };
     
    class TestB : virtual public TestA
    {
    public:
     
    	TestB()
    		: TestA("FROM B")
    	{
    		std::cout << "-> TEST B" << std::endl;
    	}
     
    	virtual ~TestB(){}
    };
     
    class TestC : virtual public TestA
    {
    public:
     
    	TestC()
    		: TestA("FROM C")
    	{
    		std::cout << "-> TEST C" << std::endl;
    	}
     
    	virtual ~TestC(){}
    };
     
    class FinalTest 
    	: public TestB
    	, public TestC
    {
    public:
    	FinalTest()
    		: TestB()
    		, TestC()
    	{
    		std::cout << "-> FINAL TEST!!" << std::endl;
    	}
     
    	~FinalTest()
    	{
     
    	}
     
    	void doIt()
    	{
    		std::cout<< "JUST DO IT!!!" << std::endl;
    	}
    };
     
     
    int main()
    {
     
    	FinalTest finalTest;
    	finalTest.doIt();
     
    	std::system( "pause" );
     
    	return 0;
    }
    Quelle surprise quand le compilateur me sort cette erreur :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    main.cpp(53) : error C2512: 'TestA::TestA' : no appropriate default constructor available
    C'est seulement à ce moment là que je comprends que finalement c'est le constructeur de la classe fille TestFinal qui va être a l'origine de l'appel au constructeur de TestA, forcant l'auteur de TestFinal à définir lui-même quel paramettre mettre dans le constructeur de TestA.
    Ca semble logique et plutot pratique (évite d'avoir a se prendre la tête avec l'ordre dans l'héritage).

    Je corrige donc mon test :

    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
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    #include <iostream>
    #include <string>
     
    #define nullptr NULL
     
    class TestA
    {
    public:
    	TestA( const std::string& msg )
    	{
    		std::cout << "-> TEST A [ " << msg << " ]" << std::endl;
    	}
    	virtual ~TestA(){}
     
    	virtual void doIt() = nullptr;
     
    };
     
    class TestB : virtual public TestA
    {
    public:
     
    	TestB()
    		: TestA("FROM B")
    	{
    		std::cout << "-> TEST B" << std::endl;
    	}
     
    	virtual ~TestB(){}
    };
     
    class TestC : virtual public TestA
    {
    public:
     
    	TestC()
    		: TestA("FROM C")
    	{
    		std::cout << "-> TEST C" << std::endl;
    	}
     
    	virtual ~TestC(){}
    };
     
    class FinalTest 
    	: public TestB
    	, public TestC
    {
    public:
    	FinalTest()
    		: TestA( "FROM FINAL" )
    		, TestB()
    		, TestC()
    	{
    		std::cout << "-> FINAL TEST!!" << std::endl;
    	}
     
    	~FinalTest()
    	{
     
    	}
     
    	void doIt()
    	{
    		std::cout<< "JUST DO IT!!!" << std::endl;
    	}
    };
     
     
    int main()
    {
     
    	FinalTest finalTest;
    	finalTest.doIt();
     
    	std::system( "pause" );
     
    	return 0;
    }
    Ca compile et donne donc comme résultat :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    -> TEST A [ FROM FINAL ]
    -> TEST B
    -> TEST C
    -> FINAL TEST!!
    JUST DO IT!!!
    Appuyez sur une touche pour continuer...
    Ce qui semble logique a priori.

    3) b) Encore une fois, est-ce toujours standard?

    J'ai donc noté que les appels explicites au constructeur de TestA dans TestB et TestC sont ignorés au profit de l'appel de TestFinal... c'est bon à savoir.

    Fort de ce savoir, je me dit que normalement, comme TestB et TestC sont virtuels (la méthode TestA::doIt est virtuelle pure et n'est pas redéfinie), théoriquement les appels explicites au constructeur de TestA sont obsoletes puisqu'ils seront TOUJOURS ignorés.

    4) Est-ce correct ou est-ce que j'ai oublié quelque chose sur ce point?

    Pourtant, je fais une simple modification : je commente les appels a TestA::TestA() dans les constructeurs de TestB et TestC.

    Je tente de compiler mais sans succès, à ma grande surprise :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    main.cpp(23) : error C2512: 'TestA::TestA' : no appropriate default constructor available
    main.cpp(36) : error C2512: 'TestA::TestA' : no appropriate default constructor available
    DONC même si apparamment ces appels au constructeur de TestA ne sont jamais appelés, ils sont tout de même requis.

    5) Est-ce toujours Standard ou est-ce du à la façon dont à le compiler de parser le code?


    Voilà pour ma petite péripétie et mes questions. A vous messieurs les spécialisttes

  2. #2
    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
    Réponses rapides après lecture en diagonale :
    1/ Pourquoi pas. Mais voir 2
    2/ Parce que beaucoup de gens trouvent ça trop compliqué ? Au point que d'autres langages on limité sérieusement l'héritage multiple afin de ne pas avoir ce genre de situation ?
    3.a/ cf 3.b
    3.b/ Oui
    4/ Tu pourrais avoir une autre classe, concrète, dérivant de TestB uniquement. Quand il compile TestB, le compilateur ne peut pas savoir si ce sera le cas ou pas, d'où la demande de la présence d'un appel au constructeur de base.
    5/ cf 4
    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.

  3. #3
    Membre expert
    Avatar de Klaim
    Homme Profil pro
    Développeur de jeux vidéo
    Inscrit en
    Août 2004
    Messages
    1 717
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur de jeux vidéo
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 1 717
    Points : 3 344
    Points
    3 344
    Par défaut
    2) Vraiment? A cause du fait qu'il faut prévoir le coup avec les classes intermédiaires dans l'a hierarchie d'héritage peut être? En tout cas jusqu'ici ça m'a semblé largement plus évident pour designer avec "potentiellement" un problème de losange. Là je le fais apparamment directement sauter.

    4) Oui mais ce que je ne comprends pas c'est que tel qu'est déclaré TestB, il est impossible (apparamment) qu'une classe héritant uniquement de TestB n'ai pas à appeler explicitement le constructeur de TestA (je viens de tester et effectivement si j'appelle pas TestA dans la classe héritée, j'ai une erreur de compilation). Donc tel quel il est absolument impossible que TestB ou TestC fassent appel dans leur constructeurs à eux à TestA::TestA()....si? Ou le compilo détecte pas cette info peut être?

  4. #4
    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,

    Reprenons peut-être un peu depuis le début...

    Peut-être as-tu déjà entendu parler des "big four"

    Ce sont les quatre grandes méthodes que le compilateur peut fournir si tu ne le fais pas.

    Il s'agit
    1. du constructeur par défaut (comprend: ne prenant aucun paramètre)
    2. du constructeur par copie
    3. de l'opérateur d'affectation
    4. du destructeur.
    Le tout, en sachant que si tu défini un constructeur par copie, tu sera obligé de définir également un constructeur par défaut

    Le comportement de ces quatre grands est clairement connu à l'avance, et je me contenterai de rappeler celui du constructeur par défaut: il appelle le constructeur par défaut de la classe "mère", puis le constructeur par défaut de chacun des membres de la classe.

    En outre,si, dans un constructeur que tu définis toi même, tu ne précise pas explicitement le constructeur pour la classe mère ou pour l'un des membres, ce sera le constructeur par défaut qui sera appelé pour cette classe, à moins que la déclaration du constructeur n'ait été précédé du mot clé explicit (mais ca, c'est une autre histoire ).

    Ainsi, le code suivant serait tout à fait valide:
    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
    Class A
    {
        public:
            /* je laisse le compilateur donner le constructeur par défaut 
             * mais je définis le destructeur virtuel, pour m'assurer de la 
             * bonne destruction des objets en cas de polymorphisme
             */
            virtual ~A(){} /* idéalement, une fonction virtuelle ne devrait pas
                            * être définie "inline", mais passons :P 
                            */
    };
    class B : public A
    {
        public:
            /* comme il existe un constructeur par défaut (fourni par le 
             * compilateur) pour la classe A, il sera appelé automatiquement
             * lors de la construction d'une instance de B
             */
            B(int i):i(i){}
            virtual ~B(){}
    };
    class C: public A
    {
        public:
           /* le meme raisonnement est suivi ici ;) */
            C(const std::string& z):z(z){}
            virtual ~C(){}
    }
    Maintenant, voyons un peu ce qui pourrait se passer avec un héritage en losange...

    Pour cela, modifions un peu la classe A et la classe C en
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class A
    {
        public:
            A(const std::string& str = "salut"):str(str){}
            virtual ~A(){}
        protected:
            std::string str;
    };
    class C : public A
    {
        public:
            C(const std::string& z):A(z){}
            virtual ~C(){}
    };
    Et voyons ce qui se passerait si nous créions une classe D qui hériterait de B et de C:
    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
    class D : pulblic B, public C
    {
        public:
            D(int i, const std::string& z):B(i),C(z){}
            virtual ~D(){}
            void function()
            {
                std::cout<<str; //Aiie... voir plus loin 
            }
    }; 
    int main()
    {
        D d(2,"bonjour");
         d.function();
        /*...*/
        return 0;
    }
    J'ai fourni une valeur par défaut au paramètre fourni au constructeur de A, ce qui m'a permis de ne pas modifier la classe B.

    Lors de la construction de B, une instance de A est créée avec la valeur par défaut: "salut"

    Mais lors de la construction de C, une instance de A est créée avec la valeur fournie en paramètre du constructeur de C.

    Les deux instances existent donc au sein de la même instance de D, et, quand on crée la variable d dans la fonction main, on a
    D::B::A::str = "salut"
    D::C::A::str = "bonjour"
    Lorsque l'on demande, pour l'instance de D, d'afficher str, le compilateur ne sait pas s'il faut afficher la valeur de D::B::A::str ou celle de D::C::A::str...

    C'est pour éviter ce genre de problème (et la création de deux instances de la classe de base) que le mot clé virtual est rajouté dans l'héritage.

    Il a pour but de faire comprendre au compilateur que
    attention, tu ne devra créer qu'une seule instance de la classe A, et donc, tu ne devra appeler qu'une seule fois le constructeur
    Mais il est très important de comprendre que l'héritage virtuel ne va aucunement empêcher l'instanciation de la classe dérivée...

    Ainsi, si nous en arrivons à un code proche de
    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
    cclass A
    {
        public:
            /* la valeur par défaut permet d'avoir un constructeur ne prenant
             * pas d'argument
             */
            A(const std::string& str = "salut"):str(str){}
            virtual ~A(){}
        protected:
           std::string str;
    };
    class B : virtual public A
    {
        public:
            /* utilise la valeur par défaut pour le constructeur */
            B(int i) :i(i){}
            virtual ~B(){}
            void function()
            {
                std::cout<<i<<" "<<str<<std::endl;
            }
        private:
            int i;
    };
    class C : virtual public A
    {
        public:
            /* utilise la valeur transmise par l'argument pour le constructeur de
             * A
             */
            C(const std::string& z):A(z){}
            virtual ~C(){}
            void printC(){std::cout<<"juste la chaine "<<str<<std::endl;}
    };
    /* c'est pour cette classe que l'héritage de A est devenu virtuel */
    class D : public B,public C
    {
        public:
            /* contre toute attente, c'est le constructeur par défaut qui sera
             * appelé pour la classe A
             */
            D(int i, const std::string& z):B(i),C(z){}
            virtual ~D(){}
    };
    Le code suivant sera donc tout à fait valide:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    int main()
    {
        A a; /* aucun problème: ce n'est pas une classe abstraite */
        B b(3); /* pas plus une classe abstraite que A */
        C c("bonjour"); /* ici non plus */
        D d(5,"au revoir"); /*ni ici */
        /* pas moyen d'accéder à la valeur de a.str*/
        b.function();
        c.printC();
        d.function();
        d.printC();
        return 0;
    }
    qui nous donnera le résultat:
    3 salut
    juste la chaine bonjour
    5 salut
    juste la chaine salut
    Bref, ce dernier code est riche en enseignements...:

    D'abord, il indique bel et bien que ce n'est pas parce qu'une classe hérite virtuellement d'une autre qu'il est impossible de l'instancier (il n'est impossible d'instancier que les classes abstraites )

    Ensuite, il met en évidence les problèmes liés à l'utilisation du constructeur (considérable comme étant) par défaut et à l'utilisation de l'héritage en losange: à lire le code D d(5,"au revoir");, nous aurions pu nous attendre à ce que la chaine de caractères contiennent... au revoir, si nous en venions à oublier qu'il s'agit d'un héritage en losange
    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

  5. #5
    Membre expert
    Avatar de Klaim
    Homme Profil pro
    Développeur de jeux vidéo
    Inscrit en
    Août 2004
    Messages
    1 717
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur de jeux vidéo
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 1 717
    Points : 3 344
    Points
    3 344
    Par défaut
    koala01> Merci pour le récapitulatif A priori je n'ai rien apris de nouveau donc j'imagine que j'ai bien du cerner l'ensemble maintenant.

    Par contre, je ne m'explique toujours pas que TestB et TestC soient toujours obligés d'apperller TestA alors qu'elles sont bien abstraites ET que n'importe quelle classe héritée est obligée d'appeler de toutes manière TestA... M'enfin j'imagine que c'est les règles de définition d'une classe (isolée) qui veut ça.

  6. #6
    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 Klaim Voir le message

    Par contre, je ne m'explique toujours pas que TestB et TestC soient toujours obligés d'apperller TestA alors qu'elles sont bien abstraites ET que n'importe quelle classe héritée est obligée d'appeler de toutes manière TestA... M'enfin j'imagine que c'est les règles de définition d'une classe (isolée) qui veut ça.
    Soit précis, cela t'aidera

    Tu n'a pac compris que les constructeurs de testB et testC soient obligé d'appeler le constructeur de TestA... Et pourtant, c'est tout simple:

    Si tu n'appelle pas explicitement le constructeur de la classe mere ou des membre, le compilateur va essayer d'appeler le constructeur par défaut (ne prenant pas d'argument) pour la classe mere et pour les membres.

    Ainsi, si tu viens à écrire une classe sous la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    class A :/*virtual*/ public B
    {
        public:
            A(Type var, Type2 v2) :var(var){}
            /*...*/
        private:
            Type var;
             Type2 v2;
    };
    le compilateur modifiera automatiquement le code en

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    class A : /* virtual */ public B
        public:
            A(Type var): B(), var(var), v2(){}
            /*...*/
        private:
            Type var;
             Type2 v2;
    };
    Mais, comme le compilateur ne rajoute un constructeur par défaut qu'à la condition sine qua non qu'il n'y ait pas d'autre constructeur, si tu défini un constructeur prenant un argument obligatoire (n'ayant pas de valeur par défaut), le compilateur ne saura pas appeler celui... qui ne prend aucun argument... Ainsi, tu te fera "jeter" par le compilateur si tu as défini pour B ou pour Type2 un constructeur qui prend un argument obligatoire (qui n'a pas de valeur par défaut)...

    [mode DISGRESSION ="on"]
    Les dernières versions de Gcc fournissent la possibilité d'avoir des avertissements basés sur (more) effective C++...

    Si tu active cette option, tu obtiendra un avertissement te disant qu'il faudrait appeler le constructeur de B et de v2
    [mode DISGRESSION ="off"]

    On pourrait estimer que, dans le cas où la classe dérivée serait abstraite et où l'héritage serait virtuelle, l'appel automatique du constructeur par défaut au cas où nous ne l'appelons pas explicitement devrait pouvoir ne pas être effectué, la classe ne pouvant de toutes manières pas être instanciée seule et obligeant à prévoir un héritage...

    Mais, bien que n'ayant pas vérifié dans la norme si le cas est prévu, je présumes qu'il s'agit - au mieux - d'un comportement indéfini...

    A défaut de pouvoir te confirmer que la norme n'indique rien sur le sujet, je comprend les concepteur du compilateur qui auront décidé que le comportement indéfini sera le comportement le plus facile à mettre en oeuvre: le comportement par défaut du compilateur lorsque tu n'appelle pas explicitement le constructeur de la classe parent ou des membres
    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

  7. #7
    Membre expert
    Avatar de Klaim
    Homme Profil pro
    Développeur de jeux vidéo
    Inscrit en
    Août 2004
    Messages
    1 717
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur de jeux vidéo
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 1 717
    Points : 3 344
    Points
    3 344
    Par défaut
    Je ne vois que ça aussi. (je n'ai pas de standard a disposition pour vérifier, d'où le thread).

  8. #8
    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
    J'ai (peut être mal) cherché dans la norme pour voir si elle parlait de l'héritage virtuel des classes abstraites, et je n'ai rien trouvé...

    De là à en déduire qu'une classe abstraite qui hérite virtuellement d'une autre suivra exactement le même schéma qu'une classe concrète, il n'y a qu'un pas... que je franchis peut être un peu vite
    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

  9. #9
    Expert éminent sénior
    Avatar de Luc Hermitte
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2003
    Messages
    5 275
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Août 2003
    Messages : 5 275
    Points : 10 985
    Points
    10 985
    Par défaut
    <culture>Le meilleur document que j'ai pu croiser au sujet de l'héritage multiple: http://www.ddj.com/cpp/184402074
    </>
    Pour le standard le dernier brouillon en date du c++0x est dispo en ligne -> n2606
    Blog|FAQ C++|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS|Bons livres sur le C++
    Les MP ne sont pas une hotline. Je ne réponds à aucune question technique par le biais de ce média. Et de toutes façons, ma BAL sur dvpz est pleine...

  10. #10
    Membre averti Avatar de Trunks
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Mai 2004
    Messages
    534
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Mai 2004
    Messages : 534
    Points : 412
    Points
    412
    Par défaut
    Citation Envoyé par koala01 Voir le message
    Ensuite, il met en évidence les problèmes liés à l'utilisation du constructeur (considérable comme étant) par défaut et à l'utilisation de l'héritage en losange: à lire le code D d(5,"au revoir");, nous aurions pu nous attendre à ce que la chaine de caractères contiennent... au revoir, si nous en venions à oublier qu'il s'agit d'un héritage en losange
    On peut corriger le problème en appelant explicitement le constructeur de A au niveau du constructeur de D.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    D(int i, const std::string& z):A(z),B(i),C(z){}
    Quant au z de C, il est tout simplement est ignoré.

  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
    Points : 13 017
    Points
    13 017
    Par défaut
    Salut,
    F.A.Q. : Dans quel ordre sont construits les différents composants d'une classe ? Aborde la construction avec héritage virtuel.

  12. #12
    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
    Le mots-clé virtual pour l'héritage indique "c'est à la classe fille la plus dérivié d'instancier le sous-objet hérité de ce type", d'où le comportement que tu observes, à savoir c'est la liste d'initialisation de D qui intialise A.

    Une classe abstraite c'est une classe qui ne peut être instancier exceptée en tant que sous-objet hérité. Il est donc normal que les listes d'initalisation de B et C intialise A (*).

    (*) Même si en réalité ca ne sera jamais à eux d'intialiser le sous-objet hérité A, car B et C sont abstraite donc ne peuvent être instancier qu'en tant que sous_objet hérité, mais dans ce cas ca sera à la classe fille la plus dérivé d'initialiser le sous-objet hérité A, donc jamais à B ou C.

  13. #13
    Membre expert
    Avatar de Klaim
    Homme Profil pro
    Développeur de jeux vidéo
    Inscrit en
    Août 2004
    Messages
    1 717
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur de jeux vidéo
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 1 717
    Points : 3 344
    Points
    3 344
    Par défaut
    Belle réssucitation de thread

  14. #14
    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
    Points : 13 017
    Points
    13 017
    Par défaut
    Wouh. J'avais pas fait attention aux dates. Je comprend mieux pourquoi tu dis qu'il n'y avait pas d'entrée dessus dans la faq

  15. #15
    Membre averti Avatar de Trunks
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Mai 2004
    Messages
    534
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Mai 2004
    Messages : 534
    Points : 412
    Points
    412
    Par défaut
    Citation Envoyé par Klaim Voir le message
    Belle réssucitation de thread
    Effectivement, je cherchais des informations sur l'héritage virtuel, et je suis tombé sur cette discussion. Vu que j'avais trouvé (par hasard) comment résoudre le problème soulevé dans ce thread (initialisation de A), il m'a paru intelligent d'apporter le complément d'information si d'autres personnes comme moi tombent dessus un jour

  16. #16
    Membre du Club
    Homme Profil pro
    12
    Inscrit en
    Mai 2014
    Messages
    67
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 34
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : 12
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Mai 2014
    Messages : 67
    Points : 61
    Points
    61
    Par défaut constructeur virtual
    bonsoir,

    normalement un constructeur ne "doit" jammais être virtual mais est ce que c'est faux au niveau de la syntaxe!?

    j'avais un examen en c++ et cette question est posée dans l'examen j'ai expliqué pourquoi il ne doit pas être virtual, mais je pense que j'ai mentionné que ca va pas genèrer quand meme une erreur dans le code ma réponse est elle juste ?

  17. #17
    Expert éminent sénior

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 189
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Essonne (Île de France)

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

    Informations forums :
    Inscription : Juin 2007
    Messages : 5 189
    Points : 17 141
    Points
    17 141
    Par défaut
    Ca, c'est un double post avec une discussion que tu as ouverte
    Mes principes de bases du codeur qui veut pouvoir dormir:
    • Une variable de moins est une source d'erreur en moins.
    • Un pointeur de moins est une montagne d'erreurs en moins.
    • Un copier-coller, ça doit se justifier... Deux, c'est un de trop.
    • jamais signifie "sauf si j'ai passé trois jours à prouver que je peux".
    • La plus sotte des questions est celle qu'on ne pose pas.
    Pour faire des graphes, essayez yEd.
    le ter nel est le titre porté par un de mes personnages de jeu de rôle

  18. #18
    Rédacteur/Modérateur


    Homme Profil pro
    Network game programmer
    Inscrit en
    Juin 2010
    Messages
    7 115
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : Canada

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 115
    Points : 32 967
    Points
    32 967
    Billets dans le blog
    4
    Par défaut
    Sinon, faire le test prend ~5s ..
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    class B {
    public:
    	virtual B() {}
    };
    Et visual studio a une réponse suffisament claire.
    error C2633: 'B'*: 'inline' est la seule classe de stockage autorisée pour les constructeurs
    Pensez à consulter la FAQ ou les cours et tutoriels de la section C++.
    Un peu de programmation réseau ?
    Aucune aide via MP ne sera dispensée. Merci d'utiliser les forums prévus à cet effet.

Discussions similaires

  1. Question sur l'héritage virtuel
    Par ezsoft dans le forum C++
    Réponses: 10
    Dernier message: 20/08/2008, 09h13
  2. template, héritage virtuel: gcc vs Visual II
    Par 3DArchi dans le forum Langage
    Réponses: 5
    Dernier message: 07/08/2008, 15h53
  3. Héritage Virtuel et SDL
    Par Qualimero dans le forum SDL
    Réponses: 6
    Dernier message: 18/07/2006, 04h49
  4. Réponses: 2
    Dernier message: 04/12/2005, 21h10
  5. [POO-Héritage] Appel du constructeur en PHP4.3.2
    Par raoulchatigre dans le forum Langage
    Réponses: 4
    Dernier message: 28/11/2005, 15h37

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