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

Langage C++ Discussion :

Classe abstraite et polymorphisme


Sujet :

Langage C++

  1. #1
    Membre du Club
    Classe abstraite et polymorphisme
    Bonjour à tous,

    J'essaye de construire l'architecture d'un jeu de carte en C++ (pour bien m'entraîner) et j'ai les classes suivantes :

    Une classe abstraite Card qui ne peut pas être instanciée :

    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    class Card {
    private:
      std::string type;
     
    public:
      Card(std::string type);
      virtual ~Card() = 0;
      std::string getType();
    };


    Une classe abstraite ColorCard qui hérite de Card, elle même ne pouvant pas être instanciée :

    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    class ColorCard : public Card {
    private:
      std::string color;
     
    public:
      ColorCard(std::string type, std::string color);
      virtual ~ColorCard() = 0;
      std::string getColor();
    };


    Et enfin une classe NumberCard qui hérite de ColorCard :

    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    class NumberCard : public ColorCard {
    private:
      int number;
     
    public:
      NumberCard(std::string color, int number);
      int getNumber();
    };


    J'ai une première question : est-ce la bonne façon de déclarer une classe abstraite en mettant un constructeur virtuel = 0? (l'idée est de se rapprocher des classes abstraites en Java).

    Maintenant j'ai une classe Player, qui contient une liste de cartes (sa main) :

    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    class Player {
    private:
      int id;
      std::vector<Card> cards;
     
    public:
      Player(int id);
      void addCard(Card card);
    };


    On peut ajouter une carte grâce à la méthode addCard de Player cependant, le fait d'avoir défini la classe abstraite Card m'empêche d'utiliser le polymorphisme (qui marcherait correctement en Java) et j'ai l'erreur suivante :

    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
    In file included from src/Player.cpp:3:
    src/Player.hpp:16:27: error: cannot declare parameter ‘card’ to be of abstract type ‘Card’
       16 |   void addCard(Card card);
          |                ~~~~~~~~~~~^~~~
    In file included from src/Player.hpp:6,
                     from src/Player.cpp:3:
    src/card/Card.hpp:8:7: note:   because the following virtual functions are pure within ‘Card’:
        8 | class Card {
          |       ^~~~
    src/card/Card.hpp:14:11: note: 	‘virtual Card::~Card()14 |   virtual ~Card() = 0;
          |           ^
    src/Player.cpp:8:33: error: cannot declare parameter ‘card’ to be of abstract type ‘Card’
        8 | void Player::addCard(Card card) { cards.push_back(card); }
          |                      ~~~~~~~~~~~^~~~


    Quelle est la bonne façon de faire?

    Merci d'avance

  2. #2
    Rédacteur/Modérateur

    Non faire du polymorphisme c'est pas juste mettre un destructeur virtuel pur.
    Si tu n'as aucune fonction à y mettre c'est probablement parce que ton approche est mauvaise.
    Et en l’occurrence, ta hiérarchie est mauvaise imo : en quoi une carte peut-être un nombre ou une couleur ?
    Une carte c'est un objet carte, qui possède des attributs de couleur, valeur etc.
    Enfin, la base du polymorphisme c'est d'utiliser des pointeurs et références qui conservent les propriétés du typage dynamique. Pas de créer des copies de partout pour faire du typage statique.
    Pensez à consulter la FAQ ou les cours et tutoriels de la section C++.
    Un peu de programmation réseau ?
    Aucune aide via MP ne sera dispensée. Merci d'utiliser les forums prévus à cet effet.

  3. #3
    Membre du Club
    Bonjour,

    Merci pour votre réponse.

    Dans ce cas comment déclarer une classe purement abstraite? (encore une fois à la manière de Java)

    Pour être plus précis, le jeu de carte en question est le UNO. Dans le UNO il y a donc des cartes nombres de couleur et des cartes "d'effet" de couleur (changer de sens, sauter un tour, +2...).
    Il y a aussi des cartes spéciales (+4, changement de couleur) qui lorsqu'elles sont jouées, possèdent une couleur.

    Je ne pense pas que mon schéma UML soit mauvais, le but est de bien comprendre la structuration des classes en C++ comme je l'aurai fait en Java.

    Voici à quoi ça ressemble :


  4. #4
    Expert éminent
    Citation Envoyé par johhry Voir le message
    Dans ce cas comment déclarer une classe purement abstraite? (encore une fois à la manière de Java)
    Tu n'as pas compris la réponse de @Bousk

    Lorsque tu codes std::vector<Card> cards;, cela veut dire que dans ton tableau, tu as des instances de la classe Card. Mais cette classe est abstraite pure (par son destructeur et non son constructeur comme tu l'as écrit )
    On ne peut pas instancier 1 classe abstraite pure
    D'autant plus qu'avec des instances dans 1 tableau, cela pose le problème de copie profonde d'1 élément et qu'on ne peut pas prendre l'adresse d'1 case (parce que le tableau peut être réalloué et invalider le pointeur)

    Donc std::vector<Card*> cards; et éventuellement std::vector<Card&> cards; mais dans ce cas il faut utiliser std::reference_wrapper (<- et donc le C++ moderne)

    Et sinon ton schéma me semble pas terrible j'ai l'impression que tu confonds l'héritage et la composition/ agrégation. 1 carte c'est soit (1 numéro + 1 couleur) soit (spéciale(règle) + 1 couleur) soit (spéciale(règle)) (<- carte joker noire)
    Donc des booléens pour dire si 1 carte a 1 couleur ou pas, si elle est normale ou pas, et 2-3 énumérations (couleur et règle) + soit 1 entier non signé soit 1 énumération pour le nombre


    Édit : Suite à la réponse de @Bousk et lorsque je parle d'énumération pour les règles, je pense + à faire 2 classes 1 classe Card, avec que des propriétés (couleur, numéro, ...) peut-être sans logique (un DTO, "Data Transfert Object") et 1 autre classe Rule (ou 1 ensemble de fonctions, mais moins flexible) pour déterminer tous les comportements possibles. Et ainsi, lorsqu'on joue (mais peut être pas que dans ce cas), on utilise cette classe Rule (<- le lien entre la classe Card et cette classe est 1 lien de composition/ d'agrégation).
    Mais c'est juste 1 idée sans avoir tous les éléments

  5. #5
    Rédacteur/Modérateur

    Dans le cas du Uno en particulier, une carte a toujours une couleur, ou toutes les couleurs pour les cartes spéciales.
    Donc un début d'architecture pourrait être
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    class Card
    {
      enum class Color { Red, Yellow, Blue, Green, All };
      Color mColor;
     
      Card(Color color);
    };


    Après si on veut partir sur une écriture comme card->Play(); alors je dirais
    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 Card
    {
    ...
      virtual void Play(...) const =0;
    };
     
    class SimpleCard : public Card
    {
    // Une simple carte de valeur : 0,2,...
    };
    class SpecialCard : public Card
    {`
    // Carte spéciale : +4, changement de sens, ...
    };


    Avec du double dispatch pour une fonction CanPlay(const Card&) qui permette de vérifier qu'une carte est jouable ou non (même couleur, valeur, spéciale, ..).
    Pensez à consulter la FAQ ou les cours et tutoriels de la section C++.
    Un peu de programmation réseau ?
    Aucune aide via MP ne sera dispensée. Merci d'utiliser les forums prévus à cet effet.

  6. #6
    Membre du Club
    Bonjour,

    Super, merci pour vos réponses. C'est vrai que c'était pas terrible je vais revoir ça.

    Bonne journée