Je travaille avec des pointeurs depuis quelques années maintenant, mais je n'ai décidé que très récemment de passer aux pointeurs intelligents de C++11 (à savoir unique, partagé et faible). J'ai effectué un certain nombre de recherches à leur sujet et voici les conclusions que j'en ai tirées :
- Les conseils uniques sont excellents. Ils gèrent leur propre mémoire et sont aussi légers que les pointeurs bruts. Préférez unique_ptr aux pointeurs bruts autant que possible.
- Les pointeurs partagés sont compliqués. Ils ont une surcharge significative due au comptage des références. Passez-les par référence constante ou regrettez l'erreur de vos méthodes. Ils ne sont pas mauvais, mais doivent être utilisés avec parcimonie.
- Les pointeurs partagés doivent posséder des objets ; utilisez des pointeurs faibles lorsque la propriété n'est pas nécessaire. Le verrouillage d'un pointeur faible a une surcharge équivalente à celle du constructeur de copie de pointeur partagé.
- Continuer à ignorer l'existence de auto_ptr, qui est maintenant déprécié de toute façon.
C'est donc avec ces principes à l'esprit que j'ai entrepris de réviser ma base de code afin d'utiliser nos nouveaux pointeurs intelligents, avec la ferme intention d'éliminer autant de pointeurs bruts que possible. Cependant, je ne sais plus comment tirer le meilleur parti des pointeurs intelligents de C++11.
Supposons, par exemple, que nous concevions un jeu simple. Nous décidons qu'il est optimal de charger un type de données Texture fictif dans une classe TextureManager. Ces textures sont complexes et il n'est donc pas envisageable de les transmettre par valeur. De plus, supposons que les objets du jeu ont besoin de textures spécifiques en fonction de leur type d'objet (c'est-à-dire voiture, bateau, etc.).
Auparavant, j'aurais chargé les textures dans un vecteur (ou un autre conteneur comme unordered_map) et stocké des pointeurs vers ces textures dans chaque objet de jeu respectif, de sorte qu'ils puissent s'y référer lorsqu'ils ont besoin d'être rendus. Supposons que les textures sont garanties pour survivre à leurs pointeurs.
Ma question est donc de savoir comment utiliser au mieux les pointeurs intelligents dans cette situation. Je vois quelques options :
-
Stockez les textures directement dans un conteneur, puis construisez un unique_ptr dans chaque objet du jeu.
class TextureManager { public: const Texture& texture(const std::string& key) const { return textures_.at(key); } private: std::unordered_map<std::string, Texture> textures_; }; class GameObject { public: void set_texture(const Texture& texture) { texture_ = std::unique_ptr<Texture>(new Texture(texture)); } private: std::unique_ptr<Texture> texture_; };
Cependant, si j'ai bien compris, une nouvelle texture serait construite par copie à partir de la référence transmise, qui appartiendrait alors à l'unique_ptr. Cela me semble hautement indésirable, car j'aurais autant de copies de la texture que d'objets de jeu qui l'utilisent, ce qui irait à l'encontre de l'utilité des pointeurs (sans mauvais jeu de mots).
-
Ne stocke pas les textures directement, mais leurs pointeurs partagés dans un conteneur. Utilisez make_shared pour initialiser les pointeurs partagés. Construire des pointeurs faibles dans les objets du jeu.
class TextureManager { public: const std::shared_ptr<Texture>& texture(const std::string& key) const { return textures_.at(key); } private: std::unordered_map<std::string, std::shared_ptr<Texture>> textures_; }; class GameObject { public: void set_texture(const std::shared_ptr<Texture>& texture) { texture_ = texture; } private: std::weak_ptr<Texture> texture_; };
Contrairement au cas de l'unique_ptr, je n'aurai pas à copier-construire les textures elles-mêmes, mais le rendu des objets du jeu est coûteux car je devrais verrouiller le weak_ptr à chaque fois (aussi complexe que de copier-construire un nouveau shared_ptr).
Pour résumer, ma compréhension est la suivante : si j'utilise des pointeurs uniques, je dois copier et construire les textures ; si j'utilise des pointeurs partagés et faibles, je dois essentiellement copier et construire les pointeurs partagés chaque fois qu'un objet du jeu doit être dessiné.
Je comprends que les pointeurs intelligents seront intrinsèquement plus complexes que les pointeurs bruts et que je devrai donc subir une perte quelque part, mais ces deux coûts semblent plus élevés qu'ils ne devraient l'être.
Quelqu'un peut-il m'indiquer la bonne direction ?
Désolé pour la longue lecture, et merci pour votre temps !