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 :

interface et héritage multiple


Sujet :

C++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Expert éminent

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 202
    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 202
    Par défaut interface et héritage multiple
    Bonjour à tous.
    J'ai un problème que je ne comprends pas et dont j'ai une solution qui ne me plait pas du tout.

    Soient trois d'interfaces avec implémentation par défaut:
    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
     
    struct BaseSimple {
        virtual ~BaseSimple() = 0;
        virtual void simple() { cout << "BaseSimple" << endl; }
    };
     
    struct BaseRiche: public BaseSimple {
        virtual ~BaseRiche() = 0;
        virtual void riche() {}
    };
     
    struct Biduloide {
        virtual ~Biduloide() = 0;
        virtual void operator() () {}
    };
    Les trois destructeurs sont définis à l'abri des fichiers .cpp.

    Jusque là, tout va bien, j'ai trois classes abstraites, dont un héritage.

    Viennent ensuite deux classes qui ne pose pas encore problème.
    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
    class Simple: public BaseSimple, public Biduloide {
    public:
        virtual void simple() override final {
            /* un peu d'intelligence */
            do_simple();
        }
    protected:
        virtual void do_simple() {}
    };
     
     
    class MoinsSimple: public Simple {
    protected:
        virtual void do_simple() override final { cout << "MoinsSimple" << endl;}
    };
    Si à présent j'écris ceci:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    BaseSimple * s = new MoinsSimple();
    s->simple();
    J'espère bien voir s'afficher "MoinsSimple", puisque c'est ainsi que doit fonctionner les fonctions virtuelles.
    En effet, s->simple() est Simple::simple(), qui appelle Simple::do_simple(), virtuelle donc trouvée dans MoinsSimple.

    Ajoutons une nouvelle classe:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    class Riche: public BaseRiche, public MoinsSimple {
    public:
        using MoinsSimple::simple
    };
    D'après le standard, j'ai le layout suivante: <Riche> = < <<BaseSimple> BaseRiche> <<<BaseSimple> Simple> MoinsSimple> >.
    Je vois bien que j'ai deux BaseSimple.
    J'ai ainsi trois définitions de simple(): deux dans les BaseSimple, et une dans Simple (cette dernière version étant marquée final)

    Si ma lecture était bonne, simple serait ambigüe dans Riche, donc j'ai écris un using MoinsSimple::simple.
    Or, ce n'est pas le cas.
    Le code suivant affiche "BaseSimple"
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    BaseRiche * s = new Riche();//pour des raisons de smart_ptr à partager un peu partout
    s->simple();
    EDIT: dans mon vrai problème le pointeur n'est pas Riche* mais BaseRiche*

    J'ai bien une solution en rendant Simple::simple() non finale, et en écrivant:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    class Riche: public BaseRiche, public MoinsSimple {
    public:
        virtual void simple() override final {MoinsSimple::simple();}
    };
    Mais je n'ai pas de raison de le faire, puisque c'est précisément pour la rendre final que j'ai créé do_simple().

    Je pourrai peut-être m'en sortir avec de l'héritage virtuel, mais j'aimerai surtout comprendre pourquoi j'ai ce problème.

  2. #2
    Membre Expert
    Profil pro
    Inscrit en
    Mars 2007
    Messages
    1 415
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Mars 2007
    Messages : 1 415
    Par défaut
    Hello leternel

    Avec quel compilo cela pose-t-il problème ? Je viens de tester avec GCC 4.8, GCC 5.2, Clang 3.4, Clang 3.8 et tous se comportent bien de la manière que tu attends. Mais pas Visual Studio qui a le comportement que tu cites... A voir ce que ça donne sur la dernière Preview, je n'ai pas encore pu tester.

    Je pense que tu as affaire à un bug de cl.exe

  3. #3
    Expert éminent

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 202
    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 202
    Par défaut
    En l'occurence, gcc version 4.8.2 20140120 (Red Hat 4.8.2-16) (GCC)

    Je suis allé un peu vite dans ma simplification de l'exemple.

    BaseRiche * s = new Riche(); s->simple(); produit "BaseSimple", avec ou sans using.
    Sans using, Riche * s = new Riche(); s->simple(); ne compile pas (ambigu), et fonctionne comme souhaité avc.

    La solution, si je ne veux pas lever le final, c'est l'héritage virtuel.

  4. #4
    Membre Expert
    Profil pro
    Inscrit en
    Mars 2007
    Messages
    1 415
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Mars 2007
    Messages : 1 415
    Par défaut
    C'est vraiment étonnant comme comportement, je me demande ce qu'en dit la norme, mais il nous faudrait un avocat, c'est un peu subtil dans ce cas. C'est très décevant

  5. #5
    Membre Expert
    Avatar de Pyramidev
    Homme Profil pro
    Tech Lead
    Inscrit en
    Avril 2016
    Messages
    1 513
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Tech Lead

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 513
    Par défaut
    Bonjour,

    leternel, ton exemple est équivalent à celui-ci, plus facile à lire :
    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
    // Code compilé ici : http://coliru.stacked-crooked.com/
     
    #include <iostream>
     
    struct A
    {
        virtual void foo() { std::cout << "A::foo()\n"; }
    };
     
    struct B : public A
    {
        void foo() override { std::cout << "B::foo()\n"; }
        virtual void bobo() {}
    };
     
    struct C : public A
    {
        virtual void coco() {}
    };
     
    struct D : public B, public C
    {
        void         bobo() override {}
        void         coco() override {}
        virtual void dodo()          {}
    };
     
    int main()
    {
        std::cout << "Version de g++ : " << __VERSION__ << "\n\n"; // version 6.1.0
        D d;
    //  d.foo();                  // erreur de compilation : foo est ambigu
    //  static_cast<A&>(d).foo(); // erreur de compilation : A est une classe de base ambigüe
        static_cast<B&>(d).foo(); // appelle B::foo()
        static_cast<C&>(d).foo(); // appelle A::foo()
        return 0;
    }
    Dans mon exemple, à mon avis, sous le capot, ça se passe comme ça :
    Dans un objet de type D :
    • L'objet B contient un objet A qui contient un pointeur vers une table virtuelle dont les premières adresses de fonction sont :
      1. adresse de B::foo
      2. adresse de D::bobo
    • L'objet C contient un objet A qui contient un pointeur vers une table virtuelle dont les premières adresses de fonction sont :
      1. adresse de A::foo (et non pas B::foo)
      2. adresse de D::coco
    • Au moins une des deux tables précédentes a pour 3e adresse de fonction D::dodo.


    Dans l'exemple de leternel :
    • simple() a le rôle de foo.
    • BaseSimple a le rôle de A.
    • Simple a le rôle de B.
    • BaseRiche a le rôle de C.
    • Riche a le rôle de D.

  6. #6
    Expert éminent

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 202
    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 202
    Par défaut
    Je vais chercher dans la norme les explications sur les tables de virtuel, le layout des classes et l'héritage multiple.
    L'explication semble bonne.
    D'autant que rendre virtuel l'héritage de la classe de base (BaseSimple ou A) supprime le problème (à un using près)

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

Discussions similaires

  1. Héritage virtuel et constructeur.
    Par Klaim dans le forum C++
    Réponses: 17
    Dernier message: 10/01/2015, 21h18
  2. Interfaces VS héritage
    Par FRED.G dans le forum Général Dotnet
    Réponses: 38
    Dernier message: 17/03/2007, 09h32
  3. [POO] Interface ou héritage ?
    Par s.n.a.f.u dans le forum VB.NET
    Réponses: 3
    Dernier message: 17/03/2007, 01h02
  4. Héritage Virtuel et SDL
    Par Qualimero dans le forum SDL
    Réponses: 6
    Dernier message: 18/07/2006, 04h49
  5. Interface et héritage
    Par pirbd dans le forum Delphi
    Réponses: 2
    Dernier message: 12/07/2006, 13h40

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