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

Langage C++ Discussion :

Classe abstraite et polymorphisme


Sujet :

Langage C++

  1. #1
    Membre du Club
    Homme Profil pro
    Étudiant
    Inscrit en
    Mars 2017
    Messages
    93
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 25
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Étudiant
    Secteur : Arts - Culture

    Informations forums :
    Inscription : Mars 2017
    Messages : 93
    Points : 60
    Points
    60
    Par défaut 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


    Homme Profil pro
    Network game programmer
    Inscrit en
    Juin 2010
    Messages
    7 115
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : Canada

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 115
    Points : 32 967
    Points
    32 967
    Billets dans le blog
    4
    Par défaut
    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
    Homme Profil pro
    Étudiant
    Inscrit en
    Mars 2017
    Messages
    93
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 25
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Étudiant
    Secteur : Arts - Culture

    Informations forums :
    Inscription : Mars 2017
    Messages : 93
    Points : 60
    Points
    60
    Par défaut
    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 :

    Nom : Capture d’écran de 2020-05-15 21-00-28.png
Affichages : 515
Taille : 36,7 Ko

  4. #4
    Expert éminent sénior
    Homme Profil pro
    Analyste/ Programmeur
    Inscrit en
    Juillet 2013
    Messages
    4 630
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Analyste/ Programmeur

    Informations forums :
    Inscription : Juillet 2013
    Messages : 4 630
    Points : 10 556
    Points
    10 556
    Par défaut
    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


    Homme Profil pro
    Network game programmer
    Inscrit en
    Juin 2010
    Messages
    7 115
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : Canada

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 115
    Points : 32 967
    Points
    32 967
    Billets dans le blog
    4
    Par défaut
    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
    Homme Profil pro
    Étudiant
    Inscrit en
    Mars 2017
    Messages
    93
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 25
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Étudiant
    Secteur : Arts - Culture

    Informations forums :
    Inscription : Mars 2017
    Messages : 93
    Points : 60
    Points
    60
    Par défaut
    Bonjour,

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

    Bonne journée

  7. #7
    Membre habitué
    Homme Profil pro
    Inscrit en
    Février 2013
    Messages
    70
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Canada

    Informations forums :
    Inscription : Février 2013
    Messages : 70
    Points : 146
    Points
    146
    Par défaut
    En mots simples, A est un B est une condition nécessaire mais non-suffisante pour que A puisse être une sous-classe de B. Un Airbus A320 est un avion et la classe qui le représente pourrait être une sous-classe d'avion. Ce même Airbus ne pourrait jamais être une sous-classe de Formule Un Ferrari.

    Il existe aussi un autre relation : est composé de qui n'implique pas l'héritage publique. Un avion est composé d'ailes, d'un gouvernail de direction, d'un gouvernail de profondeur, de volets... La classe avion n'hérite pas de ces objets car cela impliquerait qu'un avion est un aile, est un gouvernail de profondeur...

    Pour fonctionner correctement, le polymorphisme nécessite que la classe de base possède au moins une fonction. Ce pseudocode illustre le polymorphisme

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
     
    Animal *Fido = new Chien();
    std::cout<<Fido->QuelEstLeCri(); //Écrit "Japper";
    delete FIDO;
     
    Animal *Sylvestre = new Chat();
    std::cout<<Sylvestre->QuelEstLeCri(); //Écrit "Miauler";
    delete Sylvestre;

    On accède à à la fonction QuelEstLeCri, qui retourne le cri de l'animal en français, par exemple hennir, avec un pointeur ou une référence de type Animal, mais malgré tout, nous obtenons le bon résultat, soit l'appel de la fonction dans la classe dérivée. Le polymorphisme, c'est cela.

    Dit avec des mots complexes, une sous-classe devrait respecter le principe de substitution de Livskov.

    « Si q ( x) est une propriété démontrable pour tout objet x de type T, alors q ( y ) est vraie pour tout objet y de type S tel que S est un sous-type de T. »

    La plus importante interdiction qui découle de ce principe est qu'une sous-classe ne devrait jamais introduire de nouvelles fonctions qui ne sont pas présentes dans sa super classe, elle ne peut que redéfinir les fonctions virtuelles existantes dans sa super classe. Le pseudo code qui suit est l'exemple typique de violation du principe de substitution de Liskov

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
     
    if (TypeDe(Animal) == Chien)
           Ecrire Japper
    else if (TypeDe(Animal) == Chat)
           Ecrire Miauler
     
    else
          throw TypedAnimalInconnu();
    Un tel code est désastreux par ce qu'il devra être modifié à chaque fois qu'une nouvelle sous-classe d'animal sera créée, tout oubli résultera en une exception.

    Une autre violation typique est l'utilisation d'un cast vers la classe dérivée.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    
    Animal *Sylvestre = new Chat();
    std::cout<<dynamic_cast<Chat*>(Sylvestre)->Miauler(); //Écrit "Miauler";
    
    delete Sylvestre;
    La fonction Miauler ne concernant que les chats, elle est absente de la classe de base Animal. Le programmeur est donc contraint d'utiliser un cast pour appeler cette fonction et formellement, c'est aussi mauvais que le if précédent. Les deux ont le défaut d'introduire une dépendance sur le type concret de la classe.

    Votre hiérarchie de classe n'est pas substituable. Aucun des trois types de cartes n'est substituable, ce qui détruit l'utilité de la hiérarchie de classes.

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

Discussions similaires

  1. Réponses: 2
    Dernier message: 22/08/2015, 13h29
  2. Polymorphisme et interface#classes abstraites
    Par zalalus dans le forum Débuter
    Réponses: 6
    Dernier message: 23/02/2010, 14h58
  3. Polymorphisme, classe abstraite
    Par MABB dans le forum C++
    Réponses: 2
    Dernier message: 30/06/2009, 22h23
  4. polymorphisme, pointeurs et classes abstraites
    Par legend666 dans le forum C++
    Réponses: 10
    Dernier message: 02/11/2005, 16h44
  5. pb constructeurs classes dérivant classe abstraite
    Par Cornell dans le forum Langage
    Réponses: 2
    Dernier message: 10/02/2003, 19h02

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