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 :

Heritage non virtuel.


Sujet :

Langage C++

  1. #1
    Membre du Club
    Profil pro
    Inscrit en
    Novembre 2009
    Messages
    69
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2009
    Messages : 69
    Points : 62
    Points
    62
    Par défaut Heritage non virtuel.
    Bonjour,

    Je vois dans les exemples de la libraire boost.ggl des trucs comme :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    struct my_ring : std::deque<my_point> {};
    Je pensais qu'il y avait des risques ? par exemple si on a dans le code un :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    std::deque<my_point> * truc;
    //...
     truc  = new my_ring();
    //...
    delete truc;
    truc = nullptr;
    Alors il y a bien une fuite mémoire, non?

    Bref, tout ceci signifie-t-il que "tant qu'on essaie pas de faire du polymorphisme, on ne risque rien à faire des destructeur non virtuels" ?

    ElPedro.

  2. #2
    Membre averti Avatar de vikki
    Profil pro
    Inscrit en
    Mai 2007
    Messages
    292
    Détails du profil
    Informations personnelles :
    Âge : 39
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations forums :
    Inscription : Mai 2007
    Messages : 292
    Points : 302
    Points
    302
    Par défaut
    Bonjour,

    Je vois dans les exemples de la libraire boost.ggl des trucs comme :
    Code :


    struct my_ring : std::deque<my_point> {};


    Je pensais qu'il y avait des risques ? par exemple si on a dans le code un :
    Code :


    std::deque<my_point> * truc;
    //...
    truc = new my_ring();
    //...
    delete truc;
    truc = nullptr;


    Alors il y a bien une fuite mémoire, non?

    Bref, tout ceci signifie-t-il que "tant qu'on essaie pas de faire du polymorphisme, on ne risque rien à faire des destructeur non virtuels" ?
    Hello,
    Le destructeur de my_ring ne devrait effectivement pas être appelé dans ce cas (le destructeur de deque (ou de ses classes mères) n'est pas virtuel). Mais si l'on se contente d'ajouter des fonctionnalités dans my_ring et pas de nouveaux membres, cela n'a pas d'importance (je crois).
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    struct my_ring : std::deque<my_point> {};
    C'est vraiment ca que tu as vu? Dans ce cas, un simple typedef me semble équivalent.

  3. #3
    Membre du Club
    Profil pro
    Inscrit en
    Novembre 2009
    Messages
    69
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2009
    Messages : 69
    Points : 62
    Points
    62
    Par défaut
    >>>Dans ce cas, un simple typedef me semble équivalent.
    1/ la remarque est interessante 2/ cependant je ne suis pas du tout d'accord :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    typedef std::string IdVoiture;
    typedef std::string IdAvion;
     
    void destroy(IdVoiture v);
    void destroy(IdAvion v);
    //==> PROBLEME : ambiguite
    tandis qu'avec l'héritage il n'y a aucun soucis!
    (C'est d'ailleurs l'une des raisons initiales de ce post (copie de type)).

    Par ailleurs, il n'y a pas de raison pour laquelle la classe heritee fasse la meme taille que la classe initiale (même si les compilos actuels font attention à cela souvent). Ceci implique donc une fuite mémoire de toute façon si on fait un "delete classeBase".

    Bref je réitère ma question :
    tout ceci signifie-t-il que "tant qu'on essaie pas de faire du polymorphisme, on ne risque rien à faire des destructeur non virtuels" ?

  4. #4
    Membre averti Avatar de vikki
    Profil pro
    Inscrit en
    Mai 2007
    Messages
    292
    Détails du profil
    Informations personnelles :
    Âge : 39
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations forums :
    Inscription : Mai 2007
    Messages : 292
    Points : 302
    Points
    302
    Par défaut
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    typedef std::string IdVoiture;
    typedef std::string IdAvion;
     
    void destroy(IdVoiture v);
    void destroy(IdAvion v);
    //==> PROBLEME : ambiguite
    Effectivement, le typedef ne permet pas la surcharge de fonction. Mais comme il ne s'agit pas d'un héritage à but polymorphique, l'intérêt est moindre (même si ca peut servir c'est vrai).

    Par ailleurs, il n'y a pas de raison pour laquelle la classe heritee fasse la meme taille que la classe initiale (même si les compilos actuels font attention à cela souvent). Ceci implique donc une fuite mémoire de toute façon si on fait un "delete classeBase".
    Il me semble au contraire que dans ce cas, un objet de la classe fille fera la même taille qu'un objet de la classe mère (un sizeof le confirme).

  5. #5
    Membre du Club
    Profil pro
    Inscrit en
    Novembre 2009
    Messages
    69
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2009
    Messages : 69
    Points : 62
    Points
    62
    Par défaut
    le sizeof c'est celui de ton compilo .
    bref, sur Wii, PS3 ou AS400 il n y'a aucune raison pour que tu obtiennes le même résultat. Même entre visual 6 et visual 9 tu as des différences à ce niveau là.

    L'utilite de l'heritage est :
    1/ pour faire écrire des templates
    2/ levee d'ambiguitée. Par exemple, dans le cas de string si tu manipule des IdVoiture incompatibles avec des IdAvions tu ne risques pas de les confondre : le compilateur te le dira. Et plus tard tu peux même changer le type pour une version plus complète sans difficultés.

    Cependant je me pose la question : est-ce sans risque?

  6. #6
    Membre averti Avatar de vikki
    Profil pro
    Inscrit en
    Mai 2007
    Messages
    292
    Détails du profil
    Informations personnelles :
    Âge : 39
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations forums :
    Inscription : Mai 2007
    Messages : 292
    Points : 302
    Points
    302
    Par défaut
    le sizeof c'est celui de ton compilo .
    bref, sur Wii, PS3 ou AS400 il n y'a aucune raison pour que tu obtiennes le même résultat. Même entre visual 6 et visual 9 tu as des différences à ce niveau là.
    Je ne te suis pas. Tu compile ton code avec 1 compilateur à la fois (en tout cas ca me parait logique), et un sizeof(ma_structure) renverra la même valeur dans tout ton programme. Si tu hérite d'une classe sans ajouter de nouveaux membre on devrait avoir (me semble) sizeof(mère)==sizeof(fille), quelque soit le compilateur.

    L'utilite de l'heritage est :
    1/ pour faire écrire des templates
    2/ levee d'ambiguitée. Par exemple, dans le cas de string si tu manipule des IdVoiture incompatibles avec des IdAvions tu ne risques pas de les confondre : le compilateur te le dira. Et plus tard tu peux même changer le type pour une version plus complète sans difficultés.
    Là je te suis plus du tout

  7. #7
    Membre du Club
    Profil pro
    Inscrit en
    Novembre 2009
    Messages
    69
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2009
    Messages : 69
    Points : 62
    Points
    62
    Par défaut
    >>>Tu compile ton code avec 1 compilateur à la fois
    Oui (bon sauf pour certaines dll...), mais je voulais dire que ce n'est pas une technique portable ni garantie. Par exemple, même si un héritage de std::string fait la meme taille que la classe de base, et bien :
    struct S{};
    struct C : S{};
    il est tout a fait possible que sizeof(C) != sizeof(S) pour le même compilo. Bref faut faire un assert pour chaque type.

    >>> là je te suis plus du tout.
    ARf effectivement je suis pas clair.
    Bon imagine un code qui utilise typedef de std::string. Puis un code qui utilise un heritage std::string.

    Si jamais dans le code a un moment j'essaie d'assigner un IdVoiture à un IdAvion, et bien cela ne compilera pas si j'ai fait un heritage. Par contre cela compilera si j'ai utilisé un template.
    Bref avec le template je n'ai pas détecté le comportement qui est faux.

    Yo!
    Cependant je me pose la question : est-ce sans risque d'hériter ainsi ?

  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
    Salut,

    Il n'y a, vraiment, que si tu essaye d'appeler le destructeur de la classe de base en espérant détruire un objet dont le type est la classe dérivée que tu risque d'avoir un problème.

    Autrement dit, il n'y a que dans le cas d'un
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    Base * d=new Derivee();
    /*...*/
    delete d;
    que, selon les compilateurs (ceux pour lesquels sizeof(Base) serait différent de sizeof(Derivee) ) tu pourrait effectivement remarquer une différence, car tu demande la libération de la mémoire allouée à une base, alors qu'il faudrait libérer la mémoire de la dérviée.

    Dans tous les autres cas, tu n'auras pas de problème: après tout, il est possible d'avoir un destructeur non virtuel et protégé dans une classe de base

    Ainsi:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
     
    void foo()
    {
        Derivee * ptr=new Derivee;
        Derivee obj;
        /*...*/
        delete ptr;
    } // appel implicite de ~Derivee sur obj;
    est tout à fait garanti.

    Ceci dit, es tu sur que my_ring est déclaré en tant que structure, et non en tant que classe

    Si je pose cette question, c'est parce que la visibilité par défaut de l'héritage est différente pour les classes et les structures:
    • public par défaut pour les structures (struct)
    • privé par défaut pour les classes (class)
    Ce qui signifie que, dans le cas des structures, tu aurais effectivement une relation "est un" entre la classe de base et la classe dérivée (il est possible de faire passer un pointeur ou une référence vers la classe dérivée pour un pointeur ou une référence vers la classe de base), alors que, dans le cas des classe, la relation est plutôt de l'ordre du "est implémenté en terme de", et remplace sur certain points la composition.

    Cependant, dans le cas d'une classe, il devrait alors sans doute y avoir au minimum quelques fonctions publiques, voir quelques directives using en accessibilité publique, car une classe dans laquelle tout serait en visibilité privée n'aurait aucun sens.

    Et encore, tu ne donnes pas assez de précisions pour nous permettre de nous faire une idée bien précise du problème car, s'il s'agit d'une structure imbriquée et qu'elle est déclarée dans une visibilité privée ou protégée (ou s'il s'agit d'une structure déclarée dans un espace de noms anonyme), il s'agira d'une structure qui ne serait utilisable que dans la classe dans laquelle elle est imbriquée ou dans le fichier dans lequel elle est définie.
    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
    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,
    C'est bizarre ce que tu dis.
    La destruction d'un objet de façon polymorphe sans que le destructeur ne soit virtuel est un comportement indéfini au delà de tout problème mémoire.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
     
    struct A
    {
      ~A(){}
    };
    struct B : public A
    {
       ~B(){}
    };
    int main()
    {
       A*pa = new B;
       delete pa; // comportement indéfini !
    }
    C'est pour ça qu'en général :
    1/ On n'hérite pas publiquement des classes de base dont le destructeur est public et non virtuel,
    2/ On utilise éventuellement un héritage privé dans ce cas,
    3/ Si une classe doit servir à l'héritage : soit son destructeur est virtuel et public soit sont destructeur est protégé et non virtuel. Cf F.A.Q. : Pourquoi le destructeur d'une classe de base doit être public et virtuel ou protégé et non virtuel ?

  10. #10
    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
    Est-ce à moi que tu dis que je dis quelque chose de bizard

    J'avais effectivement fait une petite faute dans le code, mais, justement, ce que je dis, c'est que tu aura un problème en cas de comportement polymoprhe uniquement...

    Si tu n'a aucun comportement polymorphe au moment de la destruction, tu n'aura aucun problème
    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

  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
    Citation Envoyé par koala01 Voir le message
    Est-ce à moi que tu dis que je dis quelque chose de bizard
    Non. Effectivement, j'ai été un peu succinct. Je trouve bizarre l'exemple de Pedro :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    struct my_ring : std::deque<my_point> {};
    .
    Ca me parait bizarre d'avoir un tel code dans la doc de Boost car c'est très dangereux comme façon de faire.

  12. #12
    screetch
    Invité(e)
    Par défaut
    ce code existe mais pas tel quel

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    template< typename T >
    struct ring : std::deque<T> {};
    (un gros raccourci)
    c'est un typedef template, ce qui n'est malheureusement pas permis par le standard C++ (a l'heure actuelle)

    et sinon je suis d'accord, c'est dangereux et je trouve cela assez indigne de boost.
    il y a une pirouette pour s'en sortir qui consiste a mettre le typedef dans un template
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    template< typename T >
    struct types
    { typedef ring std::deque<T>;
    };
     
    types<point>::ring r;

  13. #13
    Alp
    Alp est déconnecté
    Expert éminent sénior

    Avatar de Alp
    Homme Profil pro
    Inscrit en
    Juin 2005
    Messages
    8 575
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations forums :
    Inscription : Juin 2005
    Messages : 8 575
    Points : 11 860
    Points
    11 860
    Par défaut
    Citation Envoyé par screetch Voir le message
    il y a une pirouette pour s'en sortir qui consiste a mettre le typedef dans un template
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    template< typename T >
    struct types
    { typedef ring std::deque<T>;
    };
     
    types<point>::ring r;
    Template Rebinding.
    C'est utilisé entre autres dans la STL aussi, avec les allocateurs.

  14. #14
    Membre chevronné
    Avatar de Goten
    Profil pro
    Inscrit en
    Juillet 2008
    Messages
    1 580
    Détails du profil
    Informations personnelles :
    Âge : 33
    Localisation : France

    Informations forums :
    Inscription : Juillet 2008
    Messages : 1 580
    Points : 2 205
    Points
    2 205
    Par défaut
    Citation Envoyé par screetch Voir le message
    ce code existe mais pas tel quel

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    template< typename T >
    struct ring : std::deque<T> {};
    (un gros raccourci)
    c'est un typedef template, ce qui n'est malheureusement pas permis par le standard C++ (a l'heure actuelle)

    et sinon je suis d'accord, c'est dangereux et je trouve cela assez indigne de boost.
    il y a une pirouette pour s'en sortir qui consiste a mettre le typedef dans un template
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    template< typename T >
    struct types
    { typedef ring std::deque<T>;
    };
     
    types<point>::ring r;
    Il faudrait voir la source entiére. Mais amha c'est pas du template rebinding là qu'ils ont fait, c'est autre chose.
    "Hardcoded types are to generic code what magic constants are to regular code." --A. Alexandrescu

  15. #15

  16. #16
    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
    Effectivement, c'est bizarre qu'ils proposent ça. Ou quelque chose m'échappe ou c'est pas très propre. J'ai l'impression qu'ils considèrent que personne ne va regarder la cuisine interne et n'oser faire une :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    std::vector<P> *p_vect = new linear_ring<P>();

  17. #17
    Alp
    Alp est déconnecté
    Expert éminent sénior

    Avatar de Alp
    Homme Profil pro
    Inscrit en
    Juin 2005
    Messages
    8 575
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations forums :
    Inscription : Juin 2005
    Messages : 8 575
    Points : 11 860
    Points
    11 860
    Par défaut
    Mais en l'occurence il n'y a rien à détruire dans linear_ring qui n'est pas dans vector, si ?

  18. #18
    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
    Oui mais c'est un comportement indéterminé que d'appeler un destructeur non virtuel sur une classe de base

  19. #19
    Alp
    Alp est déconnecté
    Expert éminent sénior

    Avatar de Alp
    Homme Profil pro
    Inscrit en
    Juin 2005
    Messages
    8 575
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations forums :
    Inscription : Juin 2005
    Messages : 8 575
    Points : 11 860
    Points
    11 860
    Par défaut
    Citation Envoyé par 3DArchi Voir le message
    Oui mais c'est un comportement indéterminé que d'appeler un destructeur non virtuel sur une classe de base
    Oui oui biensûr, mais la casse est limitée (bizarre de voir ça dans une lib qui est en train d'être reviewée pour intégration dans Boost, toutefois) vu que la plupart des compilos appellent juste le destructeur de la classe de base point barre.

  20. #20
    Membre chevronné
    Avatar de Goten
    Profil pro
    Inscrit en
    Juillet 2008
    Messages
    1 580
    Détails du profil
    Informations personnelles :
    Âge : 33
    Localisation : France

    Informations forums :
    Inscription : Juillet 2008
    Messages : 1 580
    Points : 2 205
    Points
    2 205
    Par défaut
    C'est étonnant en effet.. Si j'ai le temps je posterais sur la ML de boost pour voir ce qu'ils ont voulu faire.
    "Hardcoded types are to generic code what magic constants are to regular code." --A. Alexandrescu

Discussions similaires

  1. Double héritage (virtuel et non virtuel)
    Par oodini dans le forum Langage
    Réponses: 9
    Dernier message: 09/11/2012, 14h15
  2. Heritage, Fonction virtuelle et cast
    Par NoIdea dans le forum C++
    Réponses: 13
    Dernier message: 11/04/2010, 12h24
  3. heritage multiple virtuel et warning 4250
    Par babar63 dans le forum C++
    Réponses: 3
    Dernier message: 10/03/2009, 18h49
  4. Creation Hyperlink sur un dossier non virtuel du serveur
    Par predalpha dans le forum ASP.NET
    Réponses: 2
    Dernier message: 13/05/2008, 15h55
  5. Réponses: 7
    Dernier message: 20/12/2007, 15h13

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