IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

C++ Discussion :

Conception d'une classe : casse-tête


Sujet :

C++

  1. #1
    Membre à l'essai
    Profil pro
    Inscrit en
    Mars 2009
    Messages
    19
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mars 2009
    Messages : 19
    Points : 13
    Points
    13
    Par défaut Conception d'une classe : casse-tête
    Bonjour,

    Je dirais que je me débrouille pas mal en C ainsi qu'en programmation impérative en général, mais j'ai un problème avec la programmation orientée objet.

    Jusqu'à présent je ne faisais que des petits programmes en C++, mais récemment j'ai voulu essayer quelque chose : créer un shoot'em'up en C++.

    J'ai écumé un peu le net à la recherche de méthodes pour concevoir des classes en C++, mais je tombe la plupart du temps sur des articles pour débutant.
    Je suis tombé également sur le "gang of four" mais je pense que cela ne correspond pas trop à mon problème.
    Je tombe aussi sur de nombreux exemples de shared_ptr, d'auto_ptr mais leur exemples sont faits sur-mesure et sont beaucoup trop simples par rapport à ce qu'on peut rencontrer dans un vrai programme.

    Le fait qu'il n'y ait pas "une seule meilleur solution" ou même "le choix entre plusieurs solutions avec chacune leur avantage" me déroute au point que j'ai déjà recommencé plusieurs fois mon code pourtant bien avancé.

    Mon principal problème se situe pour l'instant au niveau de la conception de la classe "GameScreen" (écran de jeu) qui correspond à une situation de jeu.
    Comme j'en ai un peu marre de me casser la tête là dessus et que je ne trouve rien sur le net, je voulais vous demander comment vous l'implémenteriez.

    J'ai déjà les classes "Vessel" (vaisseau) qui dérive de "DisplayElement". Pourquoi cette dérivation ? Car je compte par exemple rajouter "Bomb" ou "Obstacle" qui dériveraient aussi de "DisplayElement".

    La classe GameScreen représente l'écran qui défile et donc beaucoup de créations/destructions d'objets.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class GameScreen {
    public:
     GameScreen();
     ~GameScreen();
     
     ...
     
    private:
     Container _renderingList;
     GUI _gui;
    };
    Il me faut aussi stocker quelque part la cible d'un éventuel missile guidé lancé par le joueur, de préférence dans la classe "GUI".


    Jusque là quelques unes de mes implémentations incluaient :

    - Le passage uniquement par un identifiant, sauf que c'est très lourd à force
    Ma classe "GUI" stockerait alors un ElementID
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class GameScreen {
    public:
     typedef int ElementID;
     
     ElementID createVessel();
     ElementID createObstacle();
     ElementID getElementAtPosition(int x, int y);  // 0 = aucun
     void setElementPosition(ElementID, int x, int y);
     etc.
     
    private:
     std::map<ElementID,DisplayElement*> _renderingList;
     Element _nextElementID; // incrémenté à chaque fois pour distribuer des IDs
    };
    - L'utilisation de shared_ptr pour pouvoir accéder aux éléments de l'extérieur
    Mais si un vaisseau est détruit, le shared_ptr stocké dans GUI va empêcher sa destruction réelle (il ne va plus être affiché à l'écran mais il existera toujours, et la croix rouge que le GUI dessine dessus sera elle toujours affichée)
    Il faudrait un moyen de signaler au GUI que la cible vient d'être détruite, sans pour autant casser l'indépendance que je pense nécessaire entre les différentes classes
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    class GameScreen {
    public:
     std::shared_ptr<DisplayedElement> createVessel();
     std::shared_ptr<DisplayedElement> createObstacle();
     std::shared_ptr<DisplayedElement> getElementAtPosition(int, int);
     
    private:
     std::list<std::shared_ptr<DisplayedElement> > _renderingList;
    };
    Bien que ce problème puisse ici être résolu sans trop de problème (le GameScreen n'a qu'à vérifier si la cible du GUI correspond à un élément lorsqu'il est détruit), cela peut être je pense plus contraignant dans certains cas


    Au delà de l'implémentation de cette classe, je cherche plutôt des guides ou tutoriaux de règles à respecter en C++ pour une bonne gestion de ce genre de problèmes

    En gros je voudrais connaître "les trucs que tout le monde connaît mais que c'est écrit nulle part", comme "les règles d'or à respecter" ou je ne sais quoi

    Merci d'avance

  2. #2
    Membre éclairé

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Juin 2007
    Messages
    373
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    Localisation : Royaume-Uni

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Santé

    Informations forums :
    Inscription : Juin 2007
    Messages : 373
    Points : 764
    Points
    764
    Par défaut
    Pourquoi ne pas utiliser un "bête" pointeur :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    class GameScreen {
    public:
     DisplayElement* createVessel();
     DisplayElement* createObstacle();
     DisplayElement* getElementAtPosition(int x, int y);
     
     void Update()
     {
         // pseudo code :
         DisplayElement* pElem;
         foreach (pElem, _renderingList)
         {
             if (pElem->isOutOfScreen())
             {
                 _renderingList.erase(pElem->getID());
                 delete pElem;
             }
         }
     }
     
    private:
     std::map<ElementID, DisplayElement*> _renderingList;
     Element _nextElementID; // incrémenté à chaque fois pour distribuer des IDs
    };
    Tu gèrerai la destruction des DisplayElement uniquement dans GameScreen (en faisant des "delete" dès que tu n'en as plus besoin).
    Je pense que les pointeurs intelligents sont utiles, mais pas dans tous les cas. Il est souvent plus simple (et plus risqué c'est certain) de passer par le pointeur de base et de jouer avec "delete".

    Pour ce qui est des "règles d'or que tout le monde connais et qui ne sont écrites nul part"... Ben enfait ça s'apprend "sur le tas". Pendant un mois tu vas utiliser telle technique, puis tu vas te rendre compte qu'il y en a une meilleure (soit en réfléchissant tout seul, soit en regardant le code de qqn d'autre).
    Y a pas de solution universelle, juste une solution qui sera adaptée à ton problème. Et c'est là qu'il faut se servir de sa tête, pour en trouver une qui représente un bon compromis entre performances, lourdeur d'utilisation et flexibilité.

  3. #3
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Points : 13 017
    Points
    13 017
    Par défaut
    Salut,
    De ce que je crois comprendre de ton problème, cela relève plus de la conception que du C++ en tant que tel. Il faut que tu modélises tes différentes classes et tes différents objets en leur donnant une responsabilité bien définie. Tout l'exercice est là. Après, le genre de problème que tu poses (j'avoue avoir lu un peu en diagonale ton pb sur ton vaisseau), se résout parce qu'il existe au bon niveau. C'est à dire que la classe qui en a sa charge devra naturellement gérer sa destruction en tant que vaisseau.
    Comment résoudre ton problème ? Je te conseille de te tourner vers des cours de conception et de potasser quelques exemples pour voir comment les responsabilités sont distribués. Ensuite, fais ta propre conception. Elle n'a pas besoin d'être complète dès le début, mais elle doit te permettre d'identifier les différents éléments principaux de ton appli (le moteur de jeu, les éléments, l'affichage, l'IA, ...).

  4. #4
    Membre à l'essai
    Profil pro
    Inscrit en
    Mars 2009
    Messages
    19
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mars 2009
    Messages : 19
    Points : 13
    Points
    13
    Par défaut
    Merci de vos réponses

    Citation Envoyé par Kalith Voir le message
    Pourquoi ne pas utiliser un "bête" pointeur :
    C'est ce que j'ai fait au tout début, mais j'ai abandonné ça
    Pourquoi ? Parce qu'il était possible de stocker autre part le pointeur retourné par "createVessel" pour l'utiliser plus tard, et que ça pourrait créer un bug

    Je crois que mon problème vient du fait que pour moi une classe ne doit pas permettre à un code qui l'utilise de mal l'utiliser

    Par exemple si je vois :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    void createMemoryLeak(); // DO NOT USE
    Ben ça me va pas, il faut absolument que cette fonction ne soit pas accessible même s'il y a un gros avertissement à côté
    L'exemple est un peu exagéré bien sûr mais ça reflète mon état d'esprit


    De ce que je crois comprendre de ton problème, cela relève plus de la conception que du C++ en tant que tel. Il faut que tu modélises tes différentes classes et tes différents objets en leur donnant une responsabilité bien définie. Tout l'exercice est là.
    En fait justement je crois que je n'ai pas de problème au niveau des objets eux-mêmes mais vraiment autour de la notion de propriétaire d'un objet. Par exemple j'ai tout de suite pensé à créer une classe abstraite de ce genre alors que j'ai découvert l'article wikipédia que après

    J'ai dit dans l'OP avoir refait plusieurs fois mon code, ben la deuxième fois (et pour l'instant dernière fois) j'ai commencé par créer tous les fichiers .h avec aucune fonction inline histoire de ne pas avoir les prises de têtes que je suis en train d'avoir, et je n'ai eu aucun mal dans la création des .h (j'ai même fait un mini diagramme de classes UML car j'ai lu en diagonale le tuto du site)



    Enfin tout ça pour dire que je suis à la recherche de 'règles de codage', comme par exemple "quand une fonction retourne un pointeur il ne faut pas le stocker"

  5. #5
    Membre éprouvé
    Profil pro
    Inscrit en
    Mai 2006
    Messages
    780
    Détails du profil
    Informations personnelles :
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations forums :
    Inscription : Mai 2006
    Messages : 780
    Points : 1 176
    Points
    1 176
    Par défaut
    Je pense que tu devrais d'abord t'entrainer avec quelque chose de plus simple à modéliser, sans interface graphique.

    Ca peut être un moteur de jeu simple, juste pour voir les mécanismes, comment stocker les objets, qui gère leur durée de vie etc.. et voir exactement à quoi serve les shared_ptr et autre. Et aussi à savoir qu'on est pas obligé d'utiliser des pointeurs en C++, il y a les références etc...

  6. #6
    Rédacteur/Modérateur
    Avatar de JolyLoic
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2004
    Messages
    5 463
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Yvelines (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 5 463
    Points : 16 213
    Points
    16 213
    Par défaut
    Citation Envoyé par Tomaka17 Voir le message
    Je crois que mon problème vient du fait que pour moi une classe ne doit pas permettre à un code qui l'utilise de mal l'utiliser
    J'ai peur que tu en demandes un peu trop au C++ en l'occurrence, même si l'objectif est louable. Le mieux que tu pourras obtenir est plutôt "une classe doit inciter le code client à bien l'utiliser, en rendant les bons usages évidents et les mauvais difficiles". Ce que certains disent : "protect against Murphy, but not Machiavelli".

    Pour ton problème, en lisant en diagonale, j'ai 2 remarques. Déjà, j'ai envie de dire que le ciblage d'un missile guidé n'a aucune bonne raison de faire partie du club de ceux qui possèdent (de manière partagée) un vaisseau spatial. Il permet d'y avoir accès, mais n'a pas d'impact sur sa durée de vie. Aussi modéliserais-je ce lien en associant au missile non pas un shared_ptr sur sa cible, mais un weak_ptr.

    Ensuite, tu sembles faire coïncider durée du vie de l'objet représentant un vaisseau et état non détruit du vaisseau dans le monde que tu simules. Peut-être est-ce une bonne chose, mais ce n'est pas évident en soi, et c'est un point qui mérite probablement d'y réfléchir.
    Ma session aux Microsoft TechDays 2013 : Développer en natif avec C++11.
    Celle des Microsoft TechDays 2014 : Bonnes pratiques pour apprivoiser le C++11 avec Visual C++
    Et celle des Microsoft TechDays 2015 : Visual C++ 2015 : voyage à la découverte d'un nouveau monde
    Je donne des formations au C++ en entreprise, n'hésitez pas à me contacter.

  7. #7
    Membre à l'essai
    Profil pro
    Inscrit en
    Mars 2009
    Messages
    19
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mars 2009
    Messages : 19
    Points : 13
    Points
    13
    Par défaut
    Citation Envoyé par JolyLoic Voir le message
    J'ai peur que tu en demandes un peu trop au C++ en l'occurrence, même si l'objectif est louable. Le mieux que tu pourras obtenir est plutôt "une classe doit inciter le code client à bien l'utiliser, en rendant les bons usages évidents et les mauvais difficiles". Ce que certains disent : "protect against Murphy, but not Machiavelli".
    Oui je crois que je vais abandonner cette idée


    Citation Envoyé par JolyLoic Voir le message
    Pour ton problème, en lisant en diagonale, j'ai 2 remarques. Déjà, j'ai envie de dire que le ciblage d'un missile guidé n'a aucune bonne raison de faire partie du club de ceux qui possèdent (de manière partagée) un vaisseau spatial. Il permet d'y avoir accès, mais n'a pas d'impact sur sa durée de vie. Aussi modéliserais-je ce lien en associant au missile non pas un shared_ptr sur sa cible, mais un weak_ptr.
    Je ne connaissais pas weak_ptr, ça a l'air effectivement assez utile pour ce cas là

    En fait je voulais essayer de me passer au maximum de Boost vu que j'ai lu que la libstdc++ peut suffir normalement (et qu'en plus c'est assez lourd à installer), mais j'ai l'impression que c'est vraiment très utile en fait


    Citation Envoyé par JolyLoic Voir le message
    Ensuite, tu sembles faire coïncider durée du vie de l'objet représentant un vaisseau et état non détruit du vaisseau dans le monde que tu simules. Peut-être est-ce une bonne chose, mais ce n'est pas évident en soi, et c'est un point qui mérite probablement d'y réfléchir.
    Oui j'ai eu ce réflexe au début mais je reconnais que ça mérite réflexion
    Par exemple si mes objets 'Vaisseau' contiennent des objets 'Projectile', il faut que l'objet 'Vaisseau' ne soit détruit que quand sa liste de 'Projectile' est vide


    Je suis tombé sur cet article que je n'avais pas encore vu, je vais y jeter un coup d'oeil ce soir

    Merci en tout cas pour ces réponses

  8. #8
    Rédacteur/Modérateur
    Avatar de JolyLoic
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2004
    Messages
    5 463
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Yvelines (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 5 463
    Points : 16 213
    Points
    16 213
    Par défaut
    Il y a aussi cet article : http://loic-joly.developpez.com/tuto...mart-pointers/

    Normalement, si ta bibliothèque standard fournit les shared_ptr, elle fourni aussi les weak_ptrs, l'un ne va pas sans l'autre.
    Ma session aux Microsoft TechDays 2013 : Développer en natif avec C++11.
    Celle des Microsoft TechDays 2014 : Bonnes pratiques pour apprivoiser le C++11 avec Visual C++
    Et celle des Microsoft TechDays 2015 : Visual C++ 2015 : voyage à la découverte d'un nouveau monde
    Je donne des formations au C++ en entreprise, n'hésitez pas à me contacter.

  9. #9
    Membre chevronné
    Avatar de poukill
    Profil pro
    Inscrit en
    Février 2006
    Messages
    2 155
    Détails du profil
    Informations personnelles :
    Âge : 40
    Localisation : France

    Informations forums :
    Inscription : Février 2006
    Messages : 2 155
    Points : 2 107
    Points
    2 107
    Par défaut
    Je ne peux que te conseiller d'utiliser les pointeurs intelligents pour gérer tes ressources (cf les deux articles cités au dessus).
    Pour moi, ils sont indispensables en C++ du fait qu'il n'existe pas de ramasse miettes. C'est une solution élégante pour attribuer des responsabilités de gestion de vie (shared_ptr), ou pas (weak_ptr). Après, à qui faut-il donner des responsabilités, il faut se pencher sur la bonne conception !

  10. #10
    Membre à l'essai
    Profil pro
    Inscrit en
    Mars 2009
    Messages
    19
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mars 2009
    Messages : 19
    Points : 13
    Points
    13
    Par défaut
    Citation Envoyé par poukill Voir le message
    Je ne peux que te conseiller d'utiliser les pointeurs intelligents pour gérer tes ressources (cf les deux articles cités au dessus).
    Pour moi, ils sont indispensables en C++ du fait qu'il n'existe pas de ramasse miettes. C'est une solution élégante pour attribuer des responsabilités de gestion de vie (shared_ptr), ou pas (weak_ptr). Après, à qui faut-il donner des responsabilités, il faut se pencher sur la bonne conception !
    Merci

    C'est ce genre de conseils que j'attendais en fait

    Je vais réécrire certains objets de mon programme avec shared_ptr et weak_ptr

    Vivement le C++0x en tout cas

    (note : j'attends quelques jours pour mettre le tag [résolu] des fois que je tomberais sur un autre problème pendant la réécriture)

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. [PHP 5.2] Conception d'une classe de table de base de données
    Par anaranjado dans le forum Langage
    Réponses: 2
    Dernier message: 19/12/2009, 23h20
  2. Conception d'une classe externe d'outil
    Par zooffy dans le forum ASP.NET
    Réponses: 11
    Dernier message: 20/05/2009, 09h30
  3. Réponses: 0
    Dernier message: 09/11/2008, 14h33
  4. Réponses: 0
    Dernier message: 29/10/2007, 14h28
  5. Conception d'une classe parente
    Par VincentB dans le forum Langage
    Réponses: 9
    Dernier message: 24/06/2003, 17h28

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo