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 :

Héritage et d'appel de méthode


Sujet :

Langage C++

  1. #1
    Membre actif
    Inscrit en
    Mai 2005
    Messages
    348
    Détails du profil
    Informations forums :
    Inscription : Mai 2005
    Messages : 348
    Points : 281
    Points
    281
    Par défaut Héritage et d'appel de méthode
    Bonjour à tous,

    Je me suis heurté à un problème et j'aimerai avoir vos avis pour résoudre ce type de conflit. Disons que l'on dispose des classes suivantes:
    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
    35
    36
     
    class Aliment
    { /* class abstraite */ }
     
    class Viande : public Aliment
    { /* ... */ }
     
    class Legume : public Aliment
    { /* ... */ }
     
    class Selecteur_Aliment
    {
      Aliment * SelectOn_Client(ClientType client_type)
       {
           if(client_type == VEGAN )
               return new Legume();
           else
               return new Viande();
       }
    }
     
    class Cuiseur
    {
    private:
      void Griller(const Legume & leg) const
    { /* ... */ }
     
    void Griller(const Viande & viande) const
    { /* ... */ } 
     
    public:
       void Preparer(ClientType client_type)
        {
          Griller(*AlimentSelecteur.SelectOn_Client(client_type));
         }
    }
    Ce code ne compile pas parce que AlimentSelecteur.SelectOn_Client renvoit un pointeur sur un objet Aliment et il n'y a pas de methode Griller(Aliment).

    Comment faire pour que soit appelée la methode qui correspond à l'objet passé? (Viande ou Légume).

    Merci

  2. #2
    Membre averti
    Profil pro
    professeur des universités à la retraite
    Inscrit en
    Août 2008
    Messages
    364
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : professeur des universités à la retraite

    Informations forums :
    Inscription : Août 2008
    Messages : 364
    Points : 439
    Points
    439
    Par défaut
    Est-ce que Griller() ne devrait pas être une fonction définie comme virtuelle dans Aliment et implémentée dans Légume et dans Viande, plutôt que dans Cuiseur ?

  3. #3
    Membre actif
    Inscrit en
    Mai 2005
    Messages
    348
    Détails du profil
    Informations forums :
    Inscription : Mai 2005
    Messages : 348
    Points : 281
    Points
    281
    Par défaut
    en pratique, ce serait une solution mais... je me demande s'il n'y a pas un autre moyen.

    Je ne suis pas forcément ravis à l'idée de mettre toutes les methodes de cuisson possible dans les aliments ni d'indiquer tous les aliments dans chaque "cuiseur".

    D'un autre coté, j'aimerai quand même savoir s'il n'y a pas un moyen d'appeler une méthode, selon le type de pointeur renvoyé par une méthode telle que
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    Aliment * SelectOn_Client(ClientType client_type)

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

    Informations forums :
    Inscription : Janvier 2009
    Messages : 30
    Points : 18
    Points
    18
    Par défaut
    Regarde le Downcasting, je pense que ça peut être utile! (mais fait quand même attention quant a son utilisation.)

  5. #5
    Membre actif
    Inscrit en
    Mai 2005
    Messages
    348
    Détails du profil
    Informations forums :
    Inscription : Mai 2005
    Messages : 348
    Points : 281
    Points
    281
    Par défaut
    Merci. Bien que la solution au problème se trouve dans le design de l'ensemble, le downcasting est le genre "technique" que je voulais explorer.
    Il y a quand même une question, comment connaitre le type de l'objet pointé à partir d'un pointeur sur l'objet de base?

    le downcasting permet de faire :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    Aliment * alim = new Legume();
    Legume * leg = dynamic_cast<Legume*> (alim);
    Celà sous entend que l'on sache que alim est un pointeur sur un objet Legume.

    Mais comment fait on si on reçoit le pointeur sans savoir si c'est un objet de type Legume ou Viande?

  6. #6
    Rédacteur

    Avatar de Davidbrcz
    Homme Profil pro
    Ing Supaéro - Doctorant ONERA
    Inscrit en
    Juin 2006
    Messages
    2 307
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 32
    Localisation : Suisse

    Informations professionnelles :
    Activité : Ing Supaéro - Doctorant ONERA

    Informations forums :
    Inscription : Juin 2006
    Messages : 2 307
    Points : 4 732
    Points
    4 732
    Par défaut
    Si le nombre de classe enfant est pas trop important, tu tente le downcast classe par classe et si ca échoue (renvoie null avec un pointeur) alors ce n'était pas la bonne classe.

    Mais dans ton cas je penche plus pour du dispatch simple que pour du downcast (qui est à éviter autant que possible, il n'y a que quelques cas où on ne peut pas s'en passer, ici on peut)

    Exemple vite fait de dispatch:
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
     
    #include <iostream>
    class Cuiseur;
     
    struct Aliment
    {
        virtual void Cuire(/*const */ Cuiseur& cuiseur) =0;/* class abstraite */ };
     
    struct Viande : public Aliment
    { void Cuire(/*const */ Cuiseur& cuiseur){std::cout<<"Viande a point"<<std::endl;}/* ... */ };
     
    struct Legume : public Aliment
    { void Cuire(/*const */ Cuiseur& cuiseur){std::cout<<"Legume a la vapeur"<<std::endl;}/* ... */ };
     
    struct Selecteur_Aliment
    {
      Aliment * SelectOn_Client(int n) // je passe par un int, la flemme de taper l'enum
       {
           if(n == 0)
               return new Legume();
           else
               return new Viande();
       }
    };
     
    class Cuiseur
    {
    private:
        Selecteur_Aliment AlimentSelecteur;
    public:
       void Preparer(int n)
        {
    	AlimentSelecteur.SelectOn_Client(n)->Cuire(*this); 
       //on peut décomposer la ligne en deux.
       // Aliment* a=AlimentSelecteur.SelectOn_Client(n);
      //a->Cuire(*this);
        }
    };
     
    int main(int argc, char const *argv[])
    {
        Cuiseur c;
        c.Preparer(0);
        c.Preparer(10);
        return 0;
    }

  7. #7
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 627
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 627
    Points : 30 692
    Points
    30 692
    Par défaut
    Salut,

    Je sais que je chipote, mais, si le compilateur est bien réglé, le code
    Citation Envoyé par Sunsawe Voir le message
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
     
     
    class Selecteur_Aliment
    {
      Aliment * SelectOn_Client(ClientType client_type)
       {
           if(client_type == VEGAN )
               return new Legume();
           else
               return new Viande();
       }
       /*...*/
    };
    devrait amener un avertissement proche de "attention, Selecter_Aliment::SelectOn_client devrait renvoyer quelque chose

    Ce n'est, pour l'instant, qu'un détail parce que tu as effectivement un renvoi effectué pour chaque possibilité de l'alternative envisagée, mais cela peut devenir bien plus embêtant le jour où tu aura plus de deux valeurs possibles...

    En effet, imagine que, plus tard, tu en arrive à un code proche de
    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
    class Selecteur_Aliment
    {
      Aliment * SelectOn_Client(ClientType client_type)
       {
           /* testons les différentes possibilités */
           if(client_type == VEGAN )
               return new Legume();
           else if(client_type==VIAGAN)
               return new Viande();
           else if(client_type==TRUC)
               return new Truc();
           else if(client_type==MACHIN)
               return new Machin();
           /* on a testé toutes les valeurs possibles, mais que va-t-il
            * se passer le jour où, l'utilisateur étant par défaut un imbécile
            * distrait, il aura passé une valeur incorrecte ???
            */
       }
       /*...*/
    };
    La réponse à la question posée en commentaire tient en deux mots très dangereux: comportement indéterminé...

    L'idéal est donc de forcer le retour d'une valeur "cohérente" en dehors de tout test.

    Au choix, cette valeur cohérente peut être soit une valeur par défaut (si on n'a pas affaire à un végétarien, on renvoie un menu "carnassier"), soit une valeur permettant de se rendre compte que l'on n'a pas pu déterminer le plat à préparer (typiquement: NULL), mais qu'il faut alors penser à tester

    Pour supprimer cet avertissement (et éviter le comportement indéfini), ton code devrait donc être proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    Aliment * SelectOn_Client(ClientType client_type)
    {
        if(client_type == VEGAN )
            return new Legume();
        /* si ce n'est pas du végétarien, on renvoie de la viande */
        return new Viande();
    }
    ou de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    Aliment * SelectOn_Client(ClientType client_type)
    {
        if(client_type == VEGAN )
            return new Legume();
        else if (client_type==CARNAC)
           return new Viande();
        /* si ce n'est ni un végétarien, ni un carnacier, on renvoie
         * un pointeur NULL
         */
       return NULL;
    }

  8. #8
    Membre actif
    Inscrit en
    Mai 2005
    Messages
    348
    Détails du profil
    Informations forums :
    Inscription : Mai 2005
    Messages : 348
    Points : 281
    Points
    281
    Par défaut
    merci pour l'explication, je vois le problème.

    Mais en même temps, le ClientType étant un enum, le compilateur devrait raler si on passe une autre valeur que celles définis dans l'enum.. non?

  9. #9
    Rédacteur

    Avatar de Davidbrcz
    Homme Profil pro
    Ing Supaéro - Doctorant ONERA
    Inscrit en
    Juin 2006
    Messages
    2 307
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 32
    Localisation : Suisse

    Informations professionnelles :
    Activité : Ing Supaéro - Doctorant ONERA

    Informations forums :
    Inscription : Juin 2006
    Messages : 2 307
    Points : 4 732
    Points
    4 732
    Par défaut
    L'idéal pour remplacer cette série de if tout moche, c'est une fabrique (cf mon article pour l'explication et ce code pour une version un peu plus évoluée)

  10. #10
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 627
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 627
    Points : 30 692
    Points
    30 692
    Par défaut
    Citation Envoyé par Sunsawe Voir le message
    merci pour l'explication, je vois le problème.

    Mais en même temps, le ClientType étant un enum, le compilateur devrait raler si on passe une autre valeur que celles définis dans l'enum.. non?
    Le fait est qu'il est très facile de "contourner" le type de l'énumération, comme le montre le code suivant:
    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
    enum eMyEnum
    {
        one = 1,
        two,
        three
    };
    void foo(eMyEnum e)
    {
        switch(e)
        {
            case one:
                std::cout<<"one"<<std::endl;
                break;
            case two:
                std::cout<<"two"<<std::endl;
                break;
            case three:
                std::cout<<"three"<<std::endl;
                break;
            default:
                std::cout<<"oops"<<std::endl;
            }
    };
    int main()
    {
        eMyEnum my;
        foo(eMyEnum(4));
        return 0;
    }
    Citation Envoyé par Davidbrcz Voir le message
    L'idéal pour remplacer cette série de if tout moche, c'est une fabrique (cf mon article pour l'explication et ce code pour une version un peu plus évoluée)
    De fait, mais le problème reste entier (bien que je n'ai pas relu le code en question )

    Si, pour une raison ou une autre, l'utilisateur fournit une valeur non conforme avec ce qui est attendu, il faut pouvoir le signaler à la fonction appelante, et le lancer d'exception n'est pas *forcément* la meilleure solution

    PS: en relisant le code, je me rend compte que, effectivement, tu renvoie NULL si d'aventure ton objet n'est pas enregistré dans la fabrique

  11. #11
    Membre actif
    Inscrit en
    Mai 2005
    Messages
    348
    Détails du profil
    Informations forums :
    Inscription : Mai 2005
    Messages : 348
    Points : 281
    Points
    281
    Par défaut
    Je ne savais pas qu'il était si facile de contourner un enum.
    Heureusement, dans la pratique, vu que la plus part du temps, ces cas sont voués à évoluer, même s'ils ne sont que deux à la base, j'utilise un switch avec une clause par défaut.

    En tout cas merci pour vos participations, je pense qu'elles répondent à la plus part de mes interrogations.

  12. #12
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 627
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 627
    Points : 30 692
    Points
    30 692
    Par défaut
    Mais même si tu utilise un switch et une clause par défaut, il reste "facile" d'éviter l'avertissement, simplement en veillant à rajouter un return NULL; (par exemple) en dehors de ta clause switch...

    Il ne servira sans doute à rien (à condition de veiller à avoir effectivement une clause par défaut dans ton switch...), mais cela fait toujours "plus propre" d'avoir un code qui compile sans avertissement même lorsque l'on utilise des options de compilation "paranoïaques"

    C'est, pourquoi pas, peut être aussi l'occasion de voir si tu ne peux pas implémenter SESE (Single Entry, Single Exit), cher à certains (bien que je ne sois pas forcément partisan du fait de l'avoir en tous temps ), voir d'assurer une "meilleure lisibilité" et d'éviter les effets de bord de ton code, 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
    12
    13
    14
    15
    16
    17
    18
    19
    Base* MaClass::foo( eMyEnum t)
    {
        Base* temp;
        switch (t)
        {
            case  truc:
                temp = new Truc;
                break;
            case bidule:
                temp = new Bidule;
                break;
            case machin:
                temp = new Machin;
                break;
            default:
                temp=NULL;
        }
        return temp;
    }

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

Discussions similaires

  1. [PHP 5.3] héritage, classe abstraite - pb d'appelle de méthode
    Par antrax2013 dans le forum Langage
    Réponses: 2
    Dernier message: 29/07/2012, 15h21
  2. Héritage et appel de méthode du parent
    Par waxman dans le forum Langage
    Réponses: 2
    Dernier message: 23/07/2011, 16h00
  3. Héritage : problème d'appel de méthodes
    Par parano dans le forum C++
    Réponses: 15
    Dernier message: 02/03/2007, 14h42
  4. Réponses: 4
    Dernier message: 03/07/2006, 22h52
  5. [Débutant] Héritage & appel de méthodes
    Par KooX dans le forum Langage
    Réponses: 4
    Dernier message: 11/05/2004, 23h37

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