Partage
  • Partager sur Facebook
  • Partager sur Twitter

Utilisation des pointeurs inteligents

Les pointeurs inteligents de la librairie standard

Sujet résolu
    30 juin 2019 à 22:37:02

    Bonjour ! Je viens vers la communauté car j'ai un problème dans l'utilisation de std::shared_ptr

    J'avais l'habitude d'utiliser des pointeurs, mais dans un soucis de clarté j'ai décidé de changer les pointeurs de mon code en pointeurs intelligents.

    L’intérêt principal de std::shared_ptr viens quand je veux transférer la possession d'un objet. En effet sans je doit bien faire attention à qui doit supprimer mon objet pour ne pas avoir de problème, ce qui peut poser des problème lorsque je reviens sur un code d'on je ne me souviens plus ou un code complexe.

    J'aurais besoin d'aide pour savoir quelle est la solution la plus propre à utiliser dans différents cas.

    Mon code d'origine :

    sf::RenderWindow m_window;
    
    [...]
    
    void MainHandler::pushScene(Scene* handler)
    {
        handler->init();
        handler->setWindow(&m_window);
        handler->m_mainhandler = this;
        sf::Lock l(m_mutex_scene);
        m_scenes.push(handler);
        m_scene = handler;
    }

    Ici trois cas d'utilisation de pointeurs :

    1) Le paramètre de la fonction

    Après cette fonction ma classe deviens le propriétaire du "Scene* handler" et doit gérer sa suppression donc avec std::shared_ptr :

    void MainHandler::pushScene(std::shared_ptr<Scene> const& handler)
    {
        ...
    }

     2) setWindow(&m_window);

    Cette fonction donne à la scène un pointeur vers la fenêtre sur laquelle elle doit se rendre, ici problème ; la fenêtre est un objet membre de ma classe.

    Donc comment faire ?

    Stocker ma fenêtre dans un std::shared_ptr dès le départ ? ie :

    std::shared_ptr<sf::RenderWindow> m_window = std::make_shared<sf::RenderWindow>();
    
    [...]
    
    void MainHandler::pushScene(std::shared_ptr<Scene> handler)
    {
        handler->init();
        handler->setWindow(m_window);
        ...
    }

     Ou laisser tel quel avec des pointeurs normaux puisqu'il n'y a pas de transfert de propriété ?

    3) "handler->m_mainhandler = this;"

    Ici le problème est à peu près le même sauf que le pointeur passé est l'instance de ma classe, donc je ne peut pas résoudre le problème comme tout à l'heure.

    Merci de votre aide !

    -
    Edité par frayien 30 juin 2019 à 22:37:35

    • Partager sur Facebook
    • Partager sur Twitter
      30 juin 2019 à 23:54:50

      > L’intérêt principal de std::shared_ptr viens quand je veux transférer la possession d'un objet.

      Euuh, le but de shared_ptr est de partager la responsabilité, pas de la déléguer. De ce que tu dis, j'ai l'impression qu'il n'y a qu'un seul responsable, ce qui est le rôle de std::unique_ptr. Le transfert se fait alors avec std::move.

      1) pushScene(std::unique_ptr<Scene> handler)

      c'est un vrai transfert, zéro partage. À la charge de la fonction de propager handler dans la liste de scène: m_scenes.push(std::move(handler)).

      Tu remarques que pushScene ne prend pas handler sous forme de référence constante. C'est normal. Sinon on ne pourrait pas transférer la responsabilité car cela implique d'extraire le pointeur de pointeur intelligent pour le mettre dans un autre pointeur intelligent (celui dans m_scenes). Ici, l'appelant sera obligé construire le unique_ptr directement à l'appel ou de passer par std::move pour transférer la responsabilité à la fonction pushScene.

      2) Si setWindow ne prends la responsabilité sur la durée de vie de la fenêtre, alors un simple pointeur ou une référence est justifié. La référence aura au moins l'avantage d'interdire explicitement les pointeurs nuls, puisqu'une référence n'est jamais nul.

      3) j'ai l'impression que c'est pareil que 2.

      • Partager sur Facebook
      • Partager sur Twitter
        1 juillet 2019 à 0:41:36

        Merci ! Il se trouve que je ne suis pas très au point sur l'utilisation du c++ moderne ... et j'ai l'impression que c'est précisément ce qui me fait défaut ici.

        Je ne sais pas exactement ce que fait std::move mais ce qui semble sur c'est que si je veux continuer sans faire n'importe quoi il va falloir que je me renseigne plus dessus ...

        Connaitrait-tu un cours bien qui explique ce genre de chose ? ie le c++ moderne en particulier la gestion de la mémoire. Ce serait probablement plus rapide et pertinent que de tout découvrir via la doc.

        Par exemple je ne sais pas du tout ce qu'est la notation avec deux "&" que je vois souvent dans la doc, par exemple ici :

        template< class T >
        constexpr typename std::remove_reference<T>::type&& move( T&& t ) noexcept;

        2/3) Très bien mais dans le cas ou je fait passer une référence dans la fonction setWindow :

        Je veux garder une référence vers m_window dans mon objet.

        Du coup dans mon objet je garde un pointeur normal vers m_window ? que j'obtiendrai avec &window ; ou il existe une autre méthode ?

        Aussi si j'ai bien compris le principe ma fonction

        Scene* getScene() {... return m_scene; }

        deviens

        Scene& getScene() {... return *m_scene; }

        ou

        Scene* getScene() {... return m_scene.get(); }

        et le mieux serait alors de renvoyer une référence pour ne pas se balader inutilement avec des pointeurs.

        • Partager sur Facebook
        • Partager sur Twitter
          1 juillet 2019 à 1:55:03

          Pour des explications sur la sémantique de déplacement, j'ai ça: http://h-deb.clg.qc.ca/Sujets/Divers--cplusplus/Mouvement.html et https://zestedesavoir.com/tutoriels/496/une-nouvelle-fonctionnalite-de-c-11-la-semantique-de-mouvement/

          Pour &&, il a des significations différentes si T est un type template. Normalement && sur un type est une rvalue référence, qui représente essentiellement une valeur temporaire ou que l'on veut temporaire. C'est également la forme utilisée pour dire "prend le relai, je ne touche plus à la valeur".

          Si T est template, T&& est une référence universelle: soit une rvalue, soit une lvalue. On peut ainsi conserver la vraie référence et la propager à d'autres fonctions avec std::forward. Ce n'est peut-être pas la peine de comprendre tout de suite le principe de référence universelle, mais sinon il y a https://zestedesavoir.com/tutoriels/474/la-deduction-de-type-en-c/

          Pour le retour de std::move, puisqu'il y a un tout un bazar qui supprime les références dans le type T, le retour est une rvalue. std::move n'est rien de plus qu'un cast vers une rvalue.

          > Du coup dans mon objet je garde un pointeur normal vers m_window ? que j'obtiendrai avec &window ; ou il existe une autre méthode ?

          Tu peux garder un pointeur. Après, pour des situations qui pourrait être plus restrictive, on peut envisager un pointeur qui ne peut jamais être nul (qui fait des assert dans le cas contraire par exemple) ou l'utilisation de std::reference_wrapper (qui n'est hélas pas super pratique à l'usage).

          Pour le getScene, tout dépend si utiliser getScene alors qu'il n'y a pas de scène à du sens. Un pointeur permet de faire la distinction (ou std::optional). Dans le cas contraire, une référence est toujours préférable.

          • Partager sur Facebook
          • Partager sur Twitter
            1 juillet 2019 à 12:57:52

            Je vois, merci ! Je passe en résolu.
            • Partager sur Facebook
            • Partager sur Twitter

            Utilisation des pointeurs inteligents

            × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
            × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
            • Editeur
            • Markdown