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 :

Problème de polymorphisme ou de conception


Sujet :

C++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre éclairé Avatar de LinuxUser
    Inscrit en
    Avril 2007
    Messages
    857
    Détails du profil
    Informations forums :
    Inscription : Avril 2007
    Messages : 857
    Par défaut Problème de polymorphisme ou de conception
    Bonjour,

    J'ai un problème que je vais illustrer ci-dessous, par contre je ne sais pas si c'est moi qui gère mal le polymorphisme ou si c'est un problème de conception, je vais essayé d'être clair:

    On a une classe Param qui gère des paramètres:

    Param.h:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    class Param
    {
    public:
      Param();
      ...
      double getCommonParam();
    private:
      double m_common;
    Puis deux classes qui la dérivent, Geometric (paramètres géometriques) et Numeric (paramètres numérique):

    Geometric.h
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    class Geometric : Param
    {
    public:
      Geometric();
      Geometric(double& common, double& param);
       ...
       double getGeoParam();
    private:
       double m_param;
    Numeric.h:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    class Numeric : Param
    {
    public:
      Numeric();
       ...
       double getNumericParam();
    private:
       double m_param1;
       double m_param2;
    Puis une classe Problem (gère différents problèmes: mathématiques, physiques, ...):

    Problem.h
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    class Problem
    {
    public:
      Problem();
      ... 
    private:
      Param* m_param;
    Et une classe Math qui dérive Problem:

    Math.h
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class Math : Problem
    {
    public:
      Math();
      ...
      void initParam(double& common, double& param);
      void initParam(double& common, double& param1, double& param2);
      void calculGeo();
     
    private:
      double m_resGeo;
      double m_resNum;
    Math.cpp:
    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
    #include "Math.h"
    ...
    // pas de problème, m_param est bien reconnu comme un objet de type Geo
    void Math::initParam(double& common, double& param)
    {
      m_param = new Geo(common, param);
    }
    // par contre comment faire comprendre que m_param est un Geo ici
    void Math::calcGeo()
    {
      ...
      // et faire par exemple
      m_param.getGeoParam(); // impossible, ne propose que getCommonParam()
      ...
    }
    J'avais pensé modifier void Math::initParam(double& common, double& param) :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    void Math::initParam(double& common, double& param)
    {
      Geo* geo = new Geo(common, param);
      m_param = geo;
    }
    Mais je suis pas sûr que ce soit une bonne idée et de plus je devrai ajouter un constructeur de copie avec un pointeur en argument (je sais pas comment faire cela):
    D'où l'impression que j'ai un problème de conception des classes.

    En espérant avoir été clair.

    Merci de votre aide.

  2. #2
    Expert éminent

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 202
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Juin 2007
    Messages : 5 202
    Par défaut
    ton probleme, c'est "init".

    Tu devrais utiliser des constructeurs, et l'héritage.

    par exemple
    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 Param {
    public:
        explicit Param():m_common(0){}
        explicit Param(double d):m_common(d){}
        double value() const {return m_common;}
        double& value() {return m_common;}
    private:
        double m_common;
    };
     
    class GeoParam {
    public:
        explicit GeoParam():Param(), m_geo(0){}
        explicit GeoParam(double base, double geo):Param(base), m_geo(d){}
        double geo() const {return m_geo;}
        double& geo() {return m_geo;}
    private:
        double m_geo;
    };

  3. #3
    r0d
    r0d est déconnecté
    Membre expérimenté

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2004
    Messages
    4 290
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Août 2004
    Messages : 4 290
    Billets dans le blog
    2
    Par défaut
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    m_param.getGeoParam(); // impossible, ne propose que getCommonParam()
    Tu peux faire un dynamic_cast, mais c'est pas terrible.

    En fait, j'ai l'impression que le problème c'est que tu cherche à généraliser trop tôt. Par exemple, la fonction calcGeo() va forcément avoir besoin d'un paramètre de type Geometric, mais les autres fonctions de la classe n'en auront pas forcément besoin. Il serait donc plus logique de passer ce paramètre à calcGeo(), et ne pas l'utiliser en tant que variable membre.

    Je me trompe certainement, c'est pas facile de résoudre un problème de conception avec si peu de détails

  4. #4
    Membre éclairé Avatar de LinuxUser
    Inscrit en
    Avril 2007
    Messages
    857
    Détails du profil
    Informations forums :
    Inscription : Avril 2007
    Messages : 857
    Par défaut
    Citation Envoyé par leternel Voir le message
    ton probleme, c'est "init".

    Tu devrais utiliser des constructeurs, et l'héritage.

    par exemple
    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 Param {
    public:
        explicit Param():m_common(0){}
        explicit Param(double d):m_common(d){}
        double value() const {return m_common;}
        double& value() {return m_common;}
    private:
        double m_common;
    };
     
    class GeoParam {
    public:
        explicit GeoParam():Param(), m_geo(0){}
        explicit GeoParam(double base, double geo):Param(base), m_geo(d){}
        double geo() const {return m_geo;}
        double& geo() {return m_geo;}
    private:
        double m_geo;
    };
    Je connassais pas le mot clef explicit, par contre ça ne change rien, je n'arrive toujours pas à appeler
    m_param est considéré comme Param et non Geo.

    Pour répondre à r0d:
    J'essaye de génraliser pour faire cela propremement, et là j'ai essayer d'exposer le probleme de manière très simplifier, en réalité j'ai plus de paramètres qui sont utilisés dans toute la classe.

    Leternel:
    Y'aurait pas quelquechose à faire du coté de classe Math, du genre:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    Math()
    {
      m_param = new Geo();
    }

  5. #5
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    Salut,

    Ce qui se passe, surtout, c'est que , comme tu déclare un pointeur sur un objet de type Param dans ta classe problème, le compilateur te fait purement et simplement confiance sur ce point : pour lui, il a bel et bien affaire à un pointeur sur un objet de type Param, même si, en réalité, il a affaire à un pointeur sur un objet dont le type dynamique est Geometric

    (au fait, je viens de voir que tu devrais préciser "public" pour l'héritage, car l'héritage de classes est privé par défaut )

    Ce qu'il te faut faire, c'est (attention, accroches toi, parce que le solutions ne sont pas si simples quand ont débute )

    Soit tu crées carrément deux classes qui héritent de problème :

    Une classe que je vais appeler ProblemeNumerique, qui disposera d'un membre de Numeric et une casse que je vais appeler ProblemeGeo qui disposera d'un membre de type Geometric.

    Dans le même temps, tu rajouterais une fonction virtuelle pure calcule (et non pas calculeGeo ou calculeNum ) que tu définirait dans chaque classe pour que le comportement s'adapte.

    Ainsi, tu pourrais assez facilement mélanger les deux genres de problème, et te contenter d'appeler "calcule()" pour les deux (et là, tu obtiendrais du polymorphisme )

    Soit, tu te dis que, de toutes manières, un problème reste un problème et que la seule chose qui change, c'est le type du paramètre, que tu feras passer pour un "param".

    A ce moment là, ce qu'il te faut, c'est sans doute ce que l'on appelle le "double dispatch".

    Pour faire simple, et pour que tu comprenne le principe, il faut comprendre que si Geometric et Numeric peuvent tous les deux passer pour un Param, ils savent très bien ce qu'ils sont en réalité : le Géométric n'aura jamais l'idée farfelue de croire qu'il est un Numeric, ni vice versa

    Ce qu'il y a de bien en C++, c'est que l'on dispose de la possibilité de surcharger les fonctions.

    C'est à dire que l'on peut créer deux fonction qui ont le même nom, le même type de retour et dont la seule différence tient dans le nombre et / ou le type de leurs arguments.

    Tu pourrais donc très bien avoir une fonction qui prend une référence (constante) sur un objet de type Geometric et, juste à coté, un fonction portant le même nom, renvoyant le même type de valeur mais prenant... une référence (constante) sur un objet de type Numeric.

    Si tu appelle cette fonction depuis une fonction membre de ton type (Géométric ou Numéric), en transmettant "ce qui est pointé par this" (c'est à dire en transmettant l'objet au départ duquel tu as appelé la fonction membre ), le compilateur choisira d'office la bonne fonction : celle qui prend une référence constante sur Geometric si elle est appelée depuis un objet qui est de ce type, ou celle qui prend une référence constante sur Numeric si on est dans l'autre cas

    Evidemment, tu me diras sans doute que
    c'est bien beau tout cela, mais si je ne peux accéder qu'aux fonctions de Param, je fais comment, moi, pour faire en sorte d'appeler une fonction membre spécifique de Numeric ou de Geometric
    Et la réponse tient encore en un mot : polymorphisme.

    Il "suffira" en effet de déclarer une fonction virtuelle pure (par exemple : execute ) dans ta classe param et de la définir (sous une forme proche de /*return */ calculeMoi(*this); en considérant que c'est le nom que tu auras donné à ta fonction surchargée ) aussi bien dans la classe Geometric que dans la classe Numeric, et le tour sera joué

    C'est une conception tellement habituelle que l'on a même créé un patron de conception (on parle de design pattern en anglais) qui met en place ce double dispatch : le pattern visiteur

    Au final, cela pourrait ressembler à quelque chose comme (d'abord sans le patron de conception visiteur
    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
    void calculeMoi(Geometric const & g)
    {
        /* ce qu'il faut faire en utilisant g */
    }
    void calculeMoi(Numeric const & n )
    {
        /* ce qu'il faut faire en utilisant n */
    }
    class Param
    {
        public:
            virtual void execute() const = 0;
            /* le reste de la classe comme avant */
    };
    class Numeric : public Param
    {
        public:
            virtual void execute() const {calculeMoi(*this);}
            /* le reste de la classe comme avant */
    };
     
    class Geometric : public Param
    {
        public:
            virtual void execute() const {calculeMoi(*this);}
            /* le reste de la classe comme avant */
    };
    avec une petite énumération (pour savoir si c'est un problème de math classique ou un problème de géo) proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    enum what
    {
        algebre,
        geometrie
    };
    et une classe problème 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
    class Problem
    {
    public:
      Problem(what w /* , ...*/)
      {
           if(w ==  algebre)
               m_param = new Numeric(/* ...*/);
           else
               m_param = new Geometric(/* ...*/);
      }
      ... 
    private:
      Param* m_param;
    Bon, c'est très clairement encore perfectible, mais cela me ferait partir sur l'écriture d'un roman, et j'en prend déjà le chemin, donc, si tu veux, j'expliquerai comment encore perfectionner cela plus tard

    Par contre, je vais quand meme t'expliquer le principe du pattern visiteur

    L'idée est, tout simplement, d'encapsuler la fonction qui fera effectivement le travail (calculeMoi, dans le cas présent) dans une classe que l'on appelle "visiteur" (par opposition, Generic et Numeric sont donc... les visités )

    Le prototype de execute devient alors virtual void execute(Visiteur const & v) const et l'implémentation (dans Geometric et dans Numeric) devient proche de v.calculeMoi(*this); .

    Tu vas sans doute me demander maintenant quel avantage tu pourrais tirer à faire de la sorte

    Et, encore une fois, la réponse tient, décidément, toujours dans le même mot: polymorphisme

    En effet, si tu rend calculeMoi virtuelle, tu peux décider de faire hériter plusieurs classes de ton visiteur, et, à chaque fois, redéfinir le comportements des deux "versions" de calculeMoi

    Au final, cela t'apporte encore un "point de variation" supplémentaire, car, tu pourras choisir (de par la hiérarchie de classes issue de Param), le type de problème que tu veux résoudre, et de l'autre (de par la hiérarchie de classes issue de visiteur) n'importe quel autre aspect, comme le "niveau de difficulté" ou encore un aspect particulier que tu veux calculer / vérifier

    La seule limite que tu auras pour les différentes classes qui dériveront de visiteur étant, finalement, ton imagination ou les besoins que tu n'as pas
    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

  6. #6
    Membre éclairé Avatar de LinuxUser
    Inscrit en
    Avril 2007
    Messages
    857
    Détails du profil
    Informations forums :
    Inscription : Avril 2007
    Messages : 857
    Par défaut
    Tout d'abord merci pour ton message, mais j'ai ps tout compris.


    Tu as raison désolé.
    Citation Envoyé par koala01 Voir le message
    (au fait, je viens de voir que tu devrais préciser "public" pour l'héritage, car l'héritage de classes est privé par défaut )
    C'est justement ce que je voulais éviter, je voulais faire ça de manière plus sioux.
    Citation Envoyé par koala01 Voir le message
    Soit tu crées carrément deux classes qui héritent de problème :
    Oui voilà.
    Citation Envoyé par koala01 Voir le message
    Soit, tu te dis que, de toutes manières, un problème reste un problème et que la seule chose qui change, c'est le type du paramètre, que tu feras passer pour un "param".
    Déjà là je comprends pas pourquoi le besoin des réferences.
    Citation Envoyé par koala01 Voir le message
    Tu pourrais donc très bien avoir une fonction qui prend une référence (constante) sur un objet de type Geometric et, juste à coté, un fonction portant le même nom, renvoyant le même type de valeur mais prenant... une référence (constante) sur un objet de type Numeric.
    Je vais faire des recherces la dessus.
    Citation Envoyé par koala01 Voir le message
    C'est une conception tellement habituelle que l'on a même créé un patron de conception (on parle de design pattern en anglais) qui met en place ce double dispatch : le pattern visiteur
    Citation Envoyé par koala01 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
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    void calculeMoi(Geometric const & g)
    {
        /* ce qu'il faut faire en utilisant g */
    }
    void calculeMoi(Numeric const & n )
    {
        /* ce qu'il faut faire en utilisant n */
    }
    class Param
    {
        public:
            virtual void execute() const = 0;
            /* le reste de la classe comme avant */
    };
    class Numeric : public Param
    {
        public:
            virtual void execute() const {calculeMoi(*this);}
            /* le reste de la classe comme avant */
    };
     
    class Geometric : public Param
    {
        public:
            virtual void execute() const {calculeMoi(*this);}
            /* le reste de la classe comme avant */
    };
    Si j'applique ça à mon exemple:
    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
     
    class Param
    {
    public:
      Param();
      double getCommonParam();
      virtual void execute() const = 0;
    private:
      double m_common;
    };
    class Geometric : public Param
    {
    public:
      Geometric();
      Geometric(double& common, double& param);
      virtual void execute() const {calculeMoi(*this);}
      double getGeoParam();
    private:
       double m_param;
    };
    class Numeric : public Param
    {
    public:
      Numeric();
      virtual void execute() const {calculeMoi(*this);}
      double getNumericParam();
    private:
       double m_param1;
       double m_param2;
    };
    class Math : Problem
    {
    public:
      Math();
      ...
      void initParam(double& common, double& param);
      void initParam(double& common, double& param1, double& param2);
      void calculGeo();
     
    private:
      double m_resGeo;
      double m_resNum;
    };
    Question 1:
    Où je déclare et implémente les fonctions calculeMoi(*this) ?

    Question 2:
    Ta fonction calculeMoi(*this) , est-ce qu'elle remplace ma focntion calculGeo()?
    Si oui, pourquoi kui passer une référence?

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    Citation Envoyé par LinuxUser Voir le message
    C'est justement ce que je voulais éviter, je voulais faire ça de manière plus sioux.
    Tu n'as, malheureusement, pas le choix car l'héritage publique et l'héritage privé représentent deux choses différentes

    Si tu cherches à avoir de la substituabilité (à pouvoir passer un objet du type dérivé à une fonction qui attend un pointeur ou une référence sur un objet du type de base), alors, tu dois utiliser l'héritage public, car l'héritage privé aura pour résultat de n'être accessible (comme tout ce qui est privé, d'ailleurs) qu'au départ d'une fonction membre de la classe héritière, et à elle seule.

    On parle alors d'une liaison "est implémenté en termes de"

    Ceci dit, comme je te l'ai indiqué, il y a la possibilité de faire en sorte que ce soit ProblèmeGeo et ProblèmeAlgebre qui héritent de problème.

    A ce moment là, tu peux effectivement avoir un héritage privé de Geometric (ou de Numeric) envers Param si et seulement si
    1. Problème déclare une fonction virtuelle qui sera suffisamment générique pour être appelée depuis ProblèmeGeo ou ProblemeAlgebre comme calcule (au lieu de calculeGeo)
    2. ProblemeGeo et ProblemeAlgebre implémentent la fonction calcule
    3. ProblemeGeo dispose d'un membre de type Geometric (et ProblemeAlgebre dispose d'un membre Numeric), en étant bien conscient que ni ProblemeGeo ni ProblemeAlgebre ne pourront accéder à la fonction value de Param (car elle sera considérée comme privée, à cause de l'héritage privé)
    Déjà là je comprends pas pourquoi le besoin des réferences.
    Le fait est que tu ne peux profiter du polymorphisme que si tu travailles avec un pointeur ou avec une référence.

    On préfère travailler avec des références car il y a garantie d'existence, qu'elles permettent de respecter la même syntaxe que si l'on travaillait directement avec l'objet et surtout parce qu'elles transmettent la const-correctness : quand une référence est constante, tu es sur de ne pouvoir utiliser que des fonction qui se sont engagées à ne pas modifier l'état de l'objet en étant déclarées constantes.



    Je vais faire des recherces la dessus.


    Si j'applique ça à mon exemple:
    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
     
    class Param
    {
    public:
      Param();
      double getCommonParam();
      virtual void execute() const = 0;
    private:
      double m_common;
    };
    class Geometric : public Param
    {
    public:
      Geometric();
      Geometric(double& common, double& param);
      virtual void execute() const {calculeMoi(*this);}
      double getGeoParam();
    private:
       double m_param;
    };
    class Numeric : public Param
    {
    public:
      Numeric();
      virtual void execute() const {calculeMoi(*this);}
      double getNumericParam();
    private:
       double m_param1;
       double m_param2;
    };
    class Math : Problem
    {
    public:
      Math();
      ...
      void initParam(double& common, double& param);
      void initParam(double& common, double& param1, double& param2);
      void calculGeo();
     
    private:
      double m_resGeo;
      double m_resNum;
    };
    Attention, il n'y a aucun sens à déclarer une fonction calculeGeo dans la classe Probleme, surtout si elle n'est pas virtuelle...

    D'abord, parce que tu ne peux réimplémenter dans une classe dérivée que les classes qui sont déclarée virtuelles

    Ensuite parce qu'il n'y a aucun sens à disposer d'une fonction dans la classe ProblèmeAlgebre, or, comme cette fontction est dans l'accessibilité publique, elle sera accessible depuis toutes les classes dérivées (y compris... ProblèmeAlgebre

    Nous sommes donc en violation du principe de substitution de Liskov (LSP de son petit nom) qui dit que toute propriété valide (comme le fait de pouvoir invoquer une fonction comme calculeGeo) de la classe mère doit être valide dans la classe dérivée

    Question 1:
    Où je déclare et implémente les fonctions calculeMoi(*this) ?
    Tu peux parfaitement en faire des fonctions libres

    Tu les déclarerait dans un fichier d'en-tête quelconque (mais idéalement séparé du reste) et tu les implémenterait dans un fichier *.cpp de ton choix (mais idéalement dans le fichier cpp du même nom que celui du fichier d'en-tête dans lequel elles sont déclarées )
    Question 2:
    Ta fonction calculeMoi(*this) , est-ce qu'elle remplace ma focntion calculGeo()?
    Non, ta fonction calculeGeo (qui devrait s'appeler simplement calcule) appelle Param.execute(), et la fonction calculeMoi n'a aucune connaissance de tes classes dérivées de Problème

    Quand Param.execute() est appelée, comme il s'agit d'une fonction virtuelle, le programme saura à l'exécution si elle est appelée depuis un objet de type Geometric ou depuis un objet de type Numeric.

    C'est ce que l'on appelle le RTTI (Run Time Type Information, ou, si tu préfères, les informations de type à l'exécution )

    Le programme s'occupera de lui même d'appeler comme un grand la version de execute qui correspond au type réel (dynamique) de l'objet depuis lequel elle est appelée, même s'il s'agit dans le code d'un pointeur ou d'une référence vers un objet de type Param (à condition toutefois que l'héritage soit public ) et la bonne version de execute (comprend Geometric.execute ou Numeric.execute) se chargera de passer l'objet depuis lequel elle est appelée à la bonne version de calculeMoi.

    Si oui, pourquoi kui passer une référence?
    Pour trois raisons essentiellement dont deux que je viens d'ailleurs de citer:
    1. Les références apportent une garantie supplémentaire par rapport au pointeur : leur garantie de non nullité (une référence doit faire... référence à un objet existant, alors qu'un pointeur peut pointer sur NULL ce qui correspond au final à un objet inexistant)
    2. Les références permettent de garantir la const correctness, et, enfin,
    3. Pour éviter la copie.
    Pour que tu comprennes bien cette troisième raison, il faut savoir que si tu déclares une fonction avec le prototype ayant la forme de void foo(UnType arg), tu transmets l'argument arg qui est de type UnType par valeur, ce qui fait que tu travailleras avec une copie de la variable que tu auras utilisée comme argument.

    Cela implique deux choses :
    1. D'abord, le fait que les modifications que tu pourrait apporter à arg ne seront pas répercutées sur la variable correspondante dans la fonction appelante, mais ca, ca peut aussi bien être un bien qu'un mal
    2. Ensuite cela implique qu'il y a tout un mécanisme de copie de ton objet qui sera effectué au moment de l'appel de la fonction, et tout un mécanisme de destruction de la variable qui sera effectué au moment où tu quittera la fonction

    La copie peut prendre énormément de temps et demander beaucoup de ressources et la destruction peut aussi demander énormément de temps.

    Si c'est une fonction qui est appelée souvent, cela risque d'avoir un impact très sérieux sur les performances, surtout si le code de la fonction fait peut de choses (en terme d'instructions processeur) car le ratio entre le temps nécessaire à la copie de l'objet + celui nécessaire à sa destruction par rapport au temps passé à faire quelque chose "d'utile" (l'exécution du corps de ta fonction) devient d'autant moins favorable que l'exécution du corps de la fonction est rapide

    Ensuite, il faut savoir que si une classe hérite (publiquement) d'une autre, elle a sémantique d'entité : il ne peut jamais exister deux instances différentes d'une classe ayant sémantique d'entité en mémoire représentant la même "chose".

    Prenons l'exemple d'une classe Personne dont chaque instance représente l'un des 7 milliards d'homme vivant sur la terre.

    Chaque instance représente une personne unique et bien déterminée, et tu t'attends à ce que tout ce que tu lui feras "subir" s'applique bel et bien à cette personne clairement identifiée.

    Si tu permet la création d'une copie de cette personne, il se peut que certaines choses que tu ferais subir s'applique sur la personne "originale" et que d'autres s'appliquent sur "la copie", et il te sera très difficile de s'avoir ce qui a été appliqué à qui

    Pour cette raison, il est préférable d'interdire la copie des classes ayant sémantique d'entité (et c'est ce que je te conseillerais très fort de faire pour tes classes Param et dérivées ainsi que pour tes classe Probleme et dérivées).

    Mais, si tu interdits la copie des objet dont le type a sémantique d'entité, il faudra bien trouver un moyen de les passer en paramètres aux fonctions qui en ont besoin

    Pour ce faire, on a deux solutions :
    • Soit on passe un pointeur sur l'objet en question (qui représente l'adresse mémoire à laquelle se trouve l'objet) , en sachant que tu devras tester systématiquement la validité du pointeur car il peut etre nul et en perdant la possibilité de profiter de la const-correctness
    • Soit on passe une référence (constante si la fonction appelée ne doit pas modifier l'objet reçu en argument), et l'on est sur non seulement que l'objet existe bel et bien (grâce à la garantie d'existence que représente une référence), mais, en plus, on est sur que l'on ne pourra appeler que les fonctions membres qui se sont engagées à ne pas modifier l'objet, que ce soit de manière directe ou indirecte, si l'on passe l'objet sous la forme d'une référence constante.
    Des deux solutions, la deuxième est très clairement celle qui nous apporte le plus de facilité et le plus de garanties, à moins, bien sur, que l'on ait une bonne raison de faire autrement, et la seule raison plausible est que l'objet que l'on passe en argument puisse ne pas exister, et qu'il faut veiller à cela au niveau de la fonction (par exemple, un membre "parent" dans un une structure d'arbre : la "racine" est un élément de l'arbre qui n'a pas de parent )
    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

  8. #8
    Membre éclairé Avatar de LinuxUser
    Inscrit en
    Avril 2007
    Messages
    857
    Détails du profil
    Informations forums :
    Inscription : Avril 2007
    Messages : 857
    Par défaut
    Je suis complètement perdu .

    Si vous le voulez bien, je vais encore simplifier le problème:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    classe Mere
    {
    public:
      Mere();
      double getA();
      double getB();
      double getC();
    protected:
      double m_A;
      double m_B;
      double m_C;
    };
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    classe Fille : public Mere
    {
    public:
      Fille();
      getD();
      getE();
      getF();
    private:
      double m_D;
      double m_E;
      double m_F:
    };
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class Main
    {
    public:
      Main();
      void calcul1():
      void calcul2();
    private:
      Mere* m_femme;
      int m_value1;
      int m_value2;
    };
    Dans Main.cpp
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    Main::Main()
    {
      m_femme = new Fille();
    }
    void Main::calcul1()
    {
      ...
      m_value1 = .... + m_femme->getD(); // marche pas 
    }
    void Main::calcul2()
    {
      ...
      m_value2 = .... + m_femme->getD() + m_femme->getE(); // marche pas
    }
    Comment faire en sorte que la variable membre soit utilisée comme un objet dans toute la classe?

Discussions similaires

  1. [AC-2003] Problème de formulaire ou de conception
    Par Chris_Dupasquier dans le forum Modélisation
    Réponses: 6
    Dernier message: 11/03/2010, 10h35
  2. Problème d'implementation et de conception
    Par Invité1 dans le forum C#
    Réponses: 7
    Dernier message: 14/05/2008, 00h29
  3. Problème de polymorphisme
    Par Alain Defrance dans le forum C++
    Réponses: 6
    Dernier message: 14/03/2008, 17h03
  4. [Conception] Problème au niveau de la conception d'un projet
    Par Evocatii dans le forum PHP & Base de données
    Réponses: 1
    Dernier message: 26/06/2007, 15h55
  5. Problème d'analyse ou de conception
    Par nanou2002 dans le forum Architecture
    Réponses: 4
    Dernier message: 25/10/2006, 17h27

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