Le fait que ton role soit abstrait ou juste héritable n'est pas le probleme ici. Il faut comprendre la problématique d'héritage, et ce que ça entraine sur le niveau "copiabilité" d'un type, si il manipulé comme un type parent. Ici, en paramètre de ta fonction, tu as un objet de type Role, donc tu as une copie en entrant dans la fonction, le type copié sera de type Role, sans les potentiels variation des types enfant (tu as très certainement pareil au niveau de m_role).
Tu peut passer ton Role par référence, et l'assigner sur un pointeur interne (en faisant attention que ton role ai une durée de vie au moins égal a ton player). Tu peu aussi utiliser un unique_ptr si tu veut que ton Player ai la responsabilité du Role qu'il possède. Donc, vu que ton type est héritable, il ne doit pas pouvoir être copiable, donc tu peut le rentre non copiable.
Je t'invite a aller mieux t'informer au niveau des héritages, et des sémantiques (entités/valeur) de class.
Ici, en paramètre de ta fonction, tu as un objet de type Role, donc tu as une copie en entrant dans la fonction, le type copié sera de type Role, sans les potentiels variation des types enfant (tu as très certainement pareil au niveau de m_role).
Ca signifie que si j'appelle une fonction de m_role, j'aurai pas la version overridée ?! Mais c'est sacrément embettant ca ! :/
Et au dela de ca du coup j'essaie de faire comme tu as dit :
Pour le coup du make_unique, regarde comment ça marche, tu as ici une copie a sa construction. Il faut que ton Role soit unique dés le départ, et tu le move dans ta class (tu as plus d'infos la dessus dans le tuto plus haut). Pour la ref, ta copie est ailleurs.
"Tout devrait être rendu aussi simple que possible, mais pas plus." A.Einstein
Quand tu fais une hiérarchie d'objets polymorphiques, tu dois passer tes objets par référence sur la classe de base (ca dit au comilateur : je référence un objet, qui est soit le type de base, soit le type dérivé).
Si tu passes des objets par copie (bêtise), tu dis au compilateur : prend mon objet dérivé, et convertit le, de manière implicite, dans son type de base, puis passe le type de base. Tu ne veux généralement JAMAIS faire ca. Pour te prévenir de ce genre de bêtises, penses a mettre les constructeurs du type de base en protected.
Ensuite va lire la doc de std::unique_ptr, je pense aussi qu'il y a un moment ou tu voudra soit faire voyager des objets dérivés, soit mettre des objets hétérogènes héritants d'une même classe de base dans un container, et c'est la solution à retenir.
Mince ! j'ai pas répondu depuis un certain temps, désolé, je serai fouetté au sang pour la peine !
Merci pour les astuces du constructeur en protected, c'est une bonne idée. J'ai cherché a utiliser l'unique_ptr, mais j'ai pas vraiment réussi. Du coup, je me suis que dans le doute, je pouvais tenter d'utiliser un passage d'un shared_ptr, et puis la, mystere et boule de gomme, ca fonctionne x)
Après vérif et test, plusieurs joueurs peuvent avoir le meme role, par conséquent, l'unique_ptr est pas adapté, puisqu'il appartient a un joueur unique. Du coup, le shared fait mieux le taf. Pour ce qui est de ma fonction setter, je vois pas trop comment modifier ca, parce que je crée mes joueurs bien avant de leur donner un role.
Alors je pourrais trimbaler toutes les caractéristiques d'un joueur dans différents tableaux pendant 10 ans, jusqu'au moment de savoir quel role je vais lui assigner, mais je suis pas sur que ca soit beaucoup plus propre
En fait, tu utilises une classe virtuelle pure comme enumeration...
Sauf si le rôle est simplement ce qu'on appelle une stratégie, auquel cas, c'est bien plus malin que ce qui est présenté typiquement dans le tutoriel d'OC.
BorisD a écrit:
Je te conseilles cette solution, qui est plus propre
Pas vraiment non, encore une fois le setter c'est le truc à ne pas faire.
En fait, tu utilises une classe virtuelle pure comme enumeration...
Sauf si le rôle est simplement ce qu'on appelle une stratégie, auquel cas, c'est bien plus malin que ce qui est présenté typiquement dans le tutoriel d'OC.
BorisD a écrit:
Je te conseilles cette solution, qui est plus propre
Pas vraiment non, encore une fois le setter c'est le truc à ne pas faire.
Je ne comprend pas pourquoi tu dis que c'est moins propre, peux tu expliquer un peu plus? Et, cette strategie, c'est plus malin car elle contient des methodes virtuelles utilisables directement plutot que soumises a une condition, ou des trucs comme ca?
Le fait est que, généralement un "setXXX" tout simple ne représente aucun service "digne de ce nom" que l'on est en droit d'attendre de la part d'une classe.
Dans le cas présent, le joueur a d'office un rôle lorsqu'il est créé. Il faut donc faire en sorte que ce rôle "de départ" lui soit donné à la création.
Par la suite, tu peux vouloir modifier le rôle d'un joueur, et, le cas échéant, faire des vérifications permettant de s'assurer que le role que tu donne au joueur est en adéquation avec son rôle précédant.
Or, le nom d'une fonction détermine son utilisation, sa raison d'être. Si tu crées une fonction setRole, tu dit implicitement que tu modifie le rôle du joueur, sans rien faire de plus, et donc, sans qu'il n'y ait la moindre vérification possible. Mais en plus, tu donnes à l'utilisateur de la classe l'occasion de faire "n'importe quoi".
Or, comme disait l'autre:
Il faut faire en sorte que l'utilisation correcte de la classe soit facile et qu'il soit difficile d'utiliser la classe de manière incorrecte
Du coup, une fonction changeRole (si elle a une raison d'être) correspondra bien mieux à un service que l'on est en droit d'attendre de notre classe.
C'est d'autant plus vrai que l'on pourrait parfaitement du coup lui donner un paramètre qui ne serait pas déjà du type Role (mais une énumération, par exemple, ou n'importe quelle information qui pourrait lui permettre de créer le rôle "à la demande"), et que cela permettrait donc "centraliser" l'endroit où les rôles sont créés à un seul endroit dans le code, plutôt que d'avoir 36 disséminés un peu partout dans le code, qu'il faudra corriger à chaque fois que tu voudras rajouter un nouveau rôle potentiel
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
@koala01 merci pour ton éclaircissement, ca m'aide beaucoup mieux a comprendre pourquoi un setter c'est "mal" Du coup, vaudrait mieux faire une classe changeRole au lieu d'un set. Ce que j'ai pas bien compris c'est si je dois faire quelque chose comme un assert dans la fonction, ou si simplement je li fait renvoyer un booléen (0 pour ok et 1 pour pas ok).
Du coup, ce que vous conseillez de faire, c'est une sorte de factory à l'intérieur d'une fonction changeRole, qui utilise une énumération au lieu de roles?
@koala01 merci pour ton éclaircissement, ca m'aide beaucoup mieux a comprendre pourquoi un setter c'est "mal" Du coup, vaudrait mieux faire une classe changeRole au lieu d'un set. Ce que j'ai pas bien compris c'est si je dois faire quelque chose comme un assert dans la fonction, ou si simplement je li fait renvoyer un booléen (0 pour ok et 1 pour pas ok).
Du coup, ce que vous conseillez de faire, c'est une sorte de factory à l'intérieur d'une fonction changeRole, qui utilise une énumération au lieu de roles?
Ben, je sais pas, car
Ksass`Peuk a écrit:
BorisD a écrit:
En fait, tu utilises une classe virtuelle pure comme enumeration...
Sauf si le rôle est simplement ce qu'on appelle une stratégie, auquel cas, c'est bien plus malin que ce qui est présenté typiquement dans le tutoriel d'OC.
Du coup, si j'ai bien compris, ce serait mal d'utiliser des enumerations (meme fortement typees), meme si Ksass `Peuk ne m'a pas repondu pourquoi...
D'ailleurs: prefere les exceptions aux asserts, ca permet de corriger le tire en cas d'erreur.
Si on décide d'utiliser les rôles comme des stratégies, à savoir une fonctionnalité variante en fonction du rôle, effectivement on y gagne. Parce qu'avec l'énumération, on se retrouve juste à avoir du code du genre :
switch(role){
case TRUC: ...
case MACHIN: ...
case BIDULE: ...
}
Ce qui n'est pas pratique : si on veut rajouter un rôle on va devoir modifier tous les switchs de ce genre. Alors qu'à l'inverse, le Rôle sous forme d'une stratégie polymorphe nous permet de simplement déléguer l'appel au rôle :
void something(){
my_role.something();
}
Mais on peut faire encore mieux et oublier les choses du genre "J'ai un magicien donc je crées dans mon programme la notion de magicien comme un type". Et se dire simplement qu'un joueur va avoir une liste de capacités, et que le rôle ne fait finalement que déterminer cette liste de capacité. De cette manière, créer une nouveau rôle, c'est juste créer une nouvelle liste de capacité qu'on pourrait par exemple donner en entrée d'une fonction du joueur. On peut même en ajouter/enlever au cas par cas.
BorisD a écrit:
D'ailleurs: prefere les exceptions aux asserts, ca permet de corriger le tire en cas d'erreur.
Soyons sélectifs : quand l'erreur est une erreur de programmation, il n'y a pas de tir à corriger à l'exécution, il faut corriger le code, et on ne veut pas que le développeur puisse y échapper, donc assertion. Si le développeur malgré toute sa bonne volonté ne peut pas contrôle l'erreur (plus de mémoire, connexion qui saute, fichier manquant, ...), alors effectivement, lançons lui une exception qu'il puisse traiter.
- Edité par Ksass`Peuk 20 novembre 2017 à 19:48:17
Je suis désolé, j'ai un peu de mal a comprendre ou tu veux en venir Je me contente de renommer ma fonction setRole en changeRole, et j'ajoute des assert et c'est plié, ou je dois revoir completement mon architecture?
Parce que quand je lis Mais on peut faire encore mieux et oublier les choses du genre "J'ai un magicien donc je crées dans mon programme la notion de magicien comme un type". Et se dire simplement qu'un joueur va avoir une liste de capacités, et que le rôle ne fait finalement que déterminer cette liste de capacité. Ce que je comprends c'est qu'en gros chaque joueur a la base a acces a toutes les fonctions des classes, et que le role est juste la pour "désactiver" certaines fonctions. Si c'est ca, ca me parait pas super opti non?
Je rappelle que je veux simplement un joueur qui peut changer de role a tout moment, et qu'un role a des fonctionnalités qui lui sont propres, et communes.
Ce que je comprends c'est qu'en gros chaque joueur a la base a acces a toutes les fonctions des classes, et que le role est juste la pour "désactiver" certaines fonctions. Si c'est ca, ca me parait pas super opti non?
Le mot "optimisé" n'a rien à faire ici.
Mais sinon, le joueur a accès à mieux. Il a juste une liste de capacités, qui ne sont pas des fonctions membres de la classe mais des objets.
- Edité par Ksass`Peuk 20 novembre 2017 à 20:35:35
ca veut dire que chaque fonction d'action du joueur devrait etre un objet indépendant?! Mais c'est super lourd !
Honnêtement, je suis pas super vieux dans la prog, mais je suis quand meme surpris que tu conseilles de faire une classe par fonction Je comprends tout de travers ou ca permet vraiment d'avoir un code plus maintenable?
ca veut dire que chaque fonction d'action du joueur devrait etre un objet indépendant?! Mais c'est super lourd !
Cela dépend de ce qu'on appelle une fonction d'action. Et c'est quoi le plus lourd une classe avec 12 fonctions de 5 pieds de longs, ou une douzaines de classes qui font approximativement rien ?
Partons juste d'un constat super simple : c'est quoi la différence entre lancer un sort d'attaque qui consomme 12 de manas, à maximum 14 de distance et fait 7 de dégats, et lancer une attaque physique qui fait 8 de dégâts ? Ben c'est juste l'affichage, qui n'a rien à faire dans le code actuel. Parce que le second est en fait une attaque magique à 0 de manas et 0 de distance. Donc peut être qu'on peut donner un nom plus pertinent à ça.
Ce qui peut etre vraiment efficace, ce serait une classe de traits contenant chacune de ces methodes.
class Perso
{
//m_hp : vie restante au perso
short int m_hp;
//m_name : nom du perso
std::string m_name;
//traits dont differents perso peuvent heriter
static struct Traits
{
typedef class WeaponContainer
{
std::unique_ptr<Weapon> m_weapon;
void hit();
}WeaponContainer;
};
};
Ce n'est evidemment qu'une version abregee, mais ca peut etre utile.
-
Edit:
typedef n'est plus beaucoup utilise, mais est remplace par using. N'hesitez pas si vous connaissez autre chose!
@BorisD: En fait non, c'est pas efficace. Si je considère un PNJ brigand, il aura probablement une arme dont il se servira pour détrousser les voyageurs, sauf que c'est pas un perso, c'est un PNJ, de même un tigre qui sera un typiquement un mob mais pourra devenir temporairement ou de façon permanente un allié, ne pourra pas coller à ce design. Si mon perso est magicien, il utilisera des sorts, du coup il lui faudra un SpellContainer plutôt qu'un WeaponContainer, et là encore, on se rend bien compte que ça va rapidement devenir ingérable... Et je ne parle même pas d'un guerrier mage qui pourra avoir des armes et des sorts, là ça devient carrément cauchemardesque...
Pour se sortir de ce merdier, la notion d'objet ne fonctionne clairement pas. Imaginons que mon perso apprivoise un loup, il combattra à mes côtés, quelle est la différence entre un loup de base et ce loup là? Imaginons une autre scène, je tue un brigand, il a une arme bien meilleure que la mienne, que vais je faire? pas besoin d'être grand clerc pour comprendre que je vais vouloir m'emparer de son arme. Comment va tu matérialiser le fait que mon perso s'empare de cette arme, comment va tu matérialiser qu'un tigre se bat à mes côté?
@BorisD: En fait non, c'est pas efficace. Si je considère un PNJ brigand, il aura probablement une arme dont il se servira pour détrousser les voyageurs, sauf que c'est pas un perso, c'est un PNJ, de même un tigre qui sera un typiquement un mob mais pourra devenir temporairement ou de façon permanente un allié, ne pourra pas coller à ce design. Si mon perso est magicien, il utilisera des sorts, du coup il lui faudra un SpellContainer plutôt qu'un WeaponContainer, et là encore, on se rend bien compte que ça va rapidement devenir ingérable... Et je ne parle même pas d'un guerrier mage qui pourra avoir des armes et des sorts, là ça devient carrément cauchemardesque...
Pour se sortir de ce merdier, la notion d'objet ne fonctionne clairement pas. Imaginons que mon perso apprivoise un loup, il combattra à mes côtés, quelle est la différence entre un loup de base et ce loup là? Imaginons une autre scène, je tue un brigand, il a une arme bien meilleure que la mienne, que vais je faire? pas besoin d'être grand clerc pour comprendre que je vais vouloir m'emparer de son arme. Comment va tu matérialiser le fait que mon perso s'empare de cette arme, comment va tu matérialiser qu'un tigre se bat à mes côté?
A ce moment, on peut aussi le faire heriter d'un Perso, d'un WeaponContainer, et d'un SpellContainer. Il est possible, aussi, d'utiliser ces strategies qui font l'objet de cette discussion, notamment, pour gerer les IA, comme les loups apprivoises. Si tu pensais a une meilleure solution, j'aimerais bien la connaitre.
A ce moment, on peut aussi le faire heriter d'un Perso, d'un WeaponContainer, et d'un SpellContainer. Il est possible, aussi, d'utiliser ces strategies qui font l'objet de cette discussion, notamment, pour gerer les IA, comme les loups apprivoises. Si tu pensais a une meilleure solution, j'aimerais bien la connaitre.
Sauf que, d'un point de vue conceptuel, on ne peut pas dire qu'un personnage EST un WeaponContainer ou un SpellContainer. Au mieux, on peut estimer qu'il utiliser de tels éléments, voire qu'il est implémenté en terme de tels éléments, mais surment pas qu'il est une spécialisation de ceux-ci...
De plus, une relation d'héritage poserait de nombreux problèmes en termes d'interface, car, à moins d'utiliser des fonctions dont le nom est particulièrement explicite (comme equipSpell, equipWeapon, buySpell, buyWeapon, ou autre) -- ce qui serait un comble, car on se doute qu'un SpellContainer permet l'utilisation ... d'un sort et qu'un WeaponContainer permet l'utilisation... d'une arme -- un SpellContainer et un Weapon présenteront une interface sensiblement identique: equip, loot, buy, et les autres.
Du coup, si tu fais hériter une classe perso de WeaponContainer et de SpellContainer, tu vas te retrouver avec des conflits et des ambiguités sans nom.
Ce que tu peux éventuellement faire, c'est effectivement, créer une hiérarchie de classe pour les différents container, qui interviendraient dans une implémentation du patron de conception strategie, MAIS, ton personnage devra UTILISER cette stratégie, et non en hériter.
Quoi qu'il en soit, les règles combinatoires font que, pour N roles, tu risques de te retrouver avec N! combinaisons possibles. C'est à dire que tu dois t'attendre à avoir 120 combinaisons possibles pour seulement 5 rôles! Et à ce moment là, tu n'en sortira déjà plus...
Or, il est facile d'avoir cinq roles différents: des classes Wizard, Warrior, Scoot et Tieth t'en font déjà 4(24 combinaisons possibles). Rajoute deux spécialité à cela, et tu te retrouve avec 720 combinaisons possibles!
Bien sur, toutes les combinaisons ne seront pas *** forcément *** mises en oeuvre, mais... tiens tu vraiment à prendre le risque???
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
OK, je me rend. Du coup, qu'est ce que je peux faire? Utiliser ces strategies dont on parle depuis tout a l'heure? Mais du coup, je m'y prend comment? Je les fait heriter d'une classe Capacities pour les stocker dans un std::unique_ptr (que je n'arrive toujours pas a utiliser) ou un simple pointeur si je m'occupe de lui dans le destructeur?
Je les fait heriter d'une classe Capacities pour les stocker dans un std::unique_ptr (que je n'arrive toujours pas a utiliser)
C'est une solution très simple qui marche bien (même si on peut faire encore plus flexible avec des schémas comme l'Entity Component System). Pour unique_ptr, si tu n'es pas plus précis que ça, ça va être compliqué de t'aider.
Je les fait heriter d'une classe Capacities pour les stocker dans un std::unique_ptr (que je n'arrive toujours pas a utiliser)
C'est une solution très simple qui marche bien (même si on peut faire encore plus flexible avec des schémas comme l'Entity Component System). Pour unique_ptr, si tu n'es pas plus précis que ça, ça va être compliqué de t'aider.
L'entity component system, c'est ce que j'expliquais par
J'ai écrit:
A ce moment, on peut aussi le faire heriter d'un Perso, d'un WeaponContainer, et d'un SpellContainer. Il est possible, aussi, d'utiliser ces strategies qui font l'objet de cette discussion, notamment, pour gerer les IA, comme les loups apprivoises. Si tu pensais a une meilleure solution, j'aimerais bien la connaitre.
Je sais que je m'explique mal, mais si je me rapelle plus du nom, on va pas se comprendre. Pour ce qui est des std::unique_ptr, je n'arrive pas a:
1-Y acceder depuis une classe, sous pretexte que je ne peut pas y acceder comme si c'etait un pointreur membre avec
MaClasse obj;
obj.*ptr = 0;
2-Changer de cible en detruisant l'objet.
3-Effectuer un transfert de responsabilites vers un autre pointeur nu/unique.
Je sais que je meriterait qu'on me reponde RTFM, mais bon, moi, l'anglais, ca va bien quand c'est du niveau college.
Tu as une fonction get() qui te renvoie le pointeur, une fonction reset() qui te permet de changer la cible et une fonction release() qui permet d'abandonner la propriété de la cible (au profit d'un autre pointeur intelligent, ca va de soi )
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
× 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.
* Un wrapper C++ pour sqlite * Une alternative a boost units
Posez vos questions ou discutez informatique, sur le Discord NaN | Tuto : Preuve de programmes C
Posez vos questions ou discutez informatique, sur le Discord NaN | Tuto : Preuve de programmes C
Posez vos questions ou discutez informatique, sur le Discord NaN | Tuto : Preuve de programmes C
Posez vos questions ou discutez informatique, sur le Discord NaN | Tuto : Preuve de programmes C
Posez vos questions ou discutez informatique, sur le Discord NaN | Tuto : Preuve de programmes C
Posez vos questions ou discutez informatique, sur le Discord NaN | Tuto : Preuve de programmes C
Posez vos questions ou discutez informatique, sur le Discord NaN | Tuto : Preuve de programmes C