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 :

static_pointer_cast et dynamic_pointer_cast


Sujet :

Langage C++

  1. #1
    Membre averti Avatar de CaptainKrabs
    Homme Profil pro
    Étudiant
    Inscrit en
    Juin 2018
    Messages
    25
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juin 2018
    Messages : 25
    Par défaut static_pointer_cast et dynamic_pointer_cast
    Bonjour,

    je cherche à comprendre les static_pointer_cast et les dynamic_pointer cast. On m'a indiqué que ces templates permettent de faire des cast de shared_ptr.
    Cependant en relisant l'extrait de code suivant, j'avoue avoir du mal à comprendre :

    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>
    #include <memory>
     
    using namespace std;
     
    class Polygone{
    public:
        virtual void display(){cout << "Polygone" << endl;}
     
    };
     
    class Triangle : public Polygone{
    public:
        virtual void display(){cout << "Triangle display" << endl;}
        virtual void display2(){cout << "Triangle display2" << endl;}
     
    };
     
    int main(int argc, char* argv[]){
        shared_ptr<Polygone> l_sharedPtrPoly(new Polygone);
        l_sharedPtrPoly->display();
     
        shared_ptr<Triangle> l_sharedPtrTriSt = static_pointer_cast<Triangle>(l_sharedPtrPoly);
        l_sharedPtrTriSt->display();
     
        // Downcast d'un objet polymorphique avec dynamic_pointer_cast
        shared_ptr<Triangle> l_sharedPtrTriDyn = dynamic_pointer_cast<Triangle>(l_sharedPtrPoly);
        if(l_sharedPtrTriDyn){
            l_sharedPtrTriDyn->display();
            l_sharedPtrTriDyn->display2();
        }else{
            cout << "Pointeur nul" << endl;
        }
     
        return 0;
    }
    J'obtiens alors en sortie :
    Polygone
    Polygone
    Pointeur nul

    Voici ce qui me questionne :
    1) Je m'attendrais à obtenir en sortie Triangle sur la 2ème ligne. Là, je ne comprends pas l'intérêt du cast.
    2) Je ne comprends pas la 3eme ligne. C'est comme si le cast n'avait pas fonctionné et le pointeur est non initialisé. Est-ce que cela est dû qu'ici mes objets ne sont pas dynamiques?

    Merci par avance de vos réponses.

  2. #2
    Membre Expert
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2011
    Messages
    760
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hérault (Languedoc Roussillon)

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

    Informations forums :
    Inscription : Juin 2011
    Messages : 760
    Par défaut
    Les xxx_pointer_cast suivent les mêmes règles que les xxx_cast et il semble y avoir un problème de compréhension sur les casts en général. Un cast d'une référence (pointeur comprit) ne change pas le type réel de la donnée, il indique que maintenant le type doit être traité comme un autre type, avec tout les conséquences que cela implique en cas d'erreur.

    Comme le type construit n'est pas un Triangle, il n'y a pas spécialement de raison que Triangle s'affiche, la vtable est toujours celle de Polygone. Mais bien pire que cela, ce code à un comportement indéfini puisque downcaster un pointeur vers un type qu'il n'est pas est UB.

    Quand a dynamic_cast, qui fait une vérification du type demandé, il donne à raison un pointeur nul: l_sharedPtrPoly ne contient pas un Triangle.

    Ce genre de fonction est vraiment à prendre avec des pincettes. Et mon avis que dynamic_cast met surtout en évidence une erreur de conception.

  3. #3
    Expert confirmé
    Avatar de fred1599
    Homme Profil pro
    Lead Dev Python
    Inscrit en
    Juillet 2006
    Messages
    4 059
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Meurthe et Moselle (Lorraine)

    Informations professionnelles :
    Activité : Lead Dev Python
    Secteur : Arts - Culture

    Informations forums :
    Inscription : Juillet 2006
    Messages : 4 059
    Par défaut
    Hello,

    Soyons clairs : le C++ ne pardonne pas l'approximation. Il exige une rigueur absolue. Manquer de cette rigueur mène inévitablement à des comportements indéfinis et à des erreurs difficiles à déboguer.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    shared_ptr<Polygone> l_sharedPtrPoly(new Polygone);
    Ici, vous allouez dynamiquement un objet de type Polygone sur le tas. l_sharedPtrPoly est un shared_ptr qui gère la durée de vie de cet objet Polygone. Point crucial : l'objet réel, celui qui existe en mémoire, est un Polygone. Ce fait est immuable, quels que soient les casts que vous tenterez par la suite. Un cast ne transforme pas un objet d'un type en un autre ; il change seulement la manière dont le compilateur interprète le type du pointeur ou de la référence qui pointe vers cet objet.

    Votre classe Polygone possède une fonction virtuelle :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    virtual void display(){cout << "Polygone" << endl;}
    Lorsqu'une classe contient au moins une fonction virtuelle, le compilateur met en place un mécanisme appelé la "table des fonctions virtuelles" (vtable). Chaque objet d'une classe polymorphique (comme Polygone et Triangle) contient un pointeur caché, souvent appelé vptr (virtual pointer), qui pointe vers la vtable de sa classe.


    • Vtable de Polygone : Elle contiendra une entrée pointant vers l'implémentation de Polygone::display.
    • Vtable de Triangle : Elle contiendra une entrée pointant vers l'implémentation de Triangle::display (puisqu'elle surcharge display).



    Lorsque vous appelez une fonction virtuelle via un pointeur ou une référence de type de base, comme ici :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    l_sharedPtrPoly->display();

    Le programme, à l'exécution, suit ces étapes :

    1. Accède à l'objet pointé par l_sharedPtrPoly (qui est un Polygone).
    2. Utilise le vptr de cet objet pour trouver la vtable de Polygone.
    3. Dans cette vtable, il trouve l'adresse de la fonction display appropriée pour un Polygone.
    4. Exécute Polygone::display().



    C'est ce qu'on appelle la "liaison tardive" ou "dynamic dispatch", et c'est le cœur du polymorphisme en C++. C'est pourquoi votre première sortie est "Polygone".

    Vous vous demandez si le problème vient du fait que "vos objets ne sont pas dynamiques". C'est une erreur d'interprétation. Vos objets sont alloués dynamiquement avec new. Le problème n'est pas la méthode d'allocation, mais le type réel de l'objet alloué. Que l'objet soit sur la pile (statique/automatique) ou sur le tas (dynamique) ne change pas fondamentalement le fonctionnement des casts de type dans ce contexte, si ce n'est que les shared_ptr sont typiquement utilisés pour la gestion de la mémoire dynamique.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    shared_ptr<Triangle> l_sharedPtrTriSt = static_pointer_cast<Triangle>(l_sharedPtrPoly);
     
    l_sharedPtrTriSt->display();
    Vous vous attendez à "Triangle" en sortie, mais vous obtenez "Polygone". Pourquoi?

    static_pointer_cast (et son équivalent pour les pointeurs bruts, static_cast) effectue une conversion de type basée sur les informations disponibles à la compilation. Il ne fait aucune vérification à l'exécution pour s'assurer que la conversion est réellement valide. Lorsque vous faites un "downcast" (conversion d'un pointeur de classe de base vers un pointeur de classe dérivée), static_cast suppose que vous, le développeur, savez ce que vous faites et que l'objet pointé est effectivement du type cible (ou d'un type qui en dérive).
    Dans votre cas, l_sharedPtrPoly (de type shared_ptr<Polygone>) pointe vers un objet qui est un Polygone. Vous demandez au compilateur : "Traite ce pointeur comme s'il pointait vers un Triangle". Le compilateur obéit aveuglément. l_sharedPtrTriSt devient un shared_ptr<Triangle>, mais il pointe toujours vers le même objet Polygone en mémoire.


    Lorsque vous appelez l_sharedPtrTriSt->display(), le mécanisme des fonctions virtuelles entre à nouveau en jeu :

    1. L'objet sous-jacent est toujours le Polygone original.
    2. Son vptr pointe toujours vers la vtable de Polygone.
    3. L'appel à display() est résolu via cette vtable, ce qui exécute Polygone::display().



    Voilà pourquoi vous obtenez "Polygone" et non "Triangle". Le static_pointer_cast a changé le type statique du pointeur (la façon dont le compilateur le voit), mais pas le type dynamique de l'objet (ce qu'il est réellement). L'appel virtuel se base sur le type dynamique.

    L'utilisation de static_pointer_cast pour un downcast est dangereuse si l'objet n'est pas réellement du type cible. Si vous aviez tenté d'appeler une méthode spécifique à Triangle qui n'existe pas dans Polygone, comme display2():

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    // l_sharedPtrTriSt->display2(); // ATTENTION!
    Vous auriez invoqué un comportement indéfini (UB). L'objet Polygone ne possède pas de méthode display2(). Tenter d'y accéder via un pointeur de type Triangle* (obtenu par un static_pointer_cast incorrect) peut mener à un crash, à des résultats incorrects, ou à tout autre comportement imprévisible. Le compilateur ne vous protège pas ici ; il vous fait confiance.Le static_cast est une affirmation de votre part que le cast est valide. Si cette affirmation est fausse, les conséquences sont pour vous. C'est un outil puissant, mais qui requiert une certitude absolue sur les types manipulés.

    Passons à votre deuxième interrogation.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    shared_ptr<Triangle> l_sharedPtrTriDyn = dynamic_pointer_cast<Triangle>(l_sharedPtrPoly);
     
    if(l_sharedPtrTriDyn){
        //...
    }else{
        cout << "Pointeur nul" << endl;
    }
    Vous obtenez "Pointeur nul", et vous ne comprenez pas pourquoi, suspectant que cela est dû au fait que "vos objets ne sont pas dynamiques".

    dynamic_pointer_cast (et son équivalent dynamic_cast pour les pointeurs bruts) est conçu pour effectuer des conversions de type de manière sûre au sein d'une hiérarchie de classes polymorphiques (c'est-à-dire, des classes ayant au moins une fonction virtuelle). Contrairement à static_cast, dynamic_cast effectue une vérification à l'exécution pour s'assurer de la validité du cast. Cette vérification s'appuie sur les informations de type à l'exécution (Run-Time Type Information - RTTI).

    Pour un downcast (de base vers dérivée) :

    • Si l'objet pointé par le pointeur de base est effectivement une instance de la classe dérivée cible (ou d'une classe qui en hérite), dynamic_cast réussit et retourne un pointeur du type dérivé pointant vers l'objet.
    • Si l'objet pointé n'est pas une instance de la classe dérivée cible, dynamic_cast échoue. Pour les pointeurs, il retourne nullptr. Pour les références, il lèverait une exception std::bad_cast.


    Pourquoi "Pointeur Nul"?

    Dans votre code :

    1. l_sharedPtrPoly pointe vers un objet Polygone.
    2. Vous tentez de le caster en shared_ptr<Triangle> avec dynamic_pointer_cast.
    3. À l'exécution, dynamic_pointer_cast examine l'objet réel. Il constate que cet objet est un Polygone, et non un Triangle.
    4. Le cast est donc invalide.
    5. Par conséquent, dynamic_pointer_cast retourne un shared_ptr nul (un shared_ptr qui ne gère aucun objet).



    Votre if(l_sharedPtrTriDyn) évalue donc à false, et le bloc else est exécuté, affichant "Pointeur nul". C'est le comportement attendu et correct de dynamic_pointer_cast dans cette situation. Il vous signale que le cast n'est pas sûr et a échoué.La condition pour que dynamic_cast (et donc dynamic_pointer_cast) fonctionne pour les downcasts et crosscasts est que la classe de base soit polymorphique (c'est-à-dire qu'elle ait au moins une fonction virtuelle). Votre classe Polygone l'est, grâce à virtual void display().

    L'échec n'est pas dû au fait que les objets "ne sont pas dynamiques" (ils le sont), mais au fait que l'objet pointé par l_sharedPtrPoly n'est pas, et n'a jamais été, un Triangle.

    Pour ajouter,


    Bien que ce ne soit pas directement lié à vos questions, il est vital de noter une omission dans votre code original qui peut conduire à des problèmes graves, en particulier avec les shared_ptr et l'héritage : l'absence de destructeur virtuel dans la classe de base Polygone.
    Si vous gérez un objet de classe dérivée (Triangle) via un pointeur de classe de base (Polygone* ou shared_ptr<Polygone>), et que la classe de base n'a pas de destructeur virtuel, alors la suppression de l'objet via le pointeur de base n'appellera que le destructeur de la classe de base. Le destructeur de la classe dérivée ne sera pas appelé, ce qui peut entraîner des fuites de ressources si la classe dérivée allouait des ressources dans son constructeur.








    Bonne pratique : Toute classe de base destinée à être utilisée polymorphiquement (c'est-à-dire, avoir des classes dérivées et être manipulée via des pointeurs/références de base) devrait déclarer un destructeur virtuel.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    class Polygone {
    public:
        virtual void display() { /*... */ }
        virtual ~Polygone() { /*... */ } // BONNE PRATIQUE!
    };


    Même si le destructeur ne fait rien, sa simple déclaration en tant que virtual assure que la chaîne de destruction correcte sera appelée. std::shared_ptr gère cela correctement si le destructeur est virtuel.


    Le choix du bon cast n'est pas une question de préférence stylistique, mais une décision technique critique basée sur les garanties que vous possédez sur les types des objets en jeu. En cas de doute, la sécurité offerte par dynamic_pointer_cast doit toujours primer.
    La maîtrise des casts, du polymorphisme et de la gestion de la mémoire est un jalon essentiel pour tout développeur C++ qui se respecte. Ce ne sont pas de simples fonctionnalités du langage ; ce sont les fondations de paradigmes de conception qui, lorsqu'ils sont bien compris et appliqués, mènent à du code flexible, maintenable, robuste et performant. Mal compris ou mal utilisés, ils mènent inéluctablement à la fragilité et au chaos. Soyez rigoureux.



    Celui qui trouve sans chercher est celui qui a longtemps cherché sans trouver.(Bachelard)
    La connaissance s'acquiert par l'expérience, tout le reste n'est que de l'information.(Einstein)

  4. #4
    Membre averti Avatar de CaptainKrabs
    Homme Profil pro
    Étudiant
    Inscrit en
    Juin 2018
    Messages
    25
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juin 2018
    Messages : 25
    Par défaut
    Bonjour,

    Merci beaucoup pour vos réponses. Elles sont très complètes et c'est beaucoup plus claire pour moi à présent.

    Bien à vous.

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

Discussions similaires

  1. Pb de compilation avec dynamic_pointer_cast
    Par vandamme dans le forum Boost
    Réponses: 1
    Dernier message: 15/09/2009, 12h30

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