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 multiple en C++ : pourquoi l'héritage d'interfaces est insuffisant [Tutoriel]


Sujet :

C++

  1. #21
    gl
    gl est déconnecté
    Rédacteur

    Homme Profil pro
    Inscrit en
    Juin 2002
    Messages
    2 165
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 46
    Localisation : France, Isère (Rhône Alpes)

    Informations forums :
    Inscription : Juin 2002
    Messages : 2 165
    Points : 4 637
    Points
    4 637
    Par défaut
    Citation Envoyé par Drannor Voir le message
    Connaître comment une fonctionnalité est implémentée permet de comprendre l'impact sur les performances (exécution & mémoire) que cela peu avoir surtout quand on fait du temps réel embarqué comme c'est mon cas.
    Je conçois très bien que cela soit intéressant. Mais je ne pense pas que cela doive faire partie d'un document sur le C++ comme celui dont il est question ici.

    Dans tout les cas je ne vois pas comment il est possible de décrire l'implémentation et l'impact de cette fonctionnalité hors de tout contexte. Dans le cadre d'une implémentation particulière, oui. Dans le cadre d'un exemple de ce qui pourrait être fait, oui. Mais dans un cadre générique, non.

  2. #22
    Nouveau membre du Club
    Profil pro
    Inscrit en
    Novembre 2010
    Messages
    13
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2010
    Messages : 13
    Points : 35
    Points
    35
    Par défaut
    En c#, certaines extensions récentes au langage disponibles avec Visual studio permettent la vérification statique.

    voir ce lien , pour un bout de code très proche de celui de l'article.
    http://devjourney.com/blog/code-cont...nd-interfaces/

    la solution que je préfère est celle de D, que je trouve très élégante
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    interface I
    {
        int foo(int i)
        in { assert(i > 7); }
        out (result) { assert(result & 1); }
     
        void bar();
    }
    Pour la programmation par policy, Alexandrescu lui même a fait une belle réponse sur le forum D:
    En substance, il faut distinguer le but du moyen, ie faire varier facilement l'implémentation en la paramétrant, du moyen en C++ , l’héritage multiple, qui permet d'injecter du code. Il suggère d'autres approches plus naturelle en D.
    http://www.digitalmars.com/d/archive..._D_130402.html

    L'héritage multiple est très puissant, mais j'essaie de minimiser son utilisation . L’expérience du c# m'a apporté au contraire une discipline supplémentaire.

    Pour la duplication de code dans ce dernier. C'est vrai c'est inévitable dans certains cas. Même si c'est les méthodes d'extension associées aux interfaces(mixin) permettent de limiter ce travers du langage. Cette approche est décriée, mais elle permet d'arriver à la même injection de code que l'héritage multiple; Permettre de d'écrire du code statique dans l'interface, aurait donner un surcroit d'élégance à cette technique.

  3. #23
    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 : 50
    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 j-jorge Voir le message
    Dans l'exemple 3-A (où il manque la déclaration d'héritage au passage)
    Citation Envoyé par Drannor Voir le message
    Il y a une erreur dans l'exemple : 3-A.
    Citation Envoyé par screetch Voir le message
    surtout que le dynamic_cast ne compile pas sur cet exemple, puisque pour utiliser le dynamic_cast il faut une table de méthode virtuelle (c'est la qu'on stocke l'info RTTI)
    Merci à vous, je viens de faire une mise à jour pour corriger ça
    Citation Envoyé par j-jorge Voir le message
    De plus, comme la qualité de l'héritage de A est définit au niveau de B et C, j'ai l'impression qu'aucun type d'héritage n'est satisfaisant. Par exemple, à supposer que j'écrive une bibliothèque où A, B et C sont définies sans héritage virtuel et qu'un utilisateur de ma bibliothèque définisse un D héritant de B et C. Il se retrouve dans l'impossibilité de choisir un héritage en losange. Du coup, lors d'un appel à une fonction de la bibliothèque prenant un A, il devra choisir s'il s'agit du A de B ou du A de C.

    Alors quel comportement adopter en pratique pour ne pas bloquer les futurs développeurs ni les évolutions du logiciel ? Faut-il tout mettre en héritage virtuel ?
    C'est en effet un problème. Mais je ne l'ai pas encore rencontré en pratique (peut-être parce qu'on a moins l'occasion d'utiliser des interfaces que dans d'autres langages, grâce au duck typing ?). Et comme l'héritage virtuel apporte aussi ses problèmes, je ne l'utilise pas de manière préventive partout.

  4. #24
    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 : 50
    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 PhilIvey Voir le message
    En c#, certaines extensions récentes au langage disponibles avec Visual studio permettent la vérification statique.

    voir ce lien , pour un bout de code très proche de celui de l'article.
    http://devjourney.com/blog/code-cont...nd-interfaces/
    C'est en effet à ce mécanisme que je faisais allusion à la fin de mon 2.A.
    Il y a quelques aspects sympas dans ce mécanisme (la vérification statique, qui ne marche bien évidemment que pour ce qui est vérifiable statiquement, je ne sais pas trop ce que ça donne en pratique) et d'autres aspects que j'aime moins (les possibilités de dérivation qui violent le LSP, le fait de devoir passer par une classe dédiée pour définir le contrat d'une interface, les contraintes sur la compilation...).

    Et surtout, il a fallu attendre une extension du langage pour pouvoir avoir ça. Je n'ai pas encore d'opinions claire sur le fait que la programmation par contrat devrait ou pas être directement et explicitement supportée par un langage de programmation. Il y a des arguments dans les deux sens. Par contre, j'apprécie qu'un langage où elle ne l'est pas soit assez souple pour permettre de la mettre en place quand même, à partir des fonctionnalités de base du langage.

    Citation Envoyé par PhilIvey Voir le message
    la solution que je préfère est celle de D, que je trouve très élégante
    Je ne connais pas assez D pour commenter. Regarder D plus en détails fait partie de ma TODO list quand les journées feront 36h

  5. #25
    screetch
    Invité(e)
    Par défaut
    Une petite précision pour le dynamic_cast: il n'est pas forcément nécessaire de le faire intervenir ici, puisqu'un static_cast va effectuer la même opération (un décalage de pointeur)
    la différence entre static_cast et dynamic_cast dans ce cas, c'est la vérification de type (au cas ou on a une table de méthode virtuelle)
    static_cast peut convertir les pointeurs aussi (pour peu qu'ils soient liés entre eux) mais il va se contenter de faire ce qu'on lui dit sans vérifier si le type dynamique de l'objet est bon ou pas.
    un cast C est (très) pervers; si le compilateur connait les types, un c-cast va marcher. Si les types ne sont pas connus a la compilation lors du cast (ce qui peut arriver si on predeclare les classes) alors il va effectuer une mavaise operation (sans le decalage de pointeur)

  6. #26
    Membre éclairé

    Profil pro
    Inscrit en
    Mai 2005
    Messages
    264
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2005
    Messages : 264
    Points : 725
    Points
    725
    Par défaut
    Citation Envoyé par PhilIvey Voir le message
    En c#, certaines extensions récentes au langage disponibles avec Visual studio permettent la vérification statique.

    voir ce lien , pour un bout de code très proche de celui de l'article.
    http://devjourney.com/blog/code-cont...nd-interfaces/

    la solution que je préfère est celle de D, que je trouve très élégante
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    interface I
    {
        int foo(int i)
        in { assert(i > 7); }
        out (result) { assert(result & 1); }
     
        void bar();
    }
    Pour la programmation par policy, Alexandrescu lui même a fait une belle réponse sur le forum D:
    En substance, il faut distinguer le but du moyen, ie faire varier facilement l'implémentation en la paramétrant, du moyen en C++ , l’héritage multiple, qui permet d'injecter du code. Il suggère d'autres approches plus naturelle en D.
    http://www.digitalmars.com/d/archive..._D_130402.html

    L'héritage multiple est très puissant, mais j'essaie de minimiser son utilisation . L’expérience du c# m'a apporté au contraire une discipline supplémentaire.

    Pour la duplication de code dans ce dernier. C'est vrai c'est inévitable dans certains cas. Même si c'est les méthodes d'extension associées aux interfaces(mixin) permettent de limiter ce travers du langage. Cette approche est décriée, mais elle permet d'arriver à la même injection de code que l'héritage multiple; Permettre de d'écrire du code statique dans l'interface, aurait donner un surcroit d'élégance à cette technique.
    J'ai noté aussi pour ma part, depuis que je me suis mis à D, une nette diminution de l'utilisation de l'héritage (par rapport à C++). On ne dérive que lorsqu'on a vraiment besoin de créer un sous-type. Dès lors, l'absence d'héritage multiple dans le langage ne se fait pas vraiment sentir.

  7. #27
    Expert éminent sénior
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2005
    Messages
    5 212
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : France, Val de Marne (Île de France)

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

    Informations forums :
    Inscription : Février 2005
    Messages : 5 212
    Points : 12 384
    Points
    12 384
    Par défaut
    Je réagis avec retard.

    Je suis un ancien programmeur C, Pascal, C++, JAVA et maintenant plutôt C#.

    Je ne suis absolument pas convaincu par les exemples montrant les limitations de l'héritage multiple d'interfaces.

    La programmation par contrat a été ajoutée au Framework4 de .NET donnant beaucoup moins de travail au développeur lamda.
    Et même bien avant ce Framework4, et pour des utilisations plus complexe et plus paramétrables, l'utilisation des mécanismes d'interception, ou encore mieux de "Policy Injection", fournis par un IoC container type Unity ou Spring.Net pour .NET et Spring pour JAVA permet d'avoir des choses bien plus puissante, plus concise, plus paramétrable et plus élégante que le pattern NVI.

    Pour l'héritage d'implémentation, de nouveau un IoC container permet de mettre en place une composition d'objet par configuration ou par code d'initialisation très simplement rendant cet héritage surfait.

    Pour la mise en place de Politique, là aussi un IoC container permette de faire pareil et c'est configurable par modification de fichier de configuration (cool pour le mocking donc la testabilité )

    Un langage ne se réduit pas à sa grammaire met il faut prendre en compte tout un écosystème que ce langage draine et que sa grammaire, ses principes, son architecture permet de fabriquer.

    Merci pour ce document objectif sur les problèmes de l'héritage multiple, je pourrait m'en servir comme référence pour indiquer pourquoi l'héritage multiple est dangereux sans qu'il y est des suspicions de querelle de chapelle.

  8. #28
    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 : 50
    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 bacelar Voir le message
    La programmation par contrat a été ajoutée au Framework4 de .NET donnant beaucoup moins de travail au développeur lamda.
    Au final, la fonctionnalité est enfin là, mais elle a du passer par une modification du compilateur pour y parvenir. Je suis assez circonspect par les fonctionnalités offertes pour qu'un programme modifie son propre compilateur. J'ai peur que ça puisse devenir très vite peu maîtrisable. Mais je manque d'expérience sur le sujet.
    Citation Envoyé par bacelar Voir le message
    Et même bien avant ce Framework4, et pour des utilisations plus complexe et plus paramétrables, l'utilisation des mécanismes d'interception, ou encore mieux de "Policy Injection", fournis par un IoC container type Unity ou Spring.Net pour .NET et Spring pour JAVA permet d'avoir des choses bien plus puissante, plus concise, plus paramétrable et plus élégante que le pattern NVI.
    Est-ce que tu pourrais détailler ces mécanismes ? Je pensais avoir compris ce qu'était les mécanismes de type IoC, mais j'avoue que je vois mal en quoi ça peut servir ici. Les mécanismes d'interception, je ne vois pas trop à quoi ça fait allusion. Est-ce que ça concerne les modifications du compilateur que j'évoquais ?
    Citation Envoyé par bacelar Voir le message
    Pour l'héritage d'implémentation, de nouveau un IoC container permet de mettre en place une composition d'objet par configuration ou par code d'initialisation très simplement rendant cet héritage surfait.

    Pour la mise en place de Politique, là aussi un IoC container permette de faire pareil et c'est configurable par modification de fichier de configuration (cool pour le mocking donc la testabilité )
    Idem.
    Citation Envoyé par bacelar Voir le message
    Merci pour ce document objectif sur les problèmes de l'héritage multiple, je pourrait m'en servir comme référence pour indiquer pourquoi l'héritage multiple est dangereux sans qu'il y est des suspicions de querelle de chapelle.
    C'est bas comme coup !

    Il te reste néanmoins à montrer que les mécanismes que tu proposes à la place n'incluent pas encore plus de problèmes que l'héritage multiple...

  9. #29
    Membre Expert

    Homme Profil pro
    Ingénieur R&D
    Inscrit en
    Juin 2003
    Messages
    4 506
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Ingénieur R&D
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2003
    Messages : 4 506
    Points : 5 723
    Points
    5 723
    Par défaut
    Citation Envoyé par JolyLoic Voir le message
    En effet, je ne pense pas qu'UML permette d'exprimer cette notion, et mes diagrammes ne sont donc probablement pas des diagrammes UML standards. Encore un exemple qui démontre que malgré ses désirs d'universalité, UML a été pensé avec certains types de langages en tête, et ne permet pas vraiment de représenter toutes les notions de tous les langages.
    Rien ne t'empêche de stéréotyper les héritages de <virtual>.

    A partir du moment où il s'agit d'un diagramme de conception/implémentation, et que donc le langage de programmation à ce stade est connu, il n'y a aucune ambiguïté à mon sens de le faire et exprime clairement la notion de non duplication (si j'ai bien compris) que tu souhaites mettre en avant.


    L'article est très intéressant sur plusieurs aspects bien que n'étant pas un pratiquant de ce langage et un adepte de l'héritage multiple en programmation à contrario de l'héritage multiple en conceptualisation. Ce que je veux dire c'est qu'en conceptualisation, j'aurais tendance à trouver suffisant ton premier diagramme en 2B (beaucoup plus clair)et je ne ferais pas le second diagramme du 2B par contre il correspondrait à une implémentation. Dans cet exemple, je reste aussi assez septique et sur ma faim sur ta conclusion sur la souplesse bien que je reconnais que le design est plus complexe.

    Il me semble aussi que dans ton second diagramme en 2B, il manque un lien pour montrer que DashBoardElementImplementation <implémente> IDashBoardElement.
    Images attachées Images attachées  

  10. #30
    Expert éminent
    Avatar de Pyramidev
    Homme Profil pro
    Tech Lead
    Inscrit en
    Avril 2016
    Messages
    1 492
    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 492
    Points : 6 202
    Points
    6 202
    Par défaut
    Up.

    Merci pour ce bon article.

    J'approuve l'argument du NVI pour pouvoir tester des préconditions, des postconditions et écrire dans un fichier de log.
    J'approuve aussi l'exemple avec l'IHM.

    Par contre, je ne suis pas d'accord sur la pertinence de l'héritage multiple dans l'exemple avec les classes de politiques, où Tree dérive publiquement de ses classes de politique (Comparator, AllocationPolicy et BalancingPolicy).
    Le problème, c'est que tout ce qui se trouve en visibilité publique dans les classes de politique vient polluer la partie publique de l'interface de Tree.
    Pour offrir à l'utilisateur de la flexibilité à l'exécution, la solution qui me semble la plus propre serait que la classe Tree ait un constructeur qui prenne en paramètre ses classes de politique par référence constante :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    explicit Tree(const Comparator& comp = Comparator(),
                  const AllocationPolicy& alloc = AllocationPolicy(),
                  const BalancingPolicy& balan = BalancingPolicy());
    D'ailleurs, c'est ce que fait std::set :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    explicit set(const key_compare& comp = key_compare(),
                 const allocator_type& alloc = allocator_type());
    Par exemple, admettons que je veuille avoir un ensemble ordonné d'entiers dont l'ordre, croissant ou décroissant, sera déterminé non pas à la compilation mais à l'exécution.
    Avec std::set, je peux faire 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
    template<class T>
    class OrdreCroissantOuDecroissant {
    private:
        bool m_croissant;
    public:
        explicit OrdreCroissantOuDecroissant(bool croissant) : m_croissant(croissant) {}
        bool operator() (const T& x, const T& y) const {
            return m_croissant && x < y || !m_croissant && y < x;
        }
    };
     
    int _tmain(int argc, _TCHAR* argv[])
    {
        const bool croissant = rand() % 2 == 0;
        const OrdreCroissantOuDecroissant<int> comp(croissant);
        std::set<int, OrdreCroissantOuDecroissant<int>> entiers(comp);
        entiers.insert(1);
        entiers.insert(2);
        entiers.insert(3);
        for(auto it = entiers.begin(); it != entiers.end(); ++it)
            std::cout << *it;
        return 0;
    }
    Pour revenir à Tree, si on veut vraiment autoriser l'utilisateur à modifier l'allocateur alors que l'objet Tree est déjà construit, à ses risques et périls, on peut toujours lui fournir des fonctions :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    AllocationPolicy getAllocationPolicy() const;
    void setAllocationPolicy(const AllocationPolicy& alloc);

Discussions similaires

  1. Réponses: 4
    Dernier message: 06/06/2008, 00h20
  2. [heritage][conception]héritage multiple en java!
    Par soulhouf dans le forum Langage
    Réponses: 9
    Dernier message: 25/08/2005, 21h03
  3. L'héritage multiple est-il possible en Delphi ?
    Par SchpatziBreizh dans le forum Langage
    Réponses: 8
    Dernier message: 30/06/2005, 12h30
  4. utilisez vous l'héritage multiple ?
    Par vodosiossbaas dans le forum C++
    Réponses: 8
    Dernier message: 13/06/2005, 21h25
  5. [XML Schemas]héritage multiple
    Par nicolas_jf dans le forum XML/XSL et SOAP
    Réponses: 2
    Dernier message: 10/06/2003, 13h55

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