Bonjour,
Je voulais savoir si utiliser un std::vector pour stocker des pointeurs d'objets est qualifiée comme 'bonne pratique' en C++
Dans mon cas je suis en train de me créer un moteur de jeu de cartes, le vector représente le deck qui contient des pointeurs vers les objets de type Carte.
Je serai ravi d'avoir vos points de vue. Merci d'avance
Je dirais que ça peut être une bonne idée, après si le nombre de carte dans le deck est fixe ou qu'il est ordonné (que les cartes ne changent pas d'ordre dans le deck), tu peux utiliser un pointeur sur pointeur sur Carte (**Carte) avec le nombre de carte présente dans le deck à réserver sur le tas/stack, et incrémenter l'indice du tableau du pointeur quand tu tires une carte
Mais oui un std::vector ça m'a l'air pas trop mal ![]()
Je pense que ce serait plus approprié en C++ de faire un vector de références.
Le 05 août 2019 à 12:20:17 Galfwin a écrit :
Je pense que ce serait plus approprié en C++ de faire un vector de références.
Impossible. Une référence est un pointeur constant alors qu'un vector a besoin de pouvoir déplacer l'objet contenu donc dans notre cas le pointeur.
Oui, ça peut se faire, l'idéal est que tu utilises des smart pointers. Après, je sais que certains préfère utiliser les std::option plutôt que des pointeurs pour des raisons d'optimisation.
Une autre solution est de ne pas utiliser le polymorphisme pour tes cartes d'avoir qu'un seul type de carte et donc plus besoin de pointeur (pour ça je pense que tu peux utiliser des foncteurs pour les actions des différentes cartes par exemple).
Je comprends pas, qu'apporterait un std::optionnal ? Un std::optionnal stock l'objet directement donc ça serait comme faire un std::vector<Carte>, avec le problème du découpage en cas de polymorphisme. La seule différence ça serait la possibilité de pouvoir stocker le fait qu'il n'y ai pas d'objet mais je vois pas trop comment ça pourrait aider.
Pardon, j'ai confondu avec le std::variant ![]()
Il n'y a pas de probleme fondamentale a stocker des pointeur dans un std::vector. La question qu'il faut qeu tu te pose est: quelle est la duree de vie des cartes stocke dans le std::vector?
Si la duree de vie des cartes est claire et que les cartes seront detruites apres le std::vector, alors utilise un pointeur normal, il n'y a pas de probleme. C'est a ca que les pointeur normaux servent.
La question devient plus complique si la duree de vie des cartes n'est pas aussi simple, peut etre qu'il n'y a pas un point de destruction unique des cartes et si eventuellement le std::vector peut etre le seul endroit ou la carte est stocke et que c'est ce chemin la qui doit amener a la destruction de l'objet carte, alors il va te falloir utiliser un truc plus complique. Commence par regarde si std::shared_ptr fait ce que tu veux. (Et dans ce cas regarde comment weak_ptr resoud le probleme de circularite de pointeur si c'est utile dans ton application.)
Je ne vois pas en quoi un std::optionnal ou un std::variant va etre utile ici.
J'ai tendance à utiliser des unique_ptr quand je travaille avec un pointeur classique, ça économise quelque ligne et permet de plus simplement utiliser les default constructor.
Pour les variant, c'est un mec qui les utilisait en mettant tout les types acceptés dans le vector (genre du ActionCard et DefenseCard dans le variant au lieu de Card* ou unique_ptr<Card>), il me disait que ça faisait gagner du temps à l'exécution mais j'ai jamais été essayé. J'évoquais juste les différentes possibilités.
Merci de vos réponses, je vais me renseigner sur les std::shared_ptr si effectivement ils peuvent m'être utiles.
Pour les cartes j'ai fait une classe Carte unique, c'est pour un jeu de tarot. Ceci dit je me sens bête de pas avoir fait dériver cette classe en des classes plus spécialisées ![]()
Le 05 août 2019 à 17:48:37 TechnoForce3 a écrit :
J'ai tendance à utiliser des unique_ptr quand je travaille avec un pointeur classique, ça économise quelque ligne et permet de plus simplement utiliser les default constructor.Pour les variant, c'est un mec qui les utilisait en mettant tout les types acceptés dans le vector (genre du ActionCard et DefenseCard dans le variant au lieu de Card* ou unique_ptr<Card>), il me disait que ça faisait gagner du temps à l'exécution mais j'ai jamais été essayé. J'évoquais juste les différentes possibilités.
Tu peux seulement utiliser les unique_ptr si l'ownership du pointeur est clair. Tu ne peux avoir qu'une seule copie de l'unique_ptr, ca veut dire que les autres copies du poitneur sont des pointeurs standard. Et c'est probablement ca qu'il va mettre dans son vector.
Pour les cartes j'ai fait une classe Carte unique, c'est pour un jeu de tarot. Ceci dit je me sens bête de pas avoir fait dériver cette classe en des classes plus spécialisées
Non, c'est probablement ce que tu veux. C'est meme peut etre trop complique deja.
En pratique, tu peux representer les cartes avec des entiers (un char meme). Tu as 14 cartes par couleurs, 21 atouts, et l'excuse. Donc tu peux mettre les piques de 0 a 13, les carreaux de 14 a 27, les cours de 28 a 43, les trefles de 44 a 57, les atouts des 58 a 79, et l'excuse a 80. Du fait, la donnee de chaque cartes est un seul char. Et tu peux faire des vectors de char. Du coup, pas de probleme d'allocation dynamique chelou.
Le 05 août 2019 à 19:38:27 godrik a écrit :
Le 05 août 2019 à 17:48:37 TechnoForce3 a écrit :
J'ai tendance à utiliser des unique_ptr quand je travaille avec un pointeur classique, ça économise quelque ligne et permet de plus simplement utiliser les default constructor.Pour les variant, c'est un mec qui les utilisait en mettant tout les types acceptés dans le vector (genre du ActionCard et DefenseCard dans le variant au lieu de Card* ou unique_ptr<Card>), il me disait que ça faisait gagner du temps à l'exécution mais j'ai jamais été essayé. J'évoquais juste les différentes possibilités.
Tu peux seulement utiliser les unique_ptr si l'ownership du pointeur est clair. Tu ne peux avoir qu'une seule copie de l'unique_ptr, ca veut dire que les autres copies du poitneur sont des pointeurs standard. Et c'est probablement ca qu'il va mettre dans son vector.
J'aurais tendance à faire un move du pointeur pour l'ajouter dans le vecteur et y accéder seulement par référence sur l'objet pointé quand demandé. Et à la limite une implémentation du pattern copy mais pas obligatoire et un appel de copy, j'imagine que ça peut être assez lourd si on le fait souvent (allocation sur le tas à chaque fois) ![]()
Si tu as qu'une seule classe pourquoi vouloir stocker un pointeur ? Met directement ton objet dedans ![]()
Le 05 août 2019 à 19:43:25 godrik a écrit :
Pour les cartes j'ai fait une classe Carte unique, c'est pour un jeu de tarot. Ceci dit je me sens bête de pas avoir fait dériver cette classe en des classes plus spécialisées
Non, c'est probablement ce que tu veux. C'est meme peut etre trop complique deja.
En pratique, tu peux representer les cartes avec des entiers (un char meme). Tu as 14 cartes par couleurs, 21 atouts, et l'excuse. Donc tu peux mettre les piques de 0 a 13, les carreaux de 14 a 27, les cours de 28 a 43, les trefles de 44 a 57, les atouts des 58 a 79, et l'excuse a 80. Du fait, la donnee de chaque cartes est un seul char. Et tu peux faire des vectors de char. Du coup, pas de probleme d'allocation dynamique chelou.
J'y avais pas pensé, je vais refaire le système de gestion des cartes parce qu'avec ces histoires de pointeurs il y en a dans tous les sens dans mon code et ca a clairement ruiné la compréhension de ce dernier.
Le 05 août 2019 à 19:58:18 TechnoForce3 a écrit :
Le 05 août 2019 à 19:38:27 godrik a écrit :
Le 05 août 2019 à 17:48:37 TechnoForce3 a écrit :
J'ai tendance à utiliser des unique_ptr quand je travaille avec un pointeur classique, ça économise quelque ligne et permet de plus simplement utiliser les default constructor.Pour les variant, c'est un mec qui les utilisait en mettant tout les types acceptés dans le vector (genre du ActionCard et DefenseCard dans le variant au lieu de Card* ou unique_ptr<Card>), il me disait que ça faisait gagner du temps à l'exécution mais j'ai jamais été essayé. J'évoquais juste les différentes possibilités.
Tu peux seulement utiliser les unique_ptr si l'ownership du pointeur est clair. Tu ne peux avoir qu'une seule copie de l'unique_ptr, ca veut dire que les autres copies du poitneur sont des pointeurs standard. Et c'est probablement ca qu'il va mettre dans son vector.
J'aurais tendance à faire un move du pointeur pour l'ajouter dans le vecteur et y accéder seulement par référence sur l'objet pointé quand demandé. Et à la limite une implémentation du pattern copy mais pas obligatoire et un appel de copy, j'imagine que ça peut être assez lourd si on le fait souvent (allocation sur le tas à chaque fois)
Si tu as qu'une seule classe pourquoi vouloir stocker un pointeur ? Met directement ton objet dedans
Je voulais stocker les pointeurs afin de passer les cartes par adresses et éviter de passer par valeur toutes les cartes à chaque fois que je voulais manipuler un deck. A vouloir optimiser j'ai embrouillé mon code
En tout cas merci à vous ![]()
Le 05 août 2019 à 19:58:18 TechnoForce3 a écrit :
Le 05 août 2019 à 19:38:27 godrik a écrit :
Le 05 août 2019 à 17:48:37 TechnoForce3 a écrit :
J'ai tendance à utiliser des unique_ptr quand je travaille avec un pointeur classique, ça économise quelque ligne et permet de plus simplement utiliser les default constructor.Pour les variant, c'est un mec qui les utilisait en mettant tout les types acceptés dans le vector (genre du ActionCard et DefenseCard dans le variant au lieu de Card* ou unique_ptr<Card>), il me disait que ça faisait gagner du temps à l'exécution mais j'ai jamais été essayé. J'évoquais juste les différentes possibilités.
Tu peux seulement utiliser les unique_ptr si l'ownership du pointeur est clair. Tu ne peux avoir qu'une seule copie de l'unique_ptr, ca veut dire que les autres copies du poitneur sont des pointeurs standard. Et c'est probablement ca qu'il va mettre dans son vector.
J'aurais tendance à faire un move du pointeur pour l'ajouter dans le vecteur et y accéder seulement par référence sur l'objet pointé quand demandé. Et à la limite une implémentation du pattern copy mais pas obligatoire et un appel de copy, j'imagine que ça peut être assez lourd si on le fait souvent (allocation sur le tas à chaque fois)
Si tu as qu'une seule classe pourquoi vouloir stocker un pointeur ? Met directement ton objet dedans
Pour un jeu de ce type la, le cout de calcull est tres faible, la lisibilite est importante. Tu peux avoir besoin de pointeur si une carte peut apparaitre dans plusieurs collection a la fois. Par exemple, ton IA pourrait maintenir des etat possibles de la main des joueurs (c'est la collection des cartes que joueur 3 pourrait avoir). Ou meme explorer des scenarios futures (comme un genre d'alpha beta dans un jeu un d'echec.)
Ou alors ton objet carte pourrait etre relativement complexe, penses a un jeu comme magic the gathering ou une carte peut une tonne de propriete, exister en double, ou etre modifie par d'autres cartes.
En l'occurence pour un jeu de tarot, met directement ton objet dedans, et l'objet c'est un entier fondamentalement.
Le 05 août 2019 à 21:33:05 godrik a écrit :
Le 05 août 2019 à 19:58:18 TechnoForce3 a écrit :
Le 05 août 2019 à 19:38:27 godrik a écrit :
Le 05 août 2019 à 17:48:37 TechnoForce3 a écrit :
J'ai tendance à utiliser des unique_ptr quand je travaille avec un pointeur classique, ça économise quelque ligne et permet de plus simplement utiliser les default constructor.Pour les variant, c'est un mec qui les utilisait en mettant tout les types acceptés dans le vector (genre du ActionCard et DefenseCard dans le variant au lieu de Card* ou unique_ptr<Card>), il me disait que ça faisait gagner du temps à l'exécution mais j'ai jamais été essayé. J'évoquais juste les différentes possibilités.
Tu peux seulement utiliser les unique_ptr si l'ownership du pointeur est clair. Tu ne peux avoir qu'une seule copie de l'unique_ptr, ca veut dire que les autres copies du poitneur sont des pointeurs standard. Et c'est probablement ca qu'il va mettre dans son vector.
J'aurais tendance à faire un move du pointeur pour l'ajouter dans le vecteur et y accéder seulement par référence sur l'objet pointé quand demandé. Et à la limite une implémentation du pattern copy mais pas obligatoire et un appel de copy, j'imagine que ça peut être assez lourd si on le fait souvent (allocation sur le tas à chaque fois)
Si tu as qu'une seule classe pourquoi vouloir stocker un pointeur ? Met directement ton objet dedans
Pour un jeu de ce type la, le cout de calcull est tres faible, la lisibilite est importante. Tu peux avoir besoin de pointeur si une carte peut apparaitre dans plusieurs collection a la fois. Par exemple, ton IA pourrait maintenir des etat possibles de la main des joueurs (c'est la collection des cartes que joueur 3 pourrait avoir). Ou meme explorer des scenarios futures (comme un genre d'alpha beta dans un jeu un d'echec.)
Ou alors ton objet carte pourrait etre relativement complexe, penses a un jeu comme magic the gathering ou une carte peut une tonne de propriete, exister en double, ou etre modifie par d'autres cartes.
En l'occurence pour un jeu de tarot, met directement ton objet dedans, et l'objet c'est un entier fondamentalement.
Je suis d'accord. Je me plaçais plutôt dans un cas plus général. Globalement, les pointeurs je m'en sers que si j'ai besoin du polymorphisme sinon je travaille directement avec les objets et chacun à sa copie et si je dois avoir un objet qui ai accès à une instance précise alors je stocke la référence mais c'est peut-être pas une bonne pratique ![]()
Edit: Bon en stockant la référence, je m'empêche de le changer dans la durée de vie de l'objet du coup, c'est assez spécifique à des cas précis.
Pour le coup je suis pas d'accord avec godrik, c'est pas parce que tu peux stocker ta valeur dans un type que le type est forcément adapté.
Typiquement imaginons une variable qui contient l'âge d'une personne, ça rentrerait dans un char, mais un char c'est fait pour stocker un caractère, pas des nombres, donc moi je mettrais ça dans un int. Oui évidemment un char c'est un nombre en réalité, mais l'idée derrière n'est pas de stocker un nombre mais un caractère, si plus tard tu voudras afficher cet âge dans la sortie standard avec un std::cout par exemple, et que cet âge vaut 65, si c'est un char tu auras un 'A' qui va s'afficher mais si c'est un int tu auras un '65'. Enfin bref pour résumer, utilise un type qui représente vraiment ce que tu veux y stocker afin de rendre ton code plus clair et d'éviter les potentiels bugs si des fonctions réagissent différemment en fonction du type de leur paramètre.
Et utiliser une classe carte te permettra aussi de valider la valeur que tu reçois pour être certain de stocker une carte valide, parce que ça ferait pas de sens de stocker un 152 de trèfle ou un -15 de cœur.
On parle de carte ici. Il va falloir etendre les cartes de tout un tas de semantique. Aucune des operations naturelles sur les types primaires ne vont correspondre a ce que tu veux sur tes cartes. Donc tu vas toujours te retrouver a devoir appliquer une fonction sur ton truc.
C'est probablement un cas parfait d'utilisation des scopes enums, tu peux utiliser le type que tu veux en backend, et ca caste pas en int (ou le type qui back l'enum) implicitement pour eviter toutes les typos classiques des enums.
Et tu va augmenter le type avec des fonctions externes qui te permettre de controller les relations entre les cartes dans different contextes que tu sera forcer a ecrire de toute facon.
Je ne vois pas bien l'interet de mettre Carte dans un objet...
Bah j'ai expliqué l'intérêt, c'est plus clair. Et surtout ça a aucun inconvénient, ton objet contient juste ce qui identifie ta carte, il a pas besoin de contenir de logique.
Genre au final au lieu de créer ton objet en faisant char carte = 2 qui a aucun sens ou char carte = DEUX_COEUR qui est certes plus lisible mais pas plus sûr tu fais Carte carte{2, Type::trefle} et tu peux avoir une exception de levée si la carte contient des données invalides, donc c'est lisible et sécurisé.
Après pour l'utilisation c'est plus simple parce que tu peux facilement accéder à la valeur ou type individuellement sans faire de modulo sur un nombre.