Partage
  • Partager sur Facebook
  • Partager sur Twitter

Modifier directement un objet avec un pointeur

Sujet résolu
    17 août 2019 à 21:55:33

    Yo, je suis debutant en c++ (enfin j'avais appris le cours de oc mais trop vieux) et je viens d'apprendre les pointeurs intelligents avec les unique les shared et tout, sauf que apparemment pour initialisé un pointeur intelligent il faut utilisé std::make_unique ou etc(ps:je ne comprend pas trop ce que ca fait mais il me semble que fait une copie du constructeur ou je ne sais pas trop) le problème etant que, dans mon moteur de jeu, j'ai une classe hud en sfml et j'aimerai pouvoir récupérer les infos du joueur donc sans a avoir a attendre un objet de type player a chaque hud.update(), donc je voulais faire pointer un unique_ptr sur l'objet attendu dans le constructeur comme ca je recupere directement les informations par le pointeur sauf que, je ne veux pas faire de copie alors que la ca en fait une et je trouve nul part si c'est possible de faire autrement, enfin je sais pas si je m'exprime correctement, vous montrez mon code serait plus comprehensible:
    #include "HUD.h"
    HUD::HUD() {}
    
    HUD::HUD(Player &player){
    	
    	m_player = std::make_unique<Player>(player);
    
    	m_Police.loadFromFile("Neon.ttf");
    	m_Vie.setPosition(sf::Vector2f(0,0));//provisoire
    	m_Vie.setFont(m_Police);
    	m_Vie.setCharacterSize(10);
    }
    
    void HUD::Update() {
    
    	{
    		sf::Vector2f Position{ m_player->GetPosition() };
    		sf::String Textvie(std::to_string(m_player->getLive()));
    		m_Vie.setString(Textvie);
    		m_Vie.setPosition(Position.x + m_player->getDimensions().x/2,Position.y + m_player->getDimensions().y / 2);
    	}
    
    }

     le .h:

    #pragma once
    #include <SFML/Graphics.hpp>
    #include "Player.h"
    class HUD:public sf::Drawable,sf::Transformable
    {
    public:
    	HUD();
    	HUD(Player &player);
    	void Update();
    private:
    	virtual void draw(sf::RenderTarget& target, sf::RenderStates states) const
    	{
    		target.draw(m_Vie);
    
    	}
    private:
    
    
    	std::unique_ptr<Player> m_player;
    	sf::Text m_Vie;
    	sf::Font m_Police;
    };
    
    
    merci d'avance pour ceux qui prendrons la peine de bien vouloir m'aider !
    • Partager sur Facebook
    • Partager sur Twitter
      18 août 2019 à 2:14:34

      Bonjour,

      Pour avoir un pointeur sur un objet sans en avoir la responsabilité, ça existe, c'est tout simplement le bon vieux pointeur brut. On peut aussi utiliser une référence qui semble être le mieux ici.

      La fonction make_unique() va - comme son nom l'indique - créer un pointeur unique. Les paramètres de cette fonction sont exactement les même que ceux du constructeur de l'objet sur lequel on veut pointer. Donc ton code demande à fabriquer une copie du paramètre player.

      • Partager sur Facebook
      • Partager sur Twitter

      En recherche d'emploi.

        18 août 2019 à 2:28:30

        Lu',

        ElianDucheyne1 a écrit:

        j'aimerai pouvoir récupérer les infos du joueur donc sans a avoir a attendre un objet de type player a chaque hud.update(), donc je voulais faire pointer un unique_ptr sur l'objet attendu dans le constructeur comme ca je recupere directement les informations par le pointeur

        C'est dommage de baser ses decisions sur le seul argument de "flemme". En faisant comme tu souhaites, chaque HUD sera désormais lié à un personnage unique. Donc autant d'HUD que tu auras de personnages (si ton personnage se trouve dans un groupe par exemple). Donc plus de mémoire utilisée, donc plus de risque de cache miss. ça c'est juste pour l'aspect performance, mais il y'a aussi l'aspect conception....bref c'est pas ouf comme idée.

        Si tu tiens quand même à persister dans ton idée, le pointeur reste inutile ici. Une simple référence ( dans l’idéal constante) suffit. Par contre adieu le cteur par défaut.



        • Partager sur Facebook
        • Partager sur Twitter

        Eug

          18 août 2019 à 13:25:07

          Salut,

          Il faut déjà comprendre ce qu'est un pointeur pour commencer.  Un pointeur, ce n'est jamais qu'une valeur numérique entière (généralement non signée) qui représente l'adresse mémoire à laquelle nous devrions pouvoir trouver des ressources du type indiqué.

          Le problème, c'est que la notion même de pointeur est très souvent utilisée en conjonction avec l'allocation dynamique de la mémoire: on va créer la ressource pointée à l'aide de new, qui aura pour résultat de demander au système d'exploitation de réserver une quantité de mémoire suffisante que pour pouvoir représenter l'ensemble des données dont la ressource a besoin pour travailler.

          Mais, du coup, la ressource en question n'est plus soumise au règles de portées "classiques" qui font que les données "normales" (celles qui sont créées sur la pile, parce qu'on n'a pas recours à l'allocation dynamique de la mémoire) sont automatiquement détruites au moment où on atteint l'accolade fermante de la portée dans laquelle elles sont déclarées.

          Pour bien te faire comprendre le principe, voici ce qui se passe lorsque l'on n'a pas recoursà l'allocation dynamqiue de la mémoire

          int main(){
              int a;
              int b; 
              if(une_condition_quelconque){
                  /* a et b peuvent être utilisées ici */
                  int c;
                  /* b peut être utilisée ici */
              } //  b est détruite ici
              /* c est parfaitement inconnue ici, si on essaye
               * de l'utiliser, le compilateur se plaindra qu'elle
               * n'existe pas
               *
               *  par contre, on peut utiliser a et b, qui existent 
               * encore
               */
          }   // b est détruite en premier
              // a est détruite en deuxième lieu

          Par contre, lorsque l'on a recours à l'allocation dynamique de la mémoire, il appartient au développeur de veiller à libérer (lui-même) la mémoire allouée par le système d'exploitation; ce qui peut poser problème, car il doit veiller:

          à ne pas le faire trop tot : il ne peut pas le faire tant qu'il devra accéder à la ressource pointée (autrement, il essayera d'accéder à un objet détruit, ce qui posera problème) mais

          il doit aussi veiller à ne pas le faire trop tard, car, si la mémoire allouée n'a pas été libérée "en temps et en heure",  une fois que la donnée représentant le pointeur devient inaccessible, nous subirons une fuite mémoire.

          Voici un petit exemple de ce qui pourrait se passer :

          int main(){
              int * ptra= new int; // c'est vraiment pour l'exemple
              /* on peut accéder à ce qui est pointé par ptra ici */
              if(une_conditon_quelconque){
                  int * ptrb=new int; //pareil
                  /* on peut accéder à ce qui est pointé aussi bien
                   * par ptra que par ptrb ici
                   */
              }/* ptrb est détruit, mais la mémoire qui lui est
                * allouée n'a pas été libérée
                */
              /* je ne peux plus utiliser ptrb, qui est inconnu 
               * du compilateur. je ne sais donc pas libérer
               * la mémoire qui y a été associée ==>fuite mémoire
               *
               * il est plus que temps de libérer la mémoire 
               * associée à ptra
               */
              delete ptra;
          } /* ptra est détruit ici, mais la mémoire qui y est
             * associée a correctement été libérée
             */

          De plus, n'importe quelle fonction manipulant un pointeur est susceptible de décider de libérer la mémoire qui y est associée, ce qui peut poser de très sérieux problèmes, si nous finissons par nous retrouver dans une situation proche de

          /* mettons une fonciton qui décide de libérer la mémoire
           * allouée à un pointeur telle que
           */
          void destroy(int * ptr){
              delete ptr;
          }
          /* et une autre fonction qui alloue la mémoire à un
           * pointeur et qui le manipule proche de
           */
          int main(){
              int * ptra=new int;
              *ptra=10; // nickel
              /* ... */
              destroy(ptra); /* CRACK la mémoire allouée à ptra
                              * est libérée par la fonction
                              */
              /* ... */
              *ptra *=5; /* BOUM: on essaye d'accéder à une 
                          * ressource qui n'existe plus
                          */
              return 0;
          }

          Evidemment, cet exemple est très simple pour bien te permettre de comprendre le problème.  Dans certains cas, les choses seront beaucoup plus compliquées, avec des fonctions qui appellent des fonctions qui appellent des fonctions dont une fait appel à la fonction destroy :'(:-°

          Les pointeurs intelligents permettent de distinguer les parties du code qui ne font que "utiliser la ressource" de celles qui sont le(s) "propriétaire(s) légal(légaux)" des ressources; celui / ceux qui a / qui ont "droit de vie et de mort" sur la ressouce.

          L'idée, c'est qu'un utilisateur ne pourra faire qu'une chose: utiliser la ressource pointée et en aucun cas décider de la libérer.  Seul(s) le(s) propriétaire(s) de la ressource pourra / pourront décider de libérer la mémoire qui y est associée.

          La classe std::unique_ptr représente, comme son nom l'indique un propriétaire unique : à tout moment de l'exécution, le pointeur n'appartient qu'à un seul et unique élément. Quand une instance de unique_ptr sort de la portée dans laquelle elle est déclarée (ou est supprimée d'une collection, par exemple), il va s'occuper automatiquement de libérer la mémoire qui était allouée à son pointeur sous-jacent

          La classe std::shared_ptr quand à elle permet de définir des "propriétaires multiples": on peut créer plusieurs shared_ptr qui pointent tous sur la même ressource en interne, car, grâce à un système de "comptage de références", il n'y aura que quand le tout dernier propriétaire sera détruit que la mémoire allouée à la ressource sera effectivement détruite.

          (N'hésite pas à relire cette partie de mon intervention plusieurs fois ni à demander des explications supplémentaires en cas de besoins)

          Maintenant, intéressons nous un tout petit peu à ton code ;)

          Généralement, on va avoir recours à l'allocation dynamique parce que la donnée que l'on veut représenter intervient dans "une hiérarchie de classes": Ta classe Player est en effet -- a priori -- la classe de base qui te permet, par la suite, de définir des classes dérivées telles que  "Warrior", "Scout", "Wizzard" ou "Thief".

          Le problème, c'est que ces classes présentent typiquement ce que l'on appelle "une sémantique d'entité" : il serait -- entre autres -- très embêtant que l'on finisse par se retrouver, à un instant T de l'exécution du programme, avec avec deux données différentes représentant exactement le même joueur, car les modifications qui seraient apportées à l'une des instances auraient de grandes chances de... ne pas avoir été apportées à l'autre. 

          Du coup nous risquerions de très grosses surprises quant à l'état observé pour notre joueur, selon que l'on vienne à interroger une instance plutôt que l'autre.

          C'est pourquoi les classes ayant sémantique d'entité sont, entre autres:

          • non copiables (le constructeur de copie est purement et simplement refusé) et
          • non assignables (l'opérateur d'affectation est purement et simplement refusé)

          Depuis C++11, nous pouvons assez facilement imposer ces deux restrictions en déclarant le constructeur de copie et l'opérateur d'affectation comme étant delete dans la classe de base (la classe Player, dans le cas présent);  ces restrictions se répercutant d'office dans les classes dérivées ;).  Ta classe Player devrait donc prendre une forme proche de

          class Player{
          public:
              /* Parce que chaque joueur a au moins un nom */
              Player(std::string const & name);
              /* pour respecter la sémantique d'entité */
              Player(Player const &) = delete;
              Player & operator=(Player const &) = delete;
              /* parce que l'on veut pouvoir détruire n'importe
               * quel joueur alors qu'on le connait comme étant
               * du type Player
               */
              virtual ~Player() = default;
              /* tout le reste vient ici, par exemple */
              std::string const & name() const;
          private:
              /*Le joueur doit connaitre son nom en permanence */
              std::string name_;
          };
          /* Tous les types de joueur hériteront de la classe
           * Player
           */

          Mais, du coup, ton code va poser un problème au niveau du constructeur:

          HUD::HUD(Player &player){
               
              m_player = std::make_unique<Player>(player);
              /* ... */
          }

          Car, cette ligne va essayer... de créer une copie de l'instance de la classe (dérivée de) Player que le constructeur obtient comme paramètre.  Or, je viens de te dire qu'on ne peut pas créer une telle copie.  Et je viens de te montrer comment faire pour être sur que cela n'arrive JAMAIS.

          On va donc devoir se poser quelques questions supplémentaires, dont la toute première est:

          qui sera le responsable légal du maintien en mémoire de notre joueur?

          Cette question nous autorise une alternative dans le sens où nous pouvons y répondre de deux manières différentes à savoir que

          • Soit, c'est la classe HUD qui va prendre la "propriété légale" du joueur
          • soit c'est "autre chose" (par exemple, un tableau de tous les joueurs existants), ce qui fait que la classe HUD ne pourra être ... qu'un utilisateur "parmi d'autre" du joueur.

          Je ne vais pas (pour une fois) me lancer dans la comparaison des avantages et des inconvénients des deux solutions (pas tout de suite, du moins), je vais me limiter à leurs implications, ce qui devrait te permettre de faire le "meilleur choix".

          Si tu décide queclasse HUD qui prend la responsabilité de l'existence même d'un joueur et qu'elle en devient le propriétaire légal, tu vas te heurter de plein fouet à une règle conceptuelle appelée "la loi de Déméter" qui nous dit que

          Si une classe A (ta classe HUD en l'occurrence) utilise en interne un objet de type B (ta classe Player en l'occurrence), l'utilisateur d'un objet (nommons le a) de type A n'a aucun besoin de connaitre le type B pour manipuler son A

          Autrement dit, au lieu de fournir une référence sur un joueur (qui ne pourra de toutes façons pas être copiée) au constructeur de ta classe HUD, il faut que tu lui fournisse ... toutes les informations nécessaires et utiles à la création du joueur, ce qui pourrait prendre une forme proche de

          /* Il faut d'abord pouvoir déterminer le type de joueur
           * qu'on veut créer
           */
          enum PlayerType{
              Warrior_type,
              Wizzard_type,
              Scout_type,
              Thief_type
          };
          /* Puis, nous avons besoin de "quelque chose" qui ne 
           * s'occupe que de la création des joueurs, qui prendrait
           * une forme proche de
           */
          std::unique_ptr<Player> createPlayer(PayerType t, 
                                               std::string const & name);
          /* le constructeur de HUD prendrait du coup une 
           * forme proche de
           */
          HUD::HUD(PlayerType t, std::string const & name){
              m_player = std::move(createPlayer(t,name));
              /* ... */
          }

          Si par contre, tu décide que ta classe HUD n'est que l'utilisateur d'un objet de type player, il y a quelques aspcets surprenants à prendre en compte, dont le principal est toutes les instances de la classe HUD dépendent explicitement de l'existance de la donnée de type Player qu'elles manipulent.

          Autrement dit: la donnée membre m_player n'a absolument rien d'optionnelle, si bien qu'il n'y a absolument aucun intérêt à la représenter sous forme d'un pointeur.  Mais, d'un autre coté, on ne peut pas non plus la représenter sous la forme d'une variable "classique", car:

          • cette variable serait détruite lorsque l'instance de HUD est détruite, et, surtout
          • la classe Player est polymorphique, cet la donnée m_player peut représenter un élément de type Warrior, de type Wizzard, de type Scout ou autre.

          la donnée m_player devrait donc être ... une référence, et prendre une forme proche de Player /* const */& m_player;.

          Seulement, une référence doit impérativement être définie lorsqu'elle est déclarée (et, quand il s'agit d'une donnée membre de la classe, elle doit être définie dans la liste d'initialisation).  Le constructeur par défaut de ta classe HUD devrait donc être supprimé ;) et le seul constructeur valable prendrait alors une forme proche de

          HUD::HUD(Player /* const*/ & p): m_player{p}{
              /* ... */
          }

          (bien sur, si m_player est une référence non constante, p ne peut être transmis que sous forme de ... référence non constante ;) )

          Eventuellement, si la classe HUD devait disposer de plusieurs joueurs, nous pourrions avoir recours à un std::reference_wrapper, et nous pourrions avoir une donnée membre proche de std::vector<std::reference_wrapper<Player>> m_players.

          Cela te permettrait de garder le constructeur par défaut de ta classe HUD (en supprimant celui qui aurait pour objectif de créer un joueur, bien sur), et d'ajouter une fonction proche de

          void HUD::addPlayer(Payer & p){
              m_players.push_back(p);
          }
          • Partager sur Facebook
          • Partager sur Twitter
          Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs  à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
            18 août 2019 à 15:36:39

            koala01 a écrit:

            Salut,

            Il faut déjà comprendre ce qu'est un pointeur pour commencer.  Un pointeur, ce n'est jamais qu'une valeur numérique entière (généralement non signée) qui représente l'adresse mémoire à laquelle nous devrions pouvoir trouver des ressources du type indiqué.

            Le problème, c'est que la notion même de pointeur est très souvent utilisée en conjonction avec l'allocation dynamique de la mémoire: on va créer la ressource pointée à l'aide de new, qui aura pour résultat de demander au système d'exploitation de réserver une quantité de mémoire suffisante que pour pouvoir représenter l'ensemble des données dont la ressource a besoin pour travailler.

            Mais, du coup, la ressource en question n'est plus soumise au règles de portées "classiques" qui font que les données "normales" (celles qui sont créées sur la pile, parce qu'on n'a pas recours à l'allocation dynamique de la mémoire) sont automatiquement détruites au moment où on atteint l'accolade fermante de la portée dans laquelle elles sont déclarées.

            Pour bien te faire comprendre le principe, voici ce qui se passe lorsque l'on n'a pas recoursà l'allocation dynamqiue de la mémoire

            int main(){
                int a;
                int b; 
                if(une_condition_quelconque){
                    /* a et b peuvent être utilisées ici */
                    int c;
                    /* b peut être utilisée ici */
                } //  b est détruite ici
                /* c est parfaitement inconnue ici, si on essaye
                 * de l'utiliser, le compilateur se plaindra qu'elle
                 * n'existe pas
                 *
                 *  par contre, on peut utiliser a et b, qui existent 
                 * encore
                 */
            }   // b est détruite en premier
                // a est détruite en deuxième lieu

            Par contre, lorsque l'on a recours à l'allocation dynamique de la mémoire, il appartient au développeur de veiller à libérer (lui-même) la mémoire allouée par le système d'exploitation; ce qui peut poser problème, car il doit veiller:

            à ne pas le faire trop tot : il ne peut pas le faire tant qu'il devra accéder à la ressource pointée (autrement, il essayera d'accéder à un objet détruit, ce qui posera problème) mais

            il doit aussi veiller à ne pas le faire trop tard, car, si la mémoire allouée n'a pas été libérée "en temps et en heure",  une fois que la donnée représentant le pointeur devient inaccessible, nous subirons une fuite mémoire.

            Voici un petit exemple de ce qui pourrait se passer :

            int main(){
                int * ptra= new int; // c'est vraiment pour l'exemple
                /* on peut accéder à ce qui est pointé par ptra ici */
                if(une_conditon_quelconque){
                    int * ptrb=new int; //pareil
                    /* on peut accéder à ce qui est pointé aussi bien
                     * par ptra que par ptrb ici
                     */
                }/* ptrb est détruit, mais la mémoire qui lui est
                  * allouée n'a pas été libérée
                  */
                /* je ne peux plus utiliser ptrb, qui est inconnu 
                 * du compilateur. je ne sais donc pas libérer
                 * la mémoire qui y a été associée ==>fuite mémoire
                 *
                 * il est plus que temps de libérer la mémoire 
                 * associée à ptra
                 */
                delete ptra;
            } /* ptra est détruit ici, mais la mémoire qui y est
               * associée a correctement été libérée
               */

            De plus, n'importe quelle fonction manipulant un pointeur est susceptible de décider de libérer la mémoire qui y est associée, ce qui peut poser de très sérieux problèmes, si nous finissons par nous retrouver dans une situation proche de

            /* mettons une fonciton qui décide de libérer la mémoire
             * allouée à un pointeur telle que
             */
            void destroy(int * ptr){
                delete ptr;
            }
            /* et une autre fonction qui alloue la mémoire à un
             * pointeur et qui le manipule proche de
             */
            int main(){
                int * ptra=new int;
                *ptra=10; // nickel
                /* ... */
                destroy(ptra); /* CRACK la mémoire allouée à ptra
                                * est libérée par la fonction
                                */
                /* ... */
                *ptra *=5; /* BOUM: on essaye d'accéder à une 
                            * ressource qui n'existe plus
                            */
                return 0;
            }

            Evidemment, cet exemple est très simple pour bien te permettre de comprendre le problème.  Dans certains cas, les choses seront beaucoup plus compliquées, avec des fonctions qui appellent des fonctions qui appellent des fonctions dont une fait appel à la fonction destroy :'(:-°

            Les pointeurs intelligents permettent de distinguer les parties du code qui ne font que "utiliser la ressource" de celles qui sont le(s) "propriétaire(s) légal(légaux)" des ressources; celui / ceux qui a / qui ont "droit de vie et de mort" sur la ressouce.

            L'idée, c'est qu'un utilisateur ne pourra faire qu'une chose: utiliser la ressource pointée et en aucun cas décider de la libérer.  Seul(s) le(s) propriétaire(s) de la ressource pourra / pourront décider de libérer la mémoire qui y est associée.

            La classe std::unique_ptr représente, comme son nom l'indique un propriétaire unique : à tout moment de l'exécution, le pointeur n'appartient qu'à un seul et unique élément. Quand une instance de unique_ptr sort de la portée dans laquelle elle est déclarée (ou est supprimée d'une collection, par exemple), il va s'occuper automatiquement de libérer la mémoire qui était allouée à son pointeur sous-jacent

            La classe std::shared_ptr quand à elle permet de définir des "propriétaires multiples": on peut créer plusieurs shared_ptr qui pointent tous sur la même ressource en interne, car, grâce à un système de "comptage de références", il n'y aura que quand le tout dernier propriétaire sera détruit que la mémoire allouée à la ressource sera effectivement détruite.

            (N'hésite pas à relire cette partie de mon intervention plusieurs fois ni à demander des explications supplémentaires en cas de besoins)

            Maintenant, intéressons nous un tout petit peu à ton code ;)

            Généralement, on va avoir recours à l'allocation dynamique parce que la donnée que l'on veut représenter intervient dans "une hiérarchie de classes": Ta classe Player est en effet -- a priori -- la classe de base qui te permet, par la suite, de définir des classes dérivées telles que  "Warrior", "Scout", "Wizzard" ou "Thief".

            Le problème, c'est que ces classes présentent typiquement ce que l'on appelle "une sémantique d'entité" : il serait -- entre autres -- très embêtant que l'on finisse par se retrouver, à un instant T de l'exécution du programme, avec avec deux données différentes représentant exactement le même joueur, car les modifications qui seraient apportées à l'une des instances auraient de grandes chances de... ne pas avoir été apportées à l'autre. 

            Du coup nous risquerions de très grosses surprises quant à l'état observé pour notre joueur, selon que l'on vienne à interroger une instance plutôt que l'autre.

            C'est pourquoi les classes ayant sémantique d'entité sont, entre autres:

            • non copiables (le constructeur de copie est purement et simplement refusé) et
            • non assignables (l'opérateur d'affectation est purement et simplement refusé)

            Depuis C++11, nous pouvons assez facilement imposer ces deux restrictions en déclarant le constructeur de copie et l'opérateur d'affectation comme étant delete dans la classe de base (la classe Player, dans le cas présent);  ces restrictions se répercutant d'office dans les classes dérivées ;).  Ta classe Player devrait donc prendre une forme proche de

            class Player{
            public:
                /* Parce que chaque joueur a au moins un nom */
                Player(std::string const & name);
                /* pour respecter la sémantique d'entité */
                Player(Player const &) = delete;
                Player & operator=(Player const &) = delete;
                /* parce que l'on veut pouvoir détruire n'importe
                 * quel joueur alors qu'on le connait comme étant
                 * du type Player
                 */
                virtual ~Player() = default;
                /* tout le reste vient ici, par exemple */
                std::string const & name() const;
            private:
                /*Le joueur doit connaitre son nom en permanence */
                std::string name_;
            };
            /* Tous les types de joueur hériteront de la classe
             * Player
             */

            Mais, du coup, ton code va poser un problème au niveau du constructeur:

            HUD::HUD(Player &player){
                 
                m_player = std::make_unique<Player>(player);
                /* ... */
            }

            Car, cette ligne va essayer... de créer une copie de l'instance de la classe (dérivée de) Player que le constructeur obtient comme paramètre.  Or, je viens de te dire qu'on ne peut pas créer une telle copie.  Et je viens de te montrer comment faire pour être sur que cela n'arrive JAMAIS.

            On va donc devoir se poser quelques questions supplémentaires, dont la toute première est:

            qui sera le responsable légal du maintien en mémoire de notre joueur?

            Cette question nous autorise une alternative dans le sens où nous pouvons y répondre de deux manières différentes à savoir que

            • Soit, c'est la classe HUD qui va prendre la "propriété légale" du joueur
            • soit c'est "autre chose" (par exemple, un tableau de tous les joueurs existants), ce qui fait que la classe HUD ne pourra être ... qu'un utilisateur "parmi d'autre" du joueur.

            Je ne vais pas (pour une fois) me lancer dans la comparaison des avantages et des inconvénients des deux solutions (pas tout de suite, du moins), je vais me limiter à leurs implications, ce qui devrait te permettre de faire le "meilleur choix".

            Si tu décide queclasse HUD qui prend la responsabilité de l'existence même d'un joueur et qu'elle en devient le propriétaire légal, tu vas te heurter de plein fouet à une règle conceptuelle appelée "la loi de Déméter" qui nous dit que

            Si une classe A (ta classe HUD en l'occurrence) utilise en interne un objet de type B (ta classe Player en l'occurrence), l'utilisateur d'un objet (nommons le a) de type A n'a aucun besoin de connaitre le type B pour manipuler son A

            Autrement dit, au lieu de fournir une référence sur un joueur (qui ne pourra de toutes façons pas être copiée) au constructeur de ta classe HUD, il faut que tu lui fournisse ... toutes les informations nécessaires et utiles à la création du joueur, ce qui pourrait prendre une forme proche de

            /* Il faut d'abord pouvoir déterminer le type de joueur
             * qu'on veut créer
             */
            enum PlayerType{
                Warrior_type,
                Wizzard_type,
                Scout_type,
                Thief_type
            };
            /* Puis, nous avons besoin de "quelque chose" qui ne 
             * s'occupe que de la création des joueurs, qui prendrait
             * une forme proche de
             */
            std::unique_ptr<Player> createPlayer(PayerType t, 
                                                 std::string const & name);
            /* le constructeur de HUD prendrait du coup une 
             * forme proche de
             */
            HUD::HUD(PlayerType t, std::string const & name){
                m_player = std::move(createPlayer(t,name));
                /* ... */
            }

            Si par contre, tu décide que ta classe HUD n'est que l'utilisateur d'un objet de type player, il y a quelques aspcets surprenants à prendre en compte, dont le principal est toutes les instances de la classe HUD dépendent explicitement de l'existance de la donnée de type Player qu'elles manipulent.

            Autrement dit: la donnée membre m_player n'a absolument rien d'optionnelle, si bien qu'il n'y a absolument aucun intérêt à la représenter sous forme d'un pointeur.  Mais, d'un autre coté, on ne peut pas non plus la représenter sous la forme d'une variable "classique", car:

            • cette variable serait détruite lorsque l'instance de HUD est détruite, et, surtout
            • la classe Player est polymorphique, cet la donnée m_player peut représenter un élément de type Warrior, de type Wizzard, de type Scout ou autre.

            la donnée m_player devrait donc être ... une référence, et prendre une forme proche de Player /* const */& m_player;.

            Seulement, une référence doit impérativement être définie lorsqu'elle est déclarée (et, quand il s'agit d'une donnée membre de la classe, elle doit être définie dans la liste d'initialisation).  Le constructeur par défaut de ta classe HUD devrait donc être supprimé ;) et le seul constructeur valable prendrait alors une forme proche de

            HUD::HUD(Player /* const*/ & p): m_player{p}{
                /* ... */
            }

            (bien sur, si m_player est une référence non constante, p ne peut être transmis que sous forme de ... référence non constante ;) )

            Eventuellement, si la classe HUD devait disposer de plusieurs joueurs, nous pourrions avoir recours à un std::reference_wrapper, et nous pourrions avoir une donnée membre proche de std::vector<std::reference_wrapper<Player>> m_players.

            Cela te permettrait de garder le constructeur par défaut de ta classe HUD (en supprimant celui qui aurait pour objectif de créer un joueur, bien sur), et d'ajouter une fonction proche de

            void HUD::addPlayer(Payer & p){
                m_players.push_back(p);
            }

            Wow ! merci c'est cool de carrement m'avoir fait un mini cours que j'ai lu attentivement, grace a toi j'ai reussi a faire ce que je voulais (avec le conteneur de reference_wrapper), je vais quand meme avouer queje n'ai pas encore le reflexe de penser pointeurs, reference et allocation dynamique quand je m'organise dans ma tete, mais ca devrait finir par arriver a force de pratiquer, je met le topic en resolu, merci a tout ceux qui m'ont aider a y voir un peu plus clair
            • Partager sur Facebook
            • Partager sur Twitter

            Modifier directement un objet avec un pointeur

            × 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