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 :

héritage constructeur et enum class


Sujet :

C++

  1. #1
    Membre habitué
    héritage constructeur et enum class
    Bonjour,
    Je voudrais pouvoir hériter d'un constructeur d'une classe mère :
    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
     
    using namespace std;
     
    enum class Couleur
     
    { vert, rouge, bleu, orange};
     
    class Forme2
    { 
    	Couleur couleur;
    public :
    	Forme2(Couleur couleur):couleur(couleur)
    	{
     
    	}
    	Couleur getcouleur();
        virtual double calculSurface() = 0;
     
    };

    La classe fille :
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
     
    #pragma once
    #include "Forme2.h"
    class Rectangle2 :
        public Forme2
    {
        int largeur;
        int longueur;

    Je voudrais pouvoir hériter du constructeur de Forme2 :
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    Rectangle2() :Forme2(Couleur couleur) : couleur(couleur)
        {
     
        }

    mais j'ai l'erreur "nom de type non autorisé"
    Si quelqu'un a une idée MERCI

  2. #2
    Expert éminent sénior
    Salut,

    Le but du constructeur d'une classe est de s'assurer que l'instance de la classe qui est créé soit initialisée avec les bonnes valeurs.

    Si ta classe Forme2 a besoin d'une information de type Couleur pour être "correctement initialisée", il faut -- "tout simplement" -- s'assurer que cette information sera transmise au constructeur de la classe Forme2 par le constructeur de la classe dérivée (Rectangle2, dans le cas présent).

    tu as deux solutions pour y arriver:

    soit, tu t'arranges pour que le constructeur de ta classe dérivée fournisse une couleur "par défaut" au constructeur de ta classe Forme2, sous une forme proche de
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    Rectangle2::Rectangle2(): // on implémente le constructeur
        Forme2{Couleur::vert} /* on fait appel au constructeur de Forme2 en 
                               * lui transmettant la couleur vert
                               */
    {
        /* C'est chouette, on n'a plus rien à faire ici */
    }

    Soit tu t'arranges pour que le constructeur de la classe dérivée puisse obtenir la couleur adéquate depuis "ailleurs", sous une forme qui sera alors proche de
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    class Rectangle2:
        public Forme2 
    {
    public:
        Rectangle2(Couleur couleur); /* on indique qu'il faut l'information de couleur
                                      * pour pouvoir construire notre rectangle
                                      */
        /*  ... le reste */
    };

    Et, l'implémentation du constructeur de Rectangle2 ressemblera à quelque chose comme
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    Rectangle2::Rectangle2(Couleur couleur): /* on implémente un constructeur
                                              * prenant une couleur comme paramètre
                                              */
        Forme2{couleur} /* on appelle le constructeur de forme
                         * en lui transmettant la couleur obtenu
                         */

    On pourrait aussi regrouper les deux possibilités en définissant une couleur "par défaut" qui servirait pourtant de paramètre au constructeur de Rectangle2 sous une forme proche de
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class Rectangle2:
        public Forme2 
    {
    public:
        Rectangle2(Couleur couleur = Couleur::rouge); /* on indique qu'il faut l'information de couleur
                                                       * pour pouvoir construire notre rectangle
                                                       * mais on indique que si aucune valeur n'est transmise,
                                                       * c'est le rouge qui sera utilisé
                                                       */
        /*  ... le reste */
    };
    (l'implémentation de ce constructeur restant la même que dans l'exemple précédent )

    Ce qu'il faut savoir, c'est que la classe de base (Forme2) fait réellement partie intégrante de la classe dérivée (Rectangle2), et que, la partie correspondant à la classe de base dans la classe dérivée devra être construite en priorité pour que l'on puisse construire une instance de la classe dérivée.

    Si la partie correspondant à la classe de base n'est pas construite correctement, la classe dérivée ne pourra purement et simplement pas être construite

    PS:
    1- La directive using namespace std; est totalement inutile dans le cas présent, ne serait-ce que parce qu'il n'y a rien dans le code que tu nous montre qui utilise la bibliothèque standard
    2- En fait, il est largement déconseillé d'utiliser la directive using namespace std; ==>voici pourquoi<==.
    3- Etant donné que la fonction calculeSurface n'aura jamais aucune raison de modifier l'état interne de l'instance de classe à partir de laquelle elle sera appelée, tu devrait la déclarer comme étant une fonction membre (virtuelle) constante, sous la forme de virtual double calculeSurface() const = 0; (dans la classe Forme2, ceci)
    4- N'oublie pas que, à cause de =0, la fonction calculeSurface est considérée comme une fonction virtuelle pure, ce qui a pour effet que la classe Forme2 est considérée comme une "classe abstraite", qui ne pourra pas être instanciée en tant que telle. Pour que tu puisse créer une instance de la classe Rectangle2, alors qu'elle hérite de la classe Forme2, il faudra t'assurer de fournir une implémentation correcte de cette fonction dans la classe Rectangle2, autrement cette classe sera aussi considérée comme abstraite
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  3. #3
    Membre habitué
    quote

    Le but du constructeur d'une classe est de s'assurer que l'instance de la classe qui est créé soit initialisée avec les bonnes valeurs.

    Si ta classe Forme2 a besoin d'une information de type Couleur pour être "correctement initialisée", il faut -- "tout simplement" -- s'assurer que cette information sera transmise au constructeur de la classe Forme2 par le constructeur de la classe dérivée (Rectangle2, dans le cas présent).

    Merci pour ta réponse, voilà ce que j'ai fait au niveau de la classe dérivée en rapport au support de formation qui dit :

    Appel à un Constructeur de la Classe Mère :
    ConstrClasseFille(....) : ConstrClasseMère(....)
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    Rectangle2(Couleur2 couleur,double largeur, double longueur):Forme2(couleur),largeur(largeur),longueur(longueur)
        {
        }

    Je retrouve bien ici le constructeur de la classe mère(en gras italique) :
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    Rectangle2(Couleur2 couleur,double largeur, double longueur):Forme2(couleur),largeur(largeur),longueur(longueur)



    4- N'oublie pas que, à cause de =0, la fonction calculeSurface est considérée comme une fonction virtuelle pure, ce qui a pour effet que la classe Forme2 est considérée comme une "classe abstraite", qui ne pourra pas être instanciée en tant que telle. Pour que tu puisse créer une instance de la classe Rectangle2, alors qu'elle hérite de la classe Forme2, il faudra t'assurer de fournir une implémentation correcte de cette fonction dans la classe Rectangle2, autrement cette classe sera aussi considérée comme abstraite
    Oui en effet j'ai eu le problème j'ai redéfini la méthode dans Rectangle2 :
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    double Rectangle2::calculSurface()
     
    {
        return largeur * longueur;
    }

    Merci à +

  4. #4
    Expert éminent
    C'est ta syntaxe qui n'est pas bonne

    @koala01 a oublié le terme technique : "liste d'initialisation"

    En gros et pour simplifier , lorsque tu appelles 1 constructeur, il faut que lorsque tu arrives à la 1ière ligne du constructeur, que ton objet soit initialisé (et par défaut ces membres seront initialisés à 0)
    Donc il y a cette liste qui commence par : après la définition du constructeur et se termine par l'accolade ouvrante, et avec laquelle tu vas dire quels membres seront initialisés avec quelle valeur.
    On peut initialiser des membres avec la syntaxe parenthèse member(value) et appeler 1 constructeur de la classe mère base_const(params).

    Et c'est pour cette raison que l'initialisation dans le corps du constructeur est redondante

  5. #5
    Membre habitué
    Ce message n'a pas pu être affiché car il comporte des erreurs.

  6. #6
    Expert éminent
    Code C++ :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
    // ...
    
    // enum class Couleur { vert, rouge, bleu, orange};
    
    class Rectangle2 : public Forme2
    {
    public :
    
        Rectangle2(Couleur couleur,
                   double  input_largeur,
                   double  input_longueur) : Couleur2 Forme2(couleur), largeur(input_largeur), longueur(input_longueur) {
    
    //      ...
        }
    
    // ...
    
    
    private :
    
        int largeur;
        int longueur;
    };


    Édit : Lire la page de cppreference.com en anglais, Constructors and member initializer lists pour + d'informations
    Par exemple, on peut appeler 1 autre constructeur de la classe et on peut utiliser les accolades au lieu des parenthèses depuis le C++ moderne.

  7. #7
    Membre habitué
    Citation Envoyé par foetus Voir le message
    Code C++ :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
    // ...
    
    // enum class Couleur { vert, rouge, bleu, orange};
    
    class Rectangle2 : public Forme2
    {
    public :
    
        Rectangle2(Couleur couleur,
                   double  input_largeur,
                   double  input_longueur) : Couleur2 Forme2(couleur), largeur(input_largeur), longueur(input_longueur) {
    
    //      ...
        }
    
    // ...
    
    
    private :
    
        int largeur;
        int longueur;
    };


    Bsr merci pour ta réponse j'ai essayé ca en rapport à ce que tu m'as proposé :
    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
     
    #pragma once
    #include "Forme2.h"
    class Rectangle2 : public Forme2  
    {
    private:
     
        double largeur;
        double longueur;
    public:
    Rectangle2(Couleur2 couleur,
            double  input_largeur,
            double  input_longueur) :  Forme2(couleur), largeur(input_largeur), longueur(input_longueur)
        {
        }
        double calculSurface();
     
    };

    Ca fonctionne mais ce que j'avais écrit niveau constructeur fonctionnait aussi :
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
     Rectangle2(Couleur2 couleur,double largeur, double longueur):Forme2(couleur),largeur(largeur),longueur(longueur)
        {
        }

    tu dis :

    Rectangle2(Couleur couleur,
    double input_largeur,
    double input_longueur) : Couleur2 Forme2(couleur), largeur(input_largeur), longueur(input_longueur) {

    // ... ici

    D'après ton explication et si j'ai bien compris
    appeler 1 constructeur de la classe mère base_const(params).
    je devrais ajouter ici l'appel au constructeur de la classe mère mais comment faire et ça sert à quoi puisque ça marche comme ca ?
    Encore merci

  8. #8
    Expert confirmé
    Bonjour,

    Le dernier code que tu présentes est bien le bon.
    Dans le zone que tu appelles ici, c'est le corps du constructeur. A cet instant, l'objet (et donc forcément aussi sa base) a déjà été construit. Donc sa base et ses membres ont été créés et initialisés, tout se passe dans la liste d'initialisation.
    Pour revenir au corps du constructeur, celui qui commence à l'ouverture de l'accolade, il est là pour effectuer des compléments et ne devrait pas remodifier les membres ou la base. Sinon c'est idiot et non optimal. En aucun cas tu ne peux créer ta base au point ici.
    Contre exemple :
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
       Rectangle2(Couleur couleur, double  in_largeur, double  in_longueur)
             : Forme2(vert) {
          setcouleur(couleur);   // trop tard pour construire la base, il y faudrait une fonction pour changer de couleur
          largeur = in_largeur;  // aurait dû être initialisé avant le début
          hauteur = in_hauteur;  // aurait dû être initialisé avant le début
       }
    Ça marche, mais on a initialisé 2 fois la base et les dimensions ont été initialisées trop tardivement.
    La solution "propre" est bien :
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
       Rectangle2(Couleur couleur, double  in_largeur, double  in_longueur)
             : Forme2(couleur), largeur(in_largeur), longueur(in_largeur) {
       }

  9. #9
    Membre habitué
    Citation Envoyé par dalfab Voir le message
    Bonjour,

    Le dernier code que tu présentes est bien le bon.

    La solution "propre" est bien :
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
       Rectangle2(Couleur couleur, double  in_largeur, double  in_longueur)
             : Forme2(couleur), largeur(in_largeur), longueur(in_largeur) {
       }
    Merci pour ta confirmation, j'ai une autre question sur le même projet j'ai cette classe Forme2 toujours :
    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
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
     
    #pragma once
    enum class Couleur2
    {
    	VERT,
    	BLEU,
    	ROUGE,
    	ORANGE
    };
     
    class Forme2
    { 
    	Couleur2 couleur;
    	double longueur;
    	double largeur;
    public:
    	Forme2();
     
    	Forme2(Couleur2 couleur):couleur(couleur),longueur(0),largeur(0)
    	{
     
    	}
    	Forme2(Couleur2 couleur,double longueur,double largeur) :couleur(couleur),longueur(longueur),largeur(largeur)
    	{
     
    	}
    	Couleur2 getCouleur() const
    	{
    		return couleur;
    	}
        double calculSurface();
     
     
    };

    avec donc 3 constructeurs dont un par défaut :
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
     
    Forme2();

    Je le décris dans le .cpp :
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    Forme2::Forme2()
    {
    }
    et là j'ai l'avertissement : C26495 "les variables ne sont pas initialisées" et là je n'y arrive pas si tu as une idée MERCI

  10. #10
    Rédacteur/Modérateur

    Tu ne comprends pas le message d'erreur pourtant écrit dans un français très clair ?
    Tu as des variables membres, quelles valeurs penses-tu qu'elles ont quand le constructeur par défaut est utilisé ?
    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.

  11. #11
    Membre habitué
    Citation Envoyé par Bousk Voir le message
    Tu ne comprends pas le message d'erreur pourtant écrit dans un français très clair ?
    Tu as des variables membres, quelles valeurs penses-tu qu'elles ont quand le constructeur par défaut est utilisé ?
    *************************************************************
    Merci pour ta réponse figure toi que j'avais déjà essayé ça :
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    Forme2::Forme2()
    {
    	couleur=Couleur2::BLEU;
    	longueur=0;
    	largeur=0;
    }

    Mais ça marche pas pour couleur d'où ma demande...
    Si tu as une idée pour couleur Merci

  12. #12
    Expert éminent sénior
    Citation Envoyé par xeron33 Voir le message
    *************************************************************
    Merci pour ta réponse figure toi que j'avais déjà essayé ça :
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    Forme2::Forme2()
    {
    	couleur=Couleur2::BLEU;
    	longueur=0;
    	largeur=0;
    }

    Mais ça marche pas pour couleur d'où ma demande...
    Si tu as une idée pour couleur Merci
    Et c'est normale que ca ne fonctionne pas, car longueur et largeur ne font pas partie de Forme2, mais de Rectangle2

    Tu vas avoir énormément de mal à initialiser, au niveau de la classe de base (Forme2, ici) des données qui ne commencent à exister qu'au niveau de la classe dérivée (Rectangle2, dans le cas présent)...

    Tout comme tu auras énormément de mal à faire l'inverse, à savoir, initialiser une variable qui est déclarée dans la classe de base (Forme2, toujours) au niveau du constructeur de la classe dérivée (Rectangle2, toujours), sans passer par la liste d'initialisation, en s'assurant d'appeler le constructeur "qui va bien" pour la partie correspondant à la classe de base
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  13. #13
    Membre habitué
    Citation Envoyé par koala01 Voir le message
    Et c'est normale que ca ne fonctionne pas, car longueur et largeur ne font pas partie de Forme2, mais de Rectangle2

    Tu vas avoir énormément de mal à initialiser, au niveau de la classe de base (Forme2, ici) des données qui ne commencent à exister qu'au niveau de la classe dérivée (Rectangle2, dans le cas présent)...

    Tout comme tu auras énormément de mal à faire l'inverse, à savoir, initialiser une variable qui est déclarée dans la classe de base (Forme2, toujours) au niveau du constructeur de la classe dérivée (Rectangle2, toujours), sans passer par la liste d'initialisation, en s'assurant d'appeler le constructeur "qui va bien" pour la partie correspondant à la classe de base
    **********************************************************
    Merci pour ta réponse, j'ai revu le problème différement et je considère que Rectangle2 est une Forme2 (une sorte de Forme2) d'ou j'ai décidé de créer une agrégation soit :
    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
     
    #pragma once
    #include "Forme2.h"
    class Rectangle2 
    {
         Forme2* owner;
    public:
     Rectangle2() :owner(0)
        {
     
        }
        Rectangle2(Forme2& owner) :owner(&owner)
        {
     
        }
        Forme2* getForme2()
        {
            return Rectangle2::owner;
        }
    };
    avec deux constructeurs et une méthode qui renvoit l'objet Rectangle2
    Dans la classe Forme2 j'ai rajouté deux méthodes :
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
     
    friend ostream& operator<<(ostream& st, Couleur2& p);
    	friend ostream& operator<<(ostream& st, Forme2& p);

    qui redéfinissent l'opérateur "<<" de sortie de flux permettant d'afficher la EnumClass Couleur2 et la classe Forme2 :
    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
     
    ostream& operator<<(ostream& st, Couleur2& p1)
    {
    	switch (p1) {
    	case Couleur2::BLEU: st  << "BLEU"; break;
    	case Couleur2::VERT: st << "VERT"; break;
    	case Couleur2::ROUGE: st << "ROUGE"; break;
    	case Couleur2::ORANGE: st << "ORANGE"; break;
    	}
    	return st;
    }
     
    ostream& operator<<(ostream& st, Forme2& p)
    {
    	return st << p.couleur <<" "<< p.longueur << " et " << p.largeur  <<" " <<  endl;
    }

    Maintenant dans le Main je créer une Forme2 :
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
     
    Forme2 rectangle(Couleur2::VERT, 10.00, 70.00);

    puis un Rectangle2 :
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
     
    Rectangle2 rec(rectangle); //Forme2* owner;

    et enfin j'affiche la valeur du pointeur getForme2() :
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
     
    cout << "rec : " << *rec.getForme2() << endl;
    et là ça me semble moins redondant car ça m'évite d'avoir deux classes Forme2 et Rectangle2 avec les mêmes attributs longueur et largeur
    voilà si tu ou d'autres ont des remarques ne pas hésiter
    Merci encore
    A+

  14. #14
    Expert éminent sénior
    Citation Envoyé par xeron33 Voir le message
    **********************************************************
    Merci pour ta réponse, j'ai revu le problème différement et je considère que Rectangle2 est une Forme2 (une sorte de Forme2) d'ou j'ai décidé de créer une agrégation
    C'est une idée intéressante, car, en effet, un Rectangle peut n'être "qu'une sorte" de Forme.

    A ceci près (outre les problèmes de ton code, bien sur) que, du coup, tu perd l'un des atouts principaux de l'héritage qui est la substituabilité: la possibilité de transmettre n'importe quel Rectangle comme argument à une fonction s'attendant à recevoir une Forme comme paramètre, tout en t'assurant que le résultat soit cohérent et valide

    En outre, tu perd également un autre atout tout aussi essentiel de l'héritage qui est le polymorphisme (d'inclusion): la possibilité de voir une fonction membre (mettons: double Forme2::superficie() const )issue de la classe mère (ta classe Frome2, ici) adapter -- à l'exécution -- son comportement au type réel de la donnée à parir duquel elle est effectivement appelée (ex: monRect.superficie(); // avec monRect de type Rectangle2)

    On dit souvent que l'héritage public est la relation la plus forte qui puisse exister entre deux classes / deux structures, et qu'il vaut donc larmement mieux de préférer la composition / l'agrégation lorsque c'est possible.

    Cette affirmation est tout à fait juste!

    Cependant, si tu veux effectivement pouvoir profiter de la substituabilité (par exemple, en faisant tenir plusieurs forme comme un Rectangle2 et un Carre ensemble dans une collection de ... Forme2), tu n'auras sans doute pas d'autre choix que de passer par l'héritage

    Pour résoudre ton problème de base, je vais donc aller un peu plus loin dans ma réponse:
    1- Tu devrais prendre l'habitude d'utiliser les listes d'initialisations dans le constructeur de tes classes.

    Si on fait simple, et que l'on a une énumération pour les couleurs qui ressemble à
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    enum class  Color{
        blanc,
        rouge,
        vert,
        bleu,
        violet,
        noir
    };
    nous pourrons créer une classe Forme qui utilise une couleur fournie en paramètre du constructeur d'une manière ressemblant à
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class Forme{
    public:
        /* construit une forme avec la couleur indiquée */
        Forme(Color color): m_color{color}{
        }
     
        Color color() const{return m_color;}
        /* d'autres choses à venir */
    private:
        Color m_color;
    };

    2- Les classes qui interviennent dans une hiérarchie de classes (comme nos classes Forme et Rectangle à venir) ont, typiquement, sémantique d'entité, si bien qu'on va éviter à tout prix qu'une copie d'une forme ne puisse être créée.

    Depuis C++11, il est possible d'indiquer explicitement au compilateur que le constructeur de copie et l'opérateur d'affectation n'existent pas, ce qui facilite la vie.
    Il ne faut donc pas oublier de déclarer le destructeur de Forme comme étant virtuel (s'il est placé en accessibilité public) de manière à ... Pouvoir détruire n'importe quelle forme valide même si celle-ci est considérée comme ... une forme "sans autre distinction".

    Améliorons donc notre classe Forme pour la faire ressembler à
    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
    class Forme{
    public:
        /* construit une forme avec la couleur indiquée */
        Forme(Color color): m_color{color}{
        }
        /* Pour éviter les copie, on supprime le 
         * constructeur de copie et l'opérateur d'affectation
         */
        Forme(Forme const &) = delete;
        Forme & operator=(Forme const &) = delete;
        /* et on rend le destructeur virtuel.
         * Comme on ne veut pas "se faire c..." avec
         * on dit néanmoins que son comportement sera celui
         * "par défaut" que le compilateur met en place
         */
        virtual ~Forme() = default;
        Color color() const{return m_color;}
        /* d'autres choses à venir */
    private:
        Color m_color;
    };


    3- Ce que beaucoup de débutants ont du mal à comprendre, c'est que lorsqu'on décide d'utiliser le paradigme orienté objets, on ne doit pas se contenter d'envisager nos classes comme de simples "agrégats de données". On doit les envisager comme des "fournisseurs de services" (auxquels l'utilisateur de la classe, qui n'est pas ** forcément ** celui qui l'a développée, est en droit de croire qu'il aura accès)

    Les mutateurs (les fonction getXXX) comme la fonction membre Color color() const (qui aurait pu s'appeler Color getColor() const, dans le cas présent) peuvent fournir certains de ces services, mais, pour le reste, la classe de base (notre classe Forme) ne doit fournir que les services que l'on retrouve obligatoirement dans l'ensemble des classes dérivées.

    Par exemple, l'utilisateur peut s'attendre à être en mesure de demander la superficie de n'importe quelle forme qu'il aurait sous la main, que ce soit un Rectangle, un Cercle ou un Triangle.

    Il serait donc "logique" que ce service soit fourni par la classe Forme.

    A ceci près que l'on ne calcule pas la superficie d'un triangle, d'un rectangle ou d'un cercle de la même manière. Il faut donc prévenir le compilateur que le comportement de la fonction (que j'appellerai superficie() par facilité) devra s'adapter au type réel de la forme utilisée en la déclarant comme virtuelle.

    De plus, il n'y a aucun moyen de calculer la superficie d'une forme dont on ignore tout (on ne sait, pour l'instant, déjà pas s'il s'agit d'un rectangle ou d'un cercle ).

    Or, le compilateur a horreur du vide et, si on déclare une fonction membre (superficie() dans notre cas) dans notre classe Forme, il voudra impérativement avoir l'implémentation de cette fonction, ce que l'on n'est absolument pas en mesure de lui donner (re ).

    Nous devons donc déclarer cette fonction comme étant "virtuelle pure", avec pour conséquence le fait que notre classe Forme devient du coup ce que l'on appelle une "classe abstraite".

    C'est à dire une classe qui ne pourra pas être instanciée en tant que telle, justement, parce qu'elle ne propose aucun comportement valide pour un des services qu'elle expose (re re ).

    Il faut d'ailleurs prendre en compte le fait que, pour pouvoir créer une instance d'une classe dérivée qui hérite d'une classe abstraite, le comportement de toutes les fonctions virtuelles pures devra être valablement défini. Car tout classe dérivée pour laquelle ce ne serait pas le cas sera elle aussi considérée comme une classe abstraite.

    Voici donc à quoi ressemblera notre classe Forme en ajoutant un service permettant à l'utilisateur d'en obtenir la superficie:
    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
    class Forme{
    public:
        /* construit une forme avec la couleur indiquée */
        Forme(Color color): m_color{color}{
        }
        Forme(Forme const &) = delete;
        Forme & operator=(Forme const &) = delete;
        /* et on rend le destructeur virtuel.
        virtual ~Forme() = default;
        Color color() const{return m_color;}
        /* virtual parce que le comportement doit s'adapter au type réel
         * = 0 indique que l'on ne fournit pas le comportement pour l'instant
         * (que c'est une fonction "virtuelle pure")
         */
        virtual double superficie() const = 0; 
        /* d'autres choses à venir */
    private:
        Color m_color;
    };


    4- On a l'habitude de définir l'héritage public comme "une relation EST-UN (IS-A en anglais)".

    Ce n'est pas faux, car un Rectangle ... EST-UN(e) Forme.

    Cependant, je préférerais sans doute qu'on le définisse comme "une relation EST-SUBSTITUABLE-A-UN (ou si tu préfère PEUT-PASSER-POUR-ETRE-UN)" car l'héritage public est soumis au principe de substitution de Liskov (ou LSP pour Liskov Substitution Principle, en anglais) qui doit impérativement être respecté

    Or, nous avons tous appris qu'un carré "est un rectangle dont tous les cotés sont égaux" et, à cause de cette définition de l'héritage comme étant une relation EST-UN, nombreux sont ceux qui croient qu'une classe Carre peut légitimement hériter d'une classe Rectangle.

    Or, si l'on fait hériter la classe Carre de la classe Rectangle, nous ne respectons absolument pas le LSP,ce qui se transformera très rapidement en très sérieux problème de conception

    Si l'on définissait l'héritage public comme étant "une relation EST-SUBSTITUABLE-A-UN ( PEUT-PASSER-POUR-ETRE-UN)" on verrait tout de suite que la classe Carre ne peut pas se faire passer pour un Rectangle (à cause du LSP) même si elle peut se faire passer pour une Forme

    Mais bon, le carré étant un cas très particulier, intéressons nous à des cas plus simple : la notion de rectangle et celle de cercle.

    Les rectangles et les cercles peuvent parfaitement être considérés comme étant ... des formes évoluées car

    1. Ils disposeront tous les deux de la notion de couleur
    2. ils sont tous les deux capables de nous donner une réponse cohérente à la question "quelle est ta superficie"

    Ce sont donc des candidats idéaux pour tester l'héritage public. Hé bien, qu'attendons nous pour le faire

    En fait, ce que l'on attend, c'est simplement de savoir comment s'y prendre. Commençons donc par réfléchir à ce qu'il nous faut pour définir un rectangle.

    Un rectangle est un quadrilatère dont les cotés sont égaux & à angles droit deux à deux. Nous pouvons appeler le coté (la paire de cotés) le(s) plus long(s) "longueur" et le coté (la paire de cotés) le(s) plus court(s) "largeur".

    De son coté, l'utilisateur s'attendra "logiquement" à être en mesure de demander à un rectangle de lui indiquer les valeur associées respectivement à sa longueur et à sa largeur.

    Pour créer un rectangle nous devons donc fournir une valeur pour la longueur et une autre pour la largeur en nous assurant que la valeur correspondant à la longueur est supérieure (ou égale) à celle de la largeur.

    Et, pour calculer la superficie d'un rectangle, il suffit de renvoyer le produit de la longueur et de la largeur.

    Ah, j'allais oublier... Comme notre rectangle est une forme, il faut aussi que nous indiquions sa couleur au moment de la création de notre rectangle et ce, même si nous pouvons considérer que tous nos rectangles sont rouges par défaut (par exemple)

    Cela nous donnera quelque chose comme
    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
    25
    26
    27
    class Rectangle: public Forme{
    public:
        Rectangle(double longueur, double largeur, Color color=Collor::rouge):
            /* il faut d'abord construire la composante Forme */
            Forme{color},
            /* puis la composante "Rectangle" (longueur + largeur)
             */
            m_longueur{longueur}, m_largeur{largeur}{
            /* on peut s'assurer que m_longueur >= m_largeur */
            assert(m_longueur >= m_largeur && "la longueur devrait être superieure ou egale à la largeur"):
        }
        /* les deux "nouveau services", propres au rectangle */
        double longueur() const{return m_longueur;}
        double largeur()const{return m_largeur;}
        /* et le calcul de la superficie, sinon Rectangle sera considéré
         * comme étant une classe abstraite
         * override force le compilateur à s'assurer qu'il existe bel et bien
         * une fonction virtuelle portant le même nom et demandant les mêmes
         * paramètres dans la classe de base
         */
        double superficie() const override{
            return m_longueur * m_largeur;
        }
    private:
        double m_longueur;
        double m_largeur;
    }

    On peut faire pareil pour le cercle (je passe les détails) en utilisant un code ressemblant à
    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
    25
    26
    27
    class Cercle : public Forme{
    public:
        /* je considère que tous mes cercles sont bleus par défaut */
        Cercle(double diametre, Color color=Color::bleu):
            Forme(color),
            m_diametre{diametre}{
        }
        double diametre() const{
            return m_diametre;
        }
        /* il est parfois intéressant d'avoir le rayon
         * (qui correspond à la moitié de la valeur du diamètre)
         */
        double  rayon() const{
            return diametr() / 2;
        }
        /* le mot clé final indique au compilateur que,
         * quoi qu'il arrive (mettons qu'une autre classe hérite
         * de notre classe Cercle), le comportement de la 
         * fonction ne pourra plus être redéfini
         */
        double superficie() const final override{
            return PI * rayon() * rayon();
        }
    private:
        double m_diametre;
    }

    Je peux maintenant créer des variables qui seront soit du type Rectangle soit du type Cercle, et je peux même changer la couleur si leur couleur ne me convient pas avec un code qui ressemble à
    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
    int main(){
        /* pour le rectangle */
        Rectangle monRect{12.0, 15.0};
        /* ou, parce que je veux un rectangle violet */
        Rectangle autreRect{20.0, 30.0, Couleur::violet};
        /* pour le cercle */
        Cercle monCercle{5.0};
        /* ou, parce que je veux un cercle vert */
        Cercle autreCercle{25.0, Color::vert};
        /* et je dispose de l'ensemble des services exposés par les classes concernées: */
        std::cout<<"monRect est de couleur "<< monRect.color()<<"\n"
                 <<"\t et est de longueur "<<monRect.longueur()
                 <<" pour une largeur de "<<monRect.largeur()<<"\n"
                 <<"\t\t soit une superficie de "<<monRect.superficie()<<"\n";
        /* ce qui fonctionne avec le rectangle fonctionne avec le cercle (en adaptant les services bien sur) */
     
        std::cout<<"monCercle est de couleur "<< monCercle.color()<<"\n"
                 <<"\t a un rayon de"<<monCercle.rayon()
                 <<" correspondant à un diametre de "<<monCercle.diametre()<<"\n"
                 <<"\t\t soit une superficie de "<<monCercle.superficie()<<"\n";
    }
    C'est cool, parce que là, le compilateur sait petrinemment que monRect est de type ... Rectangle et que monCercle est de type ... Cercle.

    Le truc, c'est que monRect et monCercle peuvent tous les deux se faire passer comme étant de type ... Forme.

    Et là, bien sur, il devient impossible de savoir si on a effectivement affaire à un Rectangle ou à un Cercle C'est à ce moment là que le polymorphisme entre en jeu

    5- On ne peut pas copier la partie "Forme", il va donc falloir la passer par référence ou par pointeur.

    En outre, dés le moment où il y a "la plus infime partie" d'une classe qui ne peut pas être copiée, c'est l'ensemble de la classe qui ne peut plus être copiée. On ne peut donc pas copier les instances des classes Rectangle et Cercle (ni de n'importe quelle autre classe dérivant de Forme de manière directe ou indirecte) parce qu'il y aura toujours cette partie "Forme" qui ne pourra pas être copiée.

    Cependant, le code ici pourra fonctionner:
    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
    void afficherSurface(Forme const & f){
         /* on ne sait pas si la forme obtenue est un cercle ou un rectangle
          * on n'a aucun moyen de le savoir
          * on ne peut donc pas faire appel au services spécifique aux classes Rectangle
          * (longueur() et largeur()) ou Cercle (rayon() et diametre())
          */
        std::cout<<"la superficie de cette forme est "<<f.superficie()<<"\n";
    }
    int main(){
        /* pour le rectangle */
        Rectangle monRect{12.0, 15.0};
        afficherSurface(monRect); // Ca marche, meme si afficherSurface ne sait pas qu'elle recoit un rectangle
        Cercle monCercle{5.0};
        afficherSurface(monCercle); // Ca marche aussi, meme si afficherSurface ne sait pas qu'elle recoit un cercle
    }

    Après, il y a tout l'aspect de la gestion de collection dont les éléments "passent pour être" du type de base (Forme), mais ca, ce sera pour le prochain épisiode
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  15. #15
    Membre habitué
    Citation Envoyé par koala01 Voir le message
    C'est une idée intéressante, car, en effet, un Rectangle peut n'être "qu'une sorte" de Forme.

    A ceci près (outre les problèmes de ton code, bien sur)
    Bonsoir et merci
    Peut tu me dire quels erreurs de code
    Ton exposé est très intéressant,quelques interrogations quand même : au niveau de la classe Rectangle pour quoi définir en constantes ? :
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
     /* les deux "nouveau services", propres au rectangle */
        double longueur() const { return m_longueur; }
        double largeur()const { return m_largeur; }

    la ligne :
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
     
     assert(m_longueur >= m_largeur && "la longueur devrait être superieure ou egale à la largeur");

    ne fonctionne pas et me provoque une erreur (exception levée) je n'ai pas réussi à trouver comment y remédier
    un peu la même question dans la classe Forme pourquoi en constantes :
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     
    Forme2_V2(Forme2_V2 const&) = delete;
        Forme2_V2& operator=(Forme2_V2 const&) = delete;
        /* et on rend le destructeur virtuel.
        virtual ~Forme() = default;
        Color color() const{return m_color;}
        /* virtual parce que le comportement doit s'adapter au type réel
         * = 0 indique que l'on ne fournit pas le comportement pour l'instant
         * (que c'est une fonction "virtuelle pure")
         */
        virtual double superficie() const = 0;

    tu dis

    On ne peut pas copier la partie "Forme", il va donc falloir la passer par référence ou par pointeur.

    En outre, dés le moment où il y a "la plus infime partie" d'une classe qui ne peut pas être copiée, c'est l'ensemble de la classe qui ne peut plus être copiée. On ne peut donc pas copier les instances des classes Rectangle et Cercle (ni de n'importe quelle autre classe dérivant de Forme de manière directe ou indirecte) parce qu'il y aura toujours cette partie "Forme" qui ne pourra pas être copiée.
    Cependant, le code ici pourra fonctionner:
    void afficherSurface(Forme const & f){
    /* on ne sait pas si la forme obtenue est un cercle ou un rectangle
    * on n'a aucun moyen de le savoir
    * on ne peut donc pas faire appel au services spécifique aux classes Rectangle
    * (longueur() et largeur()) ou Cercle (rayon() et diametre())
    */
    std::cout<<"la superficie de cette forme est "<<f.superficie()<<"\n";
    }
    int main(){
    /* pour le rectangle */
    Rectangle monRect{12.0, 15.0};
    afficherSurface(monRect);
    Mais on peut faire aussi :
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
     
    Rectangle monRect{ 12.0, 15.0 };
    cout << monRect.superficie() << endl;

    Autre question concernant la classe Forme :
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
     Color color() const
        {
            return m_color;
        }
    qui me retourne dans le Main "aucun opérateur ne correspond à ces opérandes "
    j'ai pourtant comme je l'avais fais dans ma version incorporer :
    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
     
    ostream& operator<<(ostream& st, Color p1)
    {
    	switch (p1) {
    	case Color::bleu: st << "BLEU"; break;
    	case Color::violet: st << "VIOLET"; break;
    	case Color::noir: st << "NOIR"; break;
    	}
    	return st;
    }
     
    ostream& operator<<(ostream& st, Forme2_V2& p)
    {
    	return st << p.m_color << endl;
    }
    et rien y fait si tu as une idée je pense que j'avais créé ma classe ainsi avec un pointeur :
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    class Rectangle2 
    {
         Forme2* owner;

    et le "cout " ainsi :
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
     
    cout << "rec : " << *rec.getForme2() << endl;

    peut être que ça vient de là mais j'ai voulu faire de même dans la nouvelle version mais là je manque de compétence je pense, si tu as des idées MERCI

    Si je peux me permettre j'ai posté ceci :
    https://www.developpez.net/forums/d2096498/c-cpp/outils-c-cpp/visual-cpp/connection-mysql-visual-studio-2019-a/

    et j'ai pas de réponse si tu as des infos merci
    Merci pour toutes tes infos
    A+

###raw>template_hook.ano_emploi###