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

Langage C++ Discussion :

Héritage par dominance et alternatives à l'héritage en diamant


Sujet :

Langage C++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre averti
    Inscrit en
    Mars 2007
    Messages
    53
    Détails du profil
    Informations forums :
    Inscription : Mars 2007
    Messages : 53
    Par défaut Héritage par dominance et alternatives à l'héritage en diamant
    Bonjour,

    J'ai un code du type ci-dessous (simplifié pour s'y retrouver). Ce code me génère un warning

    warning C4250: 'B1C2' : inherits 'B1::B1::b' via dominance
    warning C4250: 'B1C2' : inherits 'C2::C2::c' via dominance
    Warning que je peux désactiver avec :
    #pragma warning(disable : 4250)
    J'ai en gros deux questions :
    1) Est-il possible d'éviter ce warning en gardant le même esprit d'architecture ?
    2) Sinon, existe-t-il des alternatives à cette architecture diamant qui ne me parait pas très propre. Je pense peut-être à utiliser des templates mais je ne sais pas trop comment m'y prendre ici malgré lecture de tutos/faq. J'ai également consulté le tuto sur les classes de traits et de politiques ici mais mes fonctions de sont pas statiques.

    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
    #include <stdio.h>
    #include <stdlib.h> 
     
    //#pragma warning(disable : 4250)
     
    class A{
    public:
    	A(){};
    	void a(){ b(); c(); };
    	virtual void b() = 0;
    	virtual void c() = 0;
    };
     
    class B1 : public virtual A{
    public:
    	B1():A(){};
    	void b(){ printf("B1"); };
    };
     
    class B2 : public virtual A{
    public:
    	B2() :A(){};
    	void b(){ printf("B2"); };
    };
     
    class C1 : public virtual A{
    public:
    	C1() :A(){};
    	void c(){ printf("C1"); };
    };
     
    class C2 : public virtual A{
    public:
    	C2() :A(){};
    	void c(){ printf("C2"); };
    };
     
    class B1C2 : public B1, public C2{
    public:
    	B1C2() : A(), B1(), C2(){};
    };
     
    void main(int argc, char* argv[])
    {
    	B1C2* b1c2 = new B1C2();
    	b1c2->a();
    	printf("\n");
    	system("pause");
    }
    Merci de votre aide

  2. #2
    Expert confirmé
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2005
    Messages
    5 526
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Conseil

    Informations forums :
    Inscription : Février 2005
    Messages : 5 526
    Par défaut
    1) Est-il possible d'éviter ce warning en gardant le même esprit d'architecture ?
    Heu, donc une architecture foireuse ???
    2) Sinon, existe-t-il des alternatives à cette architecture diamant
    Heureusement, mais il faut plus de détail sur le vrai besoin initial, pour pouvoir choisir une bonne solution.

  3. #3
    Expert confirmé
    Homme Profil pro
    Ingénieur développement matériel électronique
    Inscrit en
    Décembre 2015
    Messages
    1 600
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 62
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Ingénieur développement matériel électronique
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Décembre 2015
    Messages : 1 600
    Par défaut
    Bonjour,

    Le warning nous prévient que B1C2::b() préférera B1C2::B1::b() à B1C2::C2::B::b(), un moyen de le faire disparaitre est de faire 'remonter' la fonction dans B1C2 pour lever toute ambiguïté. Un using B1C2::B1::b; dans la class B1C2 devrait ôter le warning.

  4. #4
    Membre Expert
    Avatar de Pyramidev
    Homme Profil pro
    Tech Lead
    Inscrit en
    Avril 2016
    Messages
    1 515
    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 515
    Par défaut
    Bonjour,

    Citation Envoyé par Bloodista Voir le message
    2) Sinon, existe-t-il des alternatives à cette architecture diamant qui ne me parait pas très propre.
    Solution avec l'héritage et la virtualité, sans l'héritage multiple : patron de conception Stratégie :
    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
    #include <iostream>
     
    class B {
    public:
    	virtual void b() = 0;
    };
     
    class C {
    public:
    	virtual void c() = 0;
    };
     
    class A{
    private:
    	B& m_b;
    	C& m_c;
    public:
    	A(B& x, C& y) : m_b(x), m_c(y) {}
    	void a() { m_b.b(); m_c.c(); }
    };
     
    class B1 : public B {
    public:
    	void b() override { std::cout << "B1"; }
    };
     
    class B2 : public B {
    public:
    	void b() override { std::cout << "B2"; }
    };
     
    class C1 : public C {
    public:
    	void c() override { std::cout << "C1"; }
    };
     
    class C2 : public C {
    public:
    	void c() override { std::cout << "C2"; }
    };
     
    int main()
    {
    	B1 b1;
    	C1 c1;
    	A obj(b1, c1);
    	obj.a();
    	return 0;
    }
    Solution avec les templates : classes de politique :
    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
    #include <iostream>
     
    template<class B, class C>
    class A{
    private:
    	B m_b;
    	C m_c;
    public:
    	A(const B& x = B(), const C& y = C()) : m_b(x), m_c(y) {}
    	void a() { m_b.b(); m_c.c(); }
    };
     
    class B1 {
    public:
    	void b() { std::cout << "B1"; }
    };
     
    class B2 {
    public:
    	void b() { std::cout << "B2"; }
    };
     
    class C1 {
    public:
    	void c() { std::cout << "C1"; }
    };
     
    class C2 {
    public:
    	void c() { std::cout << "C2"; }
    };
     
    int main()
    {
    	A<B1, C1> obj;
    	obj.a();
    	return 0;
    }

  5. #5
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    Salut,

    En dehors du patron de conception stratégie proposé par pyramidev, on peut déjà se poser pas mal de question, entre autre, quant au respect du LSP et de l'ISP dans ton architecture.

    Pour rappel, le principe de substitution de Liskov nous oblige à veiller à ce que la classe de base n'expose que les propriétés qui sont communes à toutes les classes dérivées. Ainsi, si une fonction "truc" n'as de sens que pour une seule des classes dérivées, il n'y a aucune raison pour qu'elle soit exposée dans la classe de base, parce qu'on ne peut pas estimer en toute conscience que [tout élément passant pour un objet du type de base dispose d'une propriété "expose la fonction truc"]

    Dans le cas présent, on pourrait estimer que la seule fonction qui devrait effectivement être exposée par ta classe A (qui est au final la seule vraie classe de base) est ... la fonction a(), et que tu aurais sans doute intérêt à utiliser le NVI, car son comportement a de grandes chances de devoir s'adapter de différentes manières.

    En effet, tu ne définis jamais le comportement des deux autres fonctions en même temps dans les classes dérivées : tu définis le comportement de b() dans B1 et dans B2 et de c() dans C1 et dans C2. Si bien que tes classes B1, B2, C1 et C2 restent malgré tout... des classes abstraites (car il y a toujours une fonction virtuelle pure qui traine "quelque part") .

    Dés lors, tu devrais envisager de sortir tes fonctions b() et c de ta classe A, en les mettant dans des interfaces séparées, sous une forme 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
    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
     
    /* la classe de base
     */
    class A{
    public:
        /* utilise le NVI pour permettre malgré tout au comportement de s'adapter de manière transparente */
        void a(){
            doA();
        }
    private:
        virtual void doA() = 0;
    };
    /* l'interface permettant d'avoir la fonction b
     */
    class BInterface{
    public:
        virtual void b() = 0;
    };
    /* L'interface permettant d'avoir la fonction c
    class CInterface{
    public:
        virtual void c() = 0;
    };
     
    /* On peut désormais créer une classe qui ne dispose que de la fonction b
     */
    class B1: public A, public BInterface{
    public:
        void b() override;
    private:
       void doA() override{
           b();
       }
    };
    /* Mais b peut n'être qu'un "comportement interne", et l'héritage privé fonctonne aussi...
     * !!! on ne pourra pas la transmettre à une fonction proche de bar(BInterface & i);
     * car la composante BInterface est privée 
     */
    class B2 : public A, private BInterface{
    private:
        // héritée de BInterface
        void b() override{
        }
        // héritée de A...
        void doA() override{
            b();
        }
     };
    /* même chose pour les classes qui n'ont besoin que de c() */
    class C1 : public A, public CInterface{
    public:
        void c() override;
    private:
        void doA() override{
            c();
        }
    };
    /* Et, si on veut une classe qui a b() et c(), on hérite des deux interfaces */
    class D : public A, public BInterface, public CInterface{
    public:
        // héritée de BInterface
        void b() override;
        // héritée de CInterface
        void c() override;
    private:
       // héritée de A
       void doA() override{
          b();
         c();
       }
    };
    // D'ailleurs, une autre classe disposant de b() et de c() pourrait resembler à D, 
    //mais en inversant les appels à b() et à c() dans doA ;)
    En plus, ce qu'il y a de bien avec C++, c'est que ce que l'on considère comme une interface n'est jamais qu'une classe tout à fait classique, dont il n'est normalement pas envisageable de créer une instance sans passer par les classes dérivées.

    Mais il n'y a a priori aucune raison pour que cette classe ne dispose pas également des données permettant aux fonction de travailler, ce qui nous permettrait de ne pas devoir les déclarer virtuelles (ou de ne pas devoir les implémenter dans les classes dérivées).

    "Tout ce qu'il y aurait à faire, c'est d'en empêcher l'instanciation, par exemple, en plaçant le constructeur dans l'accessibilité protégée, de manière à ce que seules les instances dérivées puissent y avoir accès
    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

  6. #6
    Membre averti
    Inscrit en
    Mars 2007
    Messages
    53
    Détails du profil
    Informations forums :
    Inscription : Mars 2007
    Messages : 53
    Par défaut
    Merci beaucoup pour toutes vos réponses.

    dalfab, ta solution provoque une erreur de compilation :
    error C2039: 'B1' : is not a member of 'B1C2'
    En effet, bacelar a raison, j'ai trop simplifié mon problème qui est je pense finalement plus compliqué que ça car les fonctions s'appellent mutuellement à travers les différentes classes. Désolé de ne pas avoir mis ça dès le début. Pas toujours simple de faire des versions simplifiées

    Voici une version plus détaillée de mon problème (et de mon architecture actuelle) en espérant que cela soit plus explicite. Si vous avez des idées pour améliorer ça

    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
    #include <stdio.h>
    #include <stdlib.h> 
     
    //#pragma warning(disable : 4250)
     
    class Mere{
    public:
    	Mere(){};
    	void operation1(){/*Do operation1*/; printf("op1"); };
    	void operation2(){/*Do operation2*/; printf("op2"); };
    	void operation3(){/*Do operation3*/; printf("op3"); };
    	virtual void enchainer_operations() = 0;
     
    	virtual void petite_fonction1() = 0;
    	virtual void petite_fonction2() = 0;
    	virtual void petite_fonction3() = 0;
     
    	void grosse_fonction(){
    		petite_fonction1();
    		petite_fonction2();
    		petite_fonction3();
    	};
    };
     
    class Type1 : public virtual Mere{
    public:
    	Type1() :Mere(){};
     
    	void petite_fonction1(){/* du code + */ enchainer_operations(); };
    	void petite_fonction2(){/* du code + */ enchainer_operations(); };
    	void petite_fonction3(){/* du code + */ enchainer_operations(); };
    };
     
    class Type2 : public virtual Mere{
    public:
    	Type2() :Mere(){};
     
    	void petite_fonction1(){/* du code + */ enchainer_operations(); };
    	void petite_fonction2(){/* du code + */ enchainer_operations(); };
    	void petite_fonction3(){/* du code + */ enchainer_operations(); };
     
    };
     
    class Mode1 : public virtual Mere{
    public:
    	Mode1() :Mere(){};
    	void enchainer_operations(){ operation1(); operation2(); };
    };
     
    class Mode2 : public virtual Mere{
    public:
    	Mode2() :Mere(){};
    	void enchainer_operations(){ operation1(); operation2(); operation3(); };
    };
     
    class Fille : public Type1, public Mode2{
    public:
    	Fille() : Mere(), Type1(), Mode2(){};
    };
     
    void main(int argc, char* argv[])
    {
    	Fille* fille = new Fille();
    	fille->grosse_fonction();
    	system("pause");
    }

  7. #7
    Rédacteur/Modérateur


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

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 153
    Billets dans le blog
    4
    Par défaut
    Et tu ne peux pas renommer et revoir ton architecture ?
    Le simple fait que tu appelles ça Type et Mode laissent penser que tu pourrais, voire devrais, et que ce ne sont finalement pas des relations d'héritage qu'elles devraient partager. Mais que l'héritage tombe ici par hasard par simplification de nommage.
    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.

  8. #8
    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
    Visiblement, même, les deux types ne définissent que les "petites_fonctions", et les modes l'enchainement.
    Ca ressemble furieusement à "strategy" et un peu de template.

    Sinon, il semble que les "petites fonctions" appelle aveuglement "enchainer_operations" quoi que cette dernière fasse.
    Cela ressemble à deux niveaux responsabilités distinctes.
    Cela mérite probablement de déléguer l'une des responsabilité à une autre classe, dont un objet serait contenu dans Mère.

    D'un point de vue utilisateur de Mère&, fille1 et fille..., les classes Type et Mode ont elle un intérêt par elle même.
    Voudras-t-il créer une classe en héritant?

  9. #9
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    Tu nous montre du code tellement générique qu'il est très difficile de se faire un idée précise de ta situation conceptuelle.

    Car, lorsque l'on lit class Mere{};, classe Fille: public Mere{};, la seule information dont nous disposions est : tu as décidé de recourir à l'héritage public.

    Or, toute première question que les concepteurs ont tendance à se poser est : est-ce que l'héritage public est cohérent dans le contexte actuel "; la deuxième question étant "est ce que l'héritage public respecte le LSP dans le contexte actuel "

    Et le problème au final, c'est que ce que tu nous montre ne nous permet de répondre à aucune de ces questions .

    Dés lors, je voudrais bien te croire "sur parole" et estimer que, oui, l'héritage public est cohérent dans ce contexte, et que oui, le LSP est pleinement respecté, malheureusement, je n'y arrive absolument pas. Pour une raison toute simple : tu as un problème d'héritage, et donc, c'est très certainement qu'il y en a au moins un qui n'est pas cohérent ou qui ne respecte pas le LSP. Autrement, tu n'aurais pas de problème et tu ne serais pas venu chercher notre aide

    Dés lors, je n'entrevois que deux conseils à te donner pour t'aider, qui partent du prérequis qu'il faudra changer l'architecture de ton code (chose que tu ne souhaites malheureusement pas envisager, de toute évidence):

    1. L'héritage public est la relation la plus forte qui puisse exister entre deux classes, c'est donc la dernière qui doive être envisagées lorsque toutes les autres (agrégation en tête) ont démontré leur inefficacités
    2. L'héritage public ne peut être considéré comme une alternative valable que si le principe de substitution de Liskov est pleinement respecté. Si ce n'est pas le cas, il faut chercher une autre solution.



    Ou bien tu es plus précis dans tes exemples (montre nous à quoi ressemblent réellement tes classes, avec leurs noms réels, qu'on sache à quoi elles servent réellement) et on réfléchira ensemble à la manière d'appliquer ces deux conseil, ou bien tu cogites dessus dans ton coin en vue de les appliquer, et tu viens nous montrer le résultat de tes cogitations plus tard. C'est comme tu veux

    Mais une chose est sure, il va falloir cogiter... Et comme tu nous demande de l'aide (qu'on est au demeurant tout à fait disposés à te fournir), tu devrais sans doute choisir la première solution
    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

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

Discussions similaires

  1. [2008R2] Clause SELECT avec héritage par partition
    Par Jah73 dans le forum Développement
    Réponses: 4
    Dernier message: 29/10/2013, 15h00
  2. Héritage par prototype
    Par Invité dans le forum Général JavaScript
    Réponses: 4
    Dernier message: 11/02/2013, 16h37
  3. Unités de persistance et héritage par jointure
    Par le2tbomi dans le forum Java EE
    Réponses: 0
    Dernier message: 04/10/2010, 15h04
  4. [MPD] alternatives à un héritage
    Par o.deb dans le forum Schéma
    Réponses: 4
    Dernier message: 12/07/2010, 23h08
  5. cherche alternative à l'héritage multiple
    Par X-dark dans le forum Langage
    Réponses: 4
    Dernier message: 22/04/2009, 17h04

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