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 :

Classes, Polymorphisme, Typage Tardif


Sujet :

Langage C++

  1. #1
    Nouveau Candidat au Club
    Inscrit en
    Septembre 2009
    Messages
    5
    Détails du profil
    Informations forums :
    Inscription : Septembre 2009
    Messages : 5
    Points : 1
    Points
    1
    Par défaut Classes, Polymorphisme, Typage Tardif
    J'ai une question de fond sur les classes et leur typage en c++, que j'illustrerai avec l'histoire suivante:

    Imaginons une usine de traitement de colis.
    Elle dispose d'un traitement automatisé des colis sur un tapis roulant qui classe et dirige les paquets au bon endroit suivant un code barre qui est associé dans une base de données à d'autre informations ("poids", "taille", "type", +paramètres fonction du type), ET d'un traitement spécial pour les colis pour lesquels le code barre est illisible automatiquement.

    Ces colis illisibles sortent d'un tapis roulant vers un opérateur.

    Ils sont d'abord automatiquement pesés et mesurés avant leur arrivée et le poids est annoncé sur un écran.

    L'opérateur doit ensuite manuellement déterminer le type de colis parmis "Fragile", "Normal" et "Solide"

    Et compléter selon ce type les paramètres demandés:
    -pour "Fragile" il doit donner la "pression max" autorisée pour le colis
    -pour "Normal" il ne donne rien
    -pour "Solide" il doit donner le "type de matériaux" contenu dans le colis

    Si on regarde maintenant au sens C++ les objets:
    J'ai un objet "Colis" générique qui va être traité par l'opérateur et qui contient le poids et la taille. Et en sortie du traitement de l'opérateur, J'ai 3 types d'objets qui ont des propriétés différentes selon leur type.

    J'avais pensé faire (c'est du pseudo_code c++, je ne l'ai pas compilé donc erreurs possible, mais l'idée reste la même):

    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
    class Colis{
    	public:
    		virtual int get_destination(){}
    		int poids;
    		int taille;
    };
     
    class Colis_Fragile: public Colis{
    	public:
    		int get_destination(){ return 1; }
    		int pression_max;
    };
     
    class Colis_Normal: public Colis{
    	public:
    		int get_destination(){ return 2; }
    };
     
    class Colis_Solide: public Colis{
    	public:
    		int get_destination(){ return 3; }
    		char *type_de_materiau;
    };
    En refaisant l'histoire:

    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
    ...
     
    //le colis existe
    Colis *un_colis = new Colis();
     
    //il est pesé et mesuré par la machine
    un_colis->poids = pese();
    un_colis->taille = mesure();
     
    //puis il est présenté à l'opérateur
    affichage_infos(un_colis);
     
    // et on lui demande de typer ce colis
    switch(demande_du_type_de_colis()){
    	case TYPE_FRAGILE:
    		//-->???<---
    		break;
    	case TYPE_NORMAL:
    		//--->???<---
    		break;
    	case TYPE_SOLIDE:
    		//--->???<---
    		break;
    }
     
    //maintenant que le colis est typé, la machine peut le gérer
    //en demandant à l'objet sa destination
    int destination=un_colis->get_destination();
     
    ...
    Ma question est la suivante, que pourrait t'on mettre dans le switch à la place des --->???<--- ?

    A ma connaissance on ne peut pas changer le type d'une classe mère en une classe fille. En effet l'objet construit est bien celui de la classe mère et ne contient pas les données supplémentaires de la fille.
    D'un autre côté si on veut respecter l'ordre de l'histoire on ne peut pas typer notre Colis tant qu'on ne l'a pas identifié, mais on peut déjà lui avoir renseigné des informations (le poids et la taille ici).

    Y a t'il quelquechose en c++ de prévu pour "compléter" un objet créé sur une classe de base en le typant fortement et qu'à l'occasion de ce complément, un constructeur pour les données de la classe fille soit appelé afin d'initialiser les données spécifiques au type?

    Si les données avaient été recueillies dans un autre ordre, j'aurai par exemple eu le type en 1er, et ainsi pu créer mon objet fille, puis remplir l'ensemble des données de facon sécurisée et propre... MAIS ma question est uniquement dans le cas où les données apparaissent dans cet ordre et où bien évidemment on se refuse de les stocker temporairement dans le but de créer l'objet complet à la fin en une fois.

    J'ai plusieurs pistes:
    - créer une méthode de la classe mère "Colis *change_type(int type)" qui retourne un pointeur sur un objet dupliqué dans la classe fille spécifiée, mais qui nécessite de bien gérer les destructions et qui oblige à recopier les données lors de la construction de la classe fille.

    - créer une méthode de la classe mère "change_type(int type)" qui changerait "this" directement en faisant la même chose... mais je ne sais pas si c'est jouable, et surtout utilisable car avant l'appel à cette fonction, le pointeur de l'objet serait générique et après il serait spécifique, faut voir c'est peut être bien géré.

    - changer l'ordre d'héritage des classes, et dire que le colis finalement hérite de tous les types en même temps, du coup le simple opérateur de cast fait la bonne interprétation, mais cela duplique les données, et les objets sont donc plus volumineux.

    - ??? autre solution?

    Je souhaite conserver une classe différente par type, c'est plus élégant que d'avoir un pointeur "options_du_type" qui pointerait vers des pseudos classes contenant les options spécifiques et venant s'ajouter à l'objet "générique"

    C'est bien sur un cas d'école, et je cherche simplement à trouver une solution élégante, optimisée et transparente au possible.

    Si j'avais pris l'analogie avec les cellules souches du corps humain par exemple, cela m'aurai mené vers l'héritage multiple, car une cellule souche est au début capable de tout faire et au fur et à mesure de sa spécialisation elle "oublie" pour faire moins de chose, jusqu'à devenir une simple cellule totalement différenciée capable de ne faire qu'une chose.

    L'héritage multiple ne me permet cependant pas semble t'il d'oublier qu'on est capable de tout faire, et un objet fortement différencié se trimballerait avec la totalité de ses héritiers.

    Du coup, avant de me lancer, je cherche à savoir quelle serait la plus belle solution pour ce type de problème de "typage de classe tardif"

    Merci d'avance de vos avis sur le sujet.

  2. #2
    r0d
    r0d est déconnecté
    Expert éminent

    Homme Profil pro
    tech lead c++ linux
    Inscrit en
    Août 2004
    Messages
    4 262
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : tech lead c++ linux

    Informations forums :
    Inscription : Août 2004
    Messages : 4 262
    Points : 6 680
    Points
    6 680
    Billets dans le blog
    2
    Par défaut
    Bonjour,

    pour ma part, je vois 2 solutions (il en existe certainement d'autres):
    1. Utilisation d'une factory, qui créera les Colis seulement une fois que les informations (poids, taille) sont connues. Ce n'est pas toujours possible.

    2. Séparer la classe mère Colis en 2 partie: une partie commune et une partie spécialisée, la partie spécialisée n'étant construite qu'une fois les données (taille et poids) connues. Il y a plusieurs façon de faire (basiquement, template ou composition). En utilisant la composition, cela donnerais 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
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    class Colis_Spec{
    	public:
    		virtual void get_destination() = 0;
    };
     
    class Colis_Spec_Fragile: public Colis_Spec{
    	public:
    		int get_destination(){ return 1; }
    		int pression_max;
    };
     
    class Colis_Spec_Normal: public Colis_Spec{
    	public:
    		int get_destination(){ return 2; }
    };
     
    class Colis_Spec_Solide: public Colis_Spec{
    	public:
    		int get_destination(){ return 3; }
    		char *type_de_materiau;
    };
     
    class Colis
    {
    	public:
    		Colis() : colis_spec(NULL) {}
    		int poids;
    		int taille;
    		Colis_Spec * colis_spec;
    };
     
    // utilisation:
    Colis un_colis;
    un_colis->poids = pese();
    un_colis->taille = mesure();
     
    //puis il est présenté à l'opérateur
    affichage_infos(un_colis);
     
    // et on lui demande de typer ce colis
    switch(demande_du_type_de_colis()){
    	case TYPE_FRAGILE:
    		colis.colis_spec = new Colis_Spec_Fragile();
    		break;
    	case TYPE_NORMAL:
    		colis.colis_spec = new Colis_Spec_Normal();
    		break;
    	case TYPE_SOLIDE:
    		colis.colis_spec = new Colis_Spec_Solide();
    		break;
    	case default:
    		// problème à gérer car un_colis.colis_spec sera NULL et cela peut être dangereux
    }
     
    //maintenant que le colis est typé, la machine peut le gérer
    //en demandant à l'objet sa destination
    int destination=un_colis.colis_spec->get_destination(); // on voit clairement ici pourquoi c'est dangereux si un_colis.colis_spec est NULL
    « L'effort par lequel toute chose tend à persévérer dans son être n'est rien de plus que l'essence actuelle de cette chose. »
    Spinoza — Éthique III, Proposition VII

  3. #3
    Nouveau Candidat au Club
    Inscrit en
    Septembre 2009
    Messages
    5
    Détails du profil
    Informations forums :
    Inscription : Septembre 2009
    Messages : 5
    Points : 1
    Points
    1
    Par défaut
    bonjour a tous, (un oubli du premier post :p)

    Merci pour tes propositions r0d.
    Je m'était refusé d'utiliser des variables membres de la classe mère pour résoudre mon problème (
    Je souhaite conserver une classe différente par type, c'est plus élégant que d'avoir un pointeur "options_du_type" qui pointerait vers des pseudos classes contenant les options spécifiques et venant s'ajouter à l'objet "générique"
    ) car cela me paraissait trop rafistolage et surtout déclarait notre impuissance à être indifférent à l'ordre dans lequel notre objet complet final a été construit, influençant donc la façon de l'utiliser...

    En effet, si on a les infos dans le bon ordre on peut avoir une belle hiérarchie d'objets et le polymorphisme sympathique. Or là comme l'ordre n'est pas le bon, on se retrouve limité à être obligé de jouer avec une variable sous forme de pointeurs qui contient les spécificités... je trouvais ça juste pas beau...
    Et que se passerai t'il d'ailleurs si on devait après gérer indifféremment les objets créés dans le bon ordre avec ceux gérés dans le mauvais...

    Mais il semble qu'on ait pas vraiment le choix... j'espérait qu'on me sortirai de derrière les fagots une technique de la mort qui permette de faire du transtypage à n'importe quel moment, c'est peut être une limitation du c++ et un nouveau truc à faire évoluer...

    En tout cas merci r0d pour ton point de vue.

  4. #4
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Points : 13 017
    Points
    13 017
    Par défaut
    Salut,
    Le pattern Etat que te propose R0d ne me choquerait pas avec ce que tu décris.

    La fabrique comme il propose pourrait aussi être une solution.

    Un moyen de changer le 'type dynamiquement' (c'est pas tout à fait vrai, mais bon), c'est le pattern décorateur :
    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
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    #include <iostream>
    class Colis{
    	public:
    		virtual int get_destination()=0;
    		int poids;
    		int taille;
     
    		virtual ~Colis()=0;
    };
    Colis::~Colis(){}
     
    class ColisDecoree : public Colis
    {
       public:
       ColisDecoree(Colis*base_):m_colis(base_){}
       ~ColisDecoree(){delete m_colis;}
       private:
       Colis *m_colis;
       ColisDecoree(ColisDecoree const&);
       ColisDecoree&operator=(ColisDecoree&);
    };
     
    class Colis_Indetermine : public Colis
    {
       public:
       virtual int get_destination()
       {
          throw "un colis indetermine n'a pas de destination";
       }
       ~Colis_Indetermine(){}
    };
     
    class Colis_Fragile: public ColisDecoree {
    	public:
    	Colis_Fragile(Colis*base_):ColisDecoree(base_){}
    		int get_destination(){
    		   std::cout<<"je suis fragile !"<<std::endl;
    		   return 1;
          }
    		int pression_max;
    };
     
    class Colis_Normal: public ColisDecoree{
    	public:
    	Colis_Normal(Colis*base_):ColisDecoree(base_){}
    		int get_destination(){
    		   std::cout<<"je suis normal !"<<std::endl;
    		   return 2;
          }
    };
     
    class Colis_Solide: public ColisDecoree{
    	public:
    	Colis_Solide(Colis*base_):ColisDecoree(base_){}
    		int get_destination(){
    		   std::cout<<"je suis solide !"<<std::endl;
    		   return 3;
          }
    		char *type_de_materiau;
    };
     
    enum E_Type_Colis{
       TYPE_FRAGILE,
       TYPE_NORMAL,
       TYPE_SOLIDE
    };
    template<class istreamT>
    istreamT& operator>>(istreamT &in_, E_Type_Colis&val_)
    {
       int i;
       in_>>i;
       switch(i){
          case 0:
          val_ = TYPE_FRAGILE;
          break;
          case 1:
          val_ = TYPE_NORMAL;
          break;
          case 2:
          val_ = TYPE_SOLIDE;
          break;
          default:
          throw "valeur invalide";
          break;
       }
       return in_;
    }
     
    #include <iostream>
    int main()
    {
       Colis* mon_colis(new Colis_Indetermine);
     
       mon_colis->poids = 1;
       mon_colis->taille = 1;
       E_Type_Colis type;
       std::cout<<"De quel type est le colis (0 fragile, 1 normal, 2 solide) ?";
       std::cin>>type;
       switch(type){
          case TYPE_FRAGILE:
          mon_colis = new Colis_Fragile(mon_colis);
          break;
          case TYPE_NORMAL:
          mon_colis = new Colis_Normal(mon_colis);
          break;
          case TYPE_SOLIDE:
          mon_colis = new Colis_Solide(mon_colis);
          break;
          default:
          throw;
       }
       mon_colis->get_destination();
       delete mon_colis;
       return 0;
    }

  5. #5
    Nouveau Candidat au Club
    Inscrit en
    Septembre 2009
    Messages
    5
    Détails du profil
    Informations forums :
    Inscription : Septembre 2009
    Messages : 5
    Points : 1
    Points
    1
    Par défaut
    cool merci pour ces réponses, il y a des trucs intéressants dans les design pattern, il va me falloir aller creuser tout ça.

    celui qui me semble le plus prometteur pour mon problème semble effectivement le décorateur, ça ressemble le plus à ce que j'ai envie de faire à savoir créer un objet et l'habiller avec des nouvelles propriétés, tout en ayant un typage fort de l'objet en question et en conservant le polymorphisme pour des appels transparents.

    Seul petit soucis que j'ai trouvé pour le moment c'est que dans l'exemple que tu donnes, l'objet Colis est construit à deux reprises.
    1 fois lors du new Colis_indetermine et qui est remplis avec la taille et le poids, celui là je le comprends.
    et 1 autre fois lors de la decoration, car le new Colis_Fragile par exemple, crée un Colis_decoree qui crée à son tour un Colis en plus de venir mettre à jour le pointeur sur le type (m_base)
    ce qui fait que du point de vue de l'objet Colis_decoree, il y a deux références à un colis, la première est dans la classe elle même car héritée, et la deuxième en pointeur à travers m_base.
    Donc dans le principe si le constructeur de Colis met 2h et prends 50Mo de mémoire, on aura réussi à doubler la charge...
    Bon dans mon cas c'est pas bien gênant j'ai ni cette lenteur ni cette charge mémoire

    pour info log de ton code, après l'avoir truffé de messages pour tracer les constructions destructions de tous les objets.

    "
    construction colis
    construction colis
    construction colis_decore
    construction colis_fragile
    je suis fragile !
    destruction colis_fragile
    destruction colis_decore
    destruction colis
    destruction colis
    "

    le code main correspondant étant:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
       Colis* mon_colis=new Colis_Indetermine();
     
       mon_colis->poids = 1;
       mon_colis->taille = 1;
     
       mon_colis = new Colis_Fragile(mon_colis);
       mon_colis->get_destination();
       delete mon_colis;
    en tous cas très sympas tes design patterns!

  6. #6
    r0d
    r0d est déconnecté
    Expert éminent

    Homme Profil pro
    tech lead c++ linux
    Inscrit en
    Août 2004
    Messages
    4 262
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : tech lead c++ linux

    Informations forums :
    Inscription : Août 2004
    Messages : 4 262
    Points : 6 680
    Points
    6 680
    Billets dans le blog
    2
    Par défaut
    En fait, cela va dépendre de ton contexte (le fait de faire tous ces new n'est pas forcément mauvais), mais après je pense que quoi qu'il en soit, le plus simple sera sans doute une factory. Et si possible, en respectant le RAII, c'est à dire dans ton cas, en collectant toutes les données avant l'instanciation, en en passant toutes les données directement au constructeur.
    Mais comme j'ai dit, cela dépend de ton contexte. Si par exemple tu ne peux pas récolter les données avant l'instanciation, ou si tu souhaite stocker tes colis dans un conteneur et appliquer des algorithmes dessus, ou si tu ne vas utiliser tes colis que sous forme volatile, etc. Ça va dépendre, en gros, de la "vie" de tes colis.
    La vérité se trouvera certainement quelque part entre les différentes pistes que nous t'avons présenté.
    « L'effort par lequel toute chose tend à persévérer dans son être n'est rien de plus que l'essence actuelle de cette chose. »
    Spinoza — Éthique III, Proposition VII

  7. #7
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Points : 13 017
    Points
    13 017
    Par défaut
    Salut,
    Le pattern décorateur permet de rajouter et/ou d'altérer les propriétés d'un objet en lui adjoignant un décorateur. Il contient une instance de l'objet décoré pour au moins 2 raisons. Cela évite d'avoir à imposer une contrainte de copie sur l'objet de base. Ainsi, ce pattern est souvent utilisé dans les I.H.M. où on peut alors faire évoluer le comportement d'un widget qu'on ne peut recréer par dessus un autre. La seconde raison est que la décoration peut être temporaire et in-fine on peut vouloir récupérer l'objet de base.
    R0d mentionne le RAII. C'est vrai que par facilité je n'ai pas mis en place de pointeurs intelligents et c'est certainement un tord.
    Si ton objet supporte la copie et que tu souhaites créer un nouvel objet à partir du précédent, alors tu peux garder l'idée d'une hiérarchie de Colis, mais utiliser une fabrique pour créer un nouveau Colis à partir du précédent dans le type spécialisé adéquat. A la réflexion, je me demande si ce n'est pas vraiment ça ton besoin :
    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
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    #include <iostream>
    class Colis{
    	public:
    		virtual int get_destination() const=0;
    		int poids;
    		int taille;
     
    		virtual ~Colis()=0;
    };
    Colis::~Colis(){}
     
    class Colis_Indetermine : public Colis
    {
       public:
       virtual int get_destination()const
       {
          throw "un colis indetermine n'a pas de destination";
       }
       ~Colis_Indetermine(){}
    };
     
    class Colis_Fragile: public Colis {
    	public:
    	Colis_Fragile(Colis const &base_):Colis(base_){}
    	int get_destination()const{
    	   std::cout<<"je suis fragile !"<<std::endl;
    	   return 1;
       }
    	int pression_max;
    };
     
    class Colis_Normal: public Colis{
    	public:
    	Colis_Normal(Colis const &base_):Colis(base_){}
    	int get_destination()const{
    	   std::cout<<"je suis normal !"<<std::endl;
    	   return 2;
       }
    };
     
    class Colis_Solide: public Colis{
    	public:
    	Colis_Solide(Colis const &base_):Colis(base_){}
    	int get_destination()const{
    	   std::cout<<"je suis solide !"<<std::endl;
    	   return 3;
       }
    	char *type_de_materiau;
    };
     
    enum E_Type_Colis{
       TYPE_FRAGILE,
       TYPE_NORMAL,
       TYPE_SOLIDE
    };
    template<class istreamT>
    istreamT& operator>>(istreamT &in_, E_Type_Colis&val_)
    {
       int i;
       in_>>i;
       switch(i){
          case 0:
          val_ = TYPE_FRAGILE;
          break;
          case 1:
          val_ = TYPE_NORMAL;
          break;
          case 2:
          val_ = TYPE_SOLIDE;
          break;
          default:
          throw "valeur invalide";
          break;
       }
       return in_;
    }
    #include <boost/shared_ptr.hpp>
     
    class FabriqueColis
    {
    public:
       boost::shared_ptr<Colis> CreerColis(E_Type_Colis type_,Colis const &source_) const
       {
       switch(type_){
          case TYPE_FRAGILE:
          return boost::shared_ptr<Colis>(new Colis_Fragile(source_));
          break;
          case TYPE_NORMAL:
          return boost::shared_ptr<Colis>(new Colis_Normal(source_));
          break;
          case TYPE_SOLIDE:
          return boost::shared_ptr<Colis>(new Colis_Solide(source_));
          break;
          default:
          throw;
       }
     
       }
    };
     
    #include <iostream>
    int main()
    {
       boost::shared_ptr<Colis> mon_colis(new Colis_Indetermine);
     
       mon_colis->poids = 1;
       mon_colis->taille = 1;
       E_Type_Colis type;
       std::cout<<"De quel type est le colis (0 fragile, 1 normal, 2 solide) ?";
       std::cin>>type;
       FabriqueColis ma_fabrique;
       mon_colis = ma_fabrique.CreerColis(type,*mon_colis);
       mon_colis->get_destination();
       return 0;
    }

  8. #8
    Nouveau Candidat au Club
    Inscrit en
    Septembre 2009
    Messages
    5
    Détails du profil
    Informations forums :
    Inscription : Septembre 2009
    Messages : 5
    Points : 1
    Points
    1
    Par défaut
    Merci pour toutes ces techniques, je vais certainement trouver le bon compromis propreté/lourdeur/facilité d'utilisation.

    Pour l'aspect philosophique de la question:
    Je vois qu'on est pas a court de workaround pour réaliser des cast sécurisés vers des classes filles. dommage qu'il n'y ait rien de prévu en c++ de base et qu'il faille passer par ces techniques puissantes.
    Si on regarde au niveau gestion mémoire des classes et comment elles sont utilisées tout au fond par le compilateur, il doit y avoir une allocation de faite pour chaque new qui comprends certainement une table de pointeurs de fonctions (les méthodes) une zone d'informations pour les hiérarchies de classe et une zone pour les données membre.
    Une classe héritée ayant normalement plus d'informations, on peut comprendre qu'un cast vers une fille d'une classe créée mère ne contienne pas assez d'informations (il manquerait en gros les spécificités de la fille)
    En comparaison avec une structure, il existe l'union, qui prends la plus grande de tout ce qui est dans l'union, et qui permet de gérer indifféremment l'espace mémoire alloué comme étant d'un type ou d'un autre.
    Le truc idéal là serait une sorte d'union de classe, qui garde le type de la création (polymorphisme) et qui soit capable lors d'une demande "d'évolution" d'appeler le "constructeur de complément" et qui repositionne bien comme il faut les informations de la classe, l'identifiant ainsi comme si elle avait été créée depuis le début comme la classe fille.
    On pourra rafistoler ça avec toutes les méthodes que l'on veut, tant que ce ne sera pas fait au niveau du compilateur, on sera obligé de faire des copies d'objets, d'utiliser des pointeurs contenant les zones "spécifiques" et de gérer tout ce beau monde.
    quand on pense que le cast d'une fille vers une mère prends 1 ligne c++, et que pour arriver à un concept inversé il faut plusieurs pages, je me dis qu'il y a peut être une évolution du langage à faire ^_^
    Pour prendre une analogie sous forme de boite (encore lol ^_^) si une chaine de fabrication doit transporter différents groupes d'articles (donc chaque groupe peut avoir une taille différente) la chaine va surement choisir pour le transport une boite capable de prendre le pire des cas, plutôt que de devoir se limiter à une boite moyenne et d'ajouter des petites boites autour pour avoir suffisemment de place.
    Et dans un environnement calibré et controllé, on aura certes des boites plus grandes, mais jamais plus d'une à gérer, ni de transvasement d'une boite dans une autre à faire.
    En dehors de ces questions philosophiques, vos contributions m'auront permis d'y voir plus clair sur les choix possible, en tout cas merci ^_^

  9. #9
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Points : 13 017
    Points
    13 017
    Par défaut
    La faille de ton raisonnement, c'est qu'on ne peut savoir à priori quelle sera la taille des classes dérivées d'une classes de base. Si j'ai un programme :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    class IInterface{
    // définition
    };
     
    int main()
    { 
       IInterface &ri = CreerMonObjet();
    }
    CreerMonObjet peut très bien être définie dans une DLL qui n'est pas connu du programme de base. Donc, comment pourrait-on savoir quelle est la bonne taille de IInterface ?

    En fait, il faut faire attention à ne pas essayer de résoudre par le langage des problèmes issus de la conception. Et un problème typique est de penser qu'un objet physique correspond toujours à une seule instance d'une même classe. Je ne connais pas ton problème exact, mais dans le cadre de l'exemple que tu imagines, on peut penser qu'une conception pertinente consiste à considérer ton colis comme un objet ColisNu tant qu'on n'a pas déterminé ses caractéristiques de fragilités, puis devenir un nouvel objet Colis_[Fragile/Normale/Solide] une fois ces caractéristiques connues. Cela peut être pertinent par exemple si on veut être sur qu'une chaine ne sera alimentée que par des colis déterminés, on assure par ce typage une vérification à la compilation.
    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
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    #include <iostream>
    #include <boost/shared_ptr.hpp>
     
    struct ColisNu
    {
       int poids;
       int taille;
       ColisNu(){
          std::cout<<"Creation colis nu"<<std::endl;
       }
       ColisNu(ColisNu const &){
          std::cout<<"Creation d'une copie de colis nu"<<std::endl;
       }
       ~ColisNu()
       {
          std::cout<<"Destruction d'un colis nu"<<std::endl;
       }
    private:
       ColisNu&operator=(ColisNu const&);
    };
    class Colis{
    	public:
          Colis()
             :colis_nu(new ColisNu)
          {std::cout<<"Creation d'un colis"<<std::endl;}
          Colis(boost::shared_ptr<ColisNu> const &ptr_colis_nu)
             :colis_nu(ptr_colis_nu)
          {std::cout<<"Creation d'un colis"<<std::endl;}
          Colis(Colis const &rhs_)
             :colis_nu(rhs_.colis_nu)
          {std::cout<<"Creation d'un colis"<<std::endl;}
    		virtual int get_destination() const=0;
    		virtual ~Colis()=0;
          boost::shared_ptr<ColisNu> colis_nu;
       private:
          Colis&operator=(Colis const&);
    };
    Colis::~Colis()
    {
       std::cout<<"Destruction d'un colis"<<std::endl;
    }
     
    class Colis_Fragile: public Colis {
    	public:
    	Colis_Fragile(boost::shared_ptr<ColisNu> const &ptr_colis_nu):Colis(ptr_colis_nu){}
    	int get_destination()const{
    	   std::cout<<"je suis fragile !"<<std::endl;
    	   return 1;
       }
    	int pression_max;
    };
     
    class Colis_Normal: public Colis{
    	public:
    	Colis_Normal(boost::shared_ptr<ColisNu> const &ptr_colis_nu):Colis(ptr_colis_nu){}
    	int get_destination()const{
    	   std::cout<<"je suis normal !"<<std::endl;
    	   return 2;
       }
    };
     
    class Colis_Solide: public Colis{
    	public:
    	Colis_Solide(boost::shared_ptr<ColisNu> const &ptr_colis_nu):Colis(ptr_colis_nu){}
    	int get_destination()const{
    	   std::cout<<"je suis solide !"<<std::endl;
    	   return 3;
       }
    	char *type_de_materiau;
    };
     
    enum E_Type_Colis{
       TYPE_FRAGILE,
       TYPE_NORMAL,
       TYPE_SOLIDE
    };
    template<class istreamT>
    istreamT& operator>>(istreamT &in_, E_Type_Colis&val_)
    {
       int i;
       in_>>i;
       switch(i){
          case 0:
          val_ = TYPE_FRAGILE;
          break;
          case 1:
          val_ = TYPE_NORMAL;
          break;
          case 2:
          val_ = TYPE_SOLIDE;
          break;
          default:
          throw "valeur invalide";
          break;
       }
       return in_;
    }
    #include <boost/shared_ptr.hpp>
     
    class FabriqueColis
    {
    public:
       boost::shared_ptr<Colis> CreerColis(E_Type_Colis type_,boost::shared_ptr<ColisNu> const &ptr_colis_nu_) const
       {
       switch(type_){
          case TYPE_FRAGILE:
          return boost::shared_ptr<Colis>(new Colis_Fragile(ptr_colis_nu_));
          break;
          case TYPE_NORMAL:
          return boost::shared_ptr<Colis>(new Colis_Normal(ptr_colis_nu_));
          break;
          case TYPE_SOLIDE:
          return boost::shared_ptr<Colis>(new Colis_Solide(ptr_colis_nu_));
          break;
          default:
          throw;
       }
     
       }
    };
     
    void TraiterColis(boost::shared_ptr<Colis>&colis_)
    {
       colis_->get_destination();
    }
    int main()
    {
       boost::shared_ptr<ColisNu> mon_colis_nu(new ColisNu);
       E_Type_Colis type;
       std::cout<<"De quel type est le colis (0 fragile, 1 normal, 2 solide) ?";
       std::cin>>type;
       FabriqueColis ma_fabrique;
       boost::shared_ptr<Colis> mon_colis = ma_fabrique.CreerColis(type,mon_colis_nu);
     
       TraiterColis(mon_colis);// OK
       //TraiterColis(mon_colis_nu);// Erreur à la compilation
       return 0;
    }
    Là, un seul objet ColisNu est créé et un seul objet Colis est créé.


    [EDIT] : convertir une classe dérivée vers une classe de base me parait être une formulation sinon erronée au moins bancale. Une classe dérivée (publiquement s'entend) EST-UNE classe de base un peu particulière avec des choses en moins. Oui en moins et c'est pour ça qu'une classe de base ne peut être convertie vers une classe dérivée automatiquement (au passage, la nécessité d'un dynamic_cast traduit souvent un problème de conception). Pourquoi en moins ? Parce qu'en ajoutant des propriétés (attributs/méthodes) à la classe de base, la classe dérivée diminue le champs des possibles que pouvait emprunter la classe de base. Ainsi, Colis ne doit assurer qu'une cohérence de colis_nu et n'a que faire d'un type de matériaux ou d'une pression max. Colis_Fragile EST-UN Colis ET une pression max. Dans une approche ensembliste, on pourrait dire que l'ensemble des instances de Colis_Fragile est un sous-ensemble des instances de Colis.

  10. #10
    Rédacteur/Modérateur
    Avatar de JolyLoic
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2004
    Messages
    5 463
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Yvelines (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 5 463
    Points : 16 213
    Points
    16 213
    Par défaut
    Convertir une instance d'un classe dérivée en instance de classe de base est souvent une erreur aussi. Ce qu'on fait généralement, c'est considérer une instance d'une classe dérivée comme si elle n'était qu'une instance de la classe de base (cette notion de considération s'écrivant comme une conversion de pointeur ou de référence, pas une conversion d'objet).

    Quand à convertir une instance d'une classe de base en instance de la classe dérivée, outre qu'il faudrait ajouter des choses, il faudrait peut-être les ajouter pas à la fin, mais au beau milieu, ce qui rend la chose bien plus complexe. Par exemple, si on a A, B dérivant virtuellement de A, C dérivant de B, on peut avoir en mémoire pour un objet de type C :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    Informations de type vers C
    Partie de A
    Partie de B
    Partie de C
    si maintenant on a un type D dérivant virtuellement de A, et E dérivant de D et B, on peut avoir en mémoire :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    Informations de type vers E
    Partie de A
    Partie de D
    Partie de B
    Partie de E
    E dérive bien de B, mais le layout d'un objet E n'est pas compatible avec celui d'un objet B.

    Par contre, créer un objet à partir d'un autre ne me semblerait pas une mauvaise idée. Finalement, dans ton exemple (j'ai lu en diagonale), c'est probablement ce qu'on ferait manuellement, non ? L'emballage est abimé, pas grave, on détermine le type d'emballage qu'il aurait du avoir, on prend un nouvel emballage et on transfère les objets de l'ancien déchiré au neuf, et on reballance le neuf sur la ligne.
    Ma session aux Microsoft TechDays 2013 : Développer en natif avec C++11.
    Celle des Microsoft TechDays 2014 : Bonnes pratiques pour apprivoiser le C++11 avec Visual C++
    Et celle des Microsoft TechDays 2015 : Visual C++ 2015 : voyage à la découverte d'un nouveau monde
    Je donne des formations au C++ en entreprise, n'hésitez pas à me contacter.

  11. #11
    Nouveau Candidat au Club
    Inscrit en
    Septembre 2009
    Messages
    5
    Détails du profil
    Informations forums :
    Inscription : Septembre 2009
    Messages : 5
    Points : 1
    Points
    1
    Par défaut
    je vois que ces problématiques ne sont pas innocentes, et plutôt passionnantes

    Tout ça me fait penser un peu au chat de schroedinger, où l'on ne sait pas si le chat est mort ou vivant, donc il serait à la foi mort et vivant.
    La différentiation entre ces deux états n'étant faite qu'au moment de l'analyse.
    De la même façon qu'un colis_nu n'est pas encore déterminé comme fragile normal ou solide tant qu'on ne l'aura pas analysé.

    Mais cela veut t'il dire qu'un colis_nu est une esquisse de colis à compléter ou plutôt TOUT les colis en même temps?

    Une analogie de la nature (qui fait souvent bien les choses) nous montre l'histoire des cellules souches.
    Elles commencent leur vie comme Totipotente (capable de générer un corps humain entier)
    Puis se spécialisent deviennent Pluripotente (capable de générer un groupe de cellules/organes)
    Puis se spécialisent encore et deviennent Multipotente (capable de générer quelques type de tissus)
    Puis se spécialisent encore et deviennent Unipotente (capable de générer qu'un seul type de tissu)

    Bref tout ça pour dire que dans cette hiérarchie de la vie, on a bien une relation d'héritage en cascade, à la différence que l'ancètre le plus éloigné est capable de faire plus que le dernier enfant.
    Et si une cellule était un objet, elle aurait la capacité de se spécialiser et de "perdre" au fur et à mesure des capacités jusqu'à devenir à la fin de l'histoire l'objet bien spécifique identifié.

    En c++ cela pourrait s'apparenter à un héritage multiple, mais les classes les plus simples n'auraient pas la capacité de perdre des informations et seraient gigantesques.

    Je me suis orienté pour résoudre mon problème vers une simple recopie d'objet dans le bon sous-type, c'est ce que j'ai trouvé de plus sage ^_^

Discussions similaires

  1. Polymorphisme de classe ?
    Par Neo41 dans le forum C++
    Réponses: 16
    Dernier message: 22/04/2007, 15h04
  2. Réponses: 4
    Dernier message: 25/01/2007, 21h11
  3. Réponses: 4
    Dernier message: 16/01/2007, 15h27
  4. Réponses: 36
    Dernier message: 09/09/2006, 03h06
  5. polymorphisme, pointeurs et classes abstraites
    Par legend666 dans le forum C++
    Réponses: 10
    Dernier message: 02/11/2005, 16h44

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