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 :

méthode avec un type de retour variable, erreur de conception ou polymorphisme ?


Sujet :

Langage C++

  1. #1
    Membre du Club
    Profil pro
    Inscrit en
    Mars 2008
    Messages
    39
    Détails du profil
    Informations personnelles :
    Âge : 36
    Localisation : France

    Informations forums :
    Inscription : Mars 2008
    Messages : 39
    Points : 52
    Points
    52
    Par défaut méthode avec un type de retour variable, erreur de conception ou polymorphisme ?
    Bonjour,

    Suite a 2 jours de discutions sur le Chat je viens ici pour essayer de mettre au clair certains problèmes de conception et de réalisation.

    Donc, déjà je pose le cadre de mon projet.

    Je travail sur la rétroprojection d'une base de donnée, La base d'origine ne dispose pas des moyens d’accès standard (ODBC) ni même de driver spécifique. Par contre la structure et les donnée sont facilement récupérable en format binaire.
    J'ai déjà développer la partie lecteur du parseur, et je travail sur le stockage en mémoire (avant la restitution dans une base cible, PostgreSQL dans mon cas).
    Pour stocker la mémoire j'ai décomposer la structure d'une base de donnée comme suit :

    Une table contient plusieurs lignes d'enregistrement (std::vector<lineRec>)
    Une ligne d'enregistrement contiens plusieurs cellule (std::vector<Field>)
    Une cellule contiens une donnée, une longueur (besoin spécifique a mon projet détachée de toute notion de longueur de champ existant en bdd) et un type.
    Mon implémentation actuelle :
    • 7 type (f_chaine, f_date, f_monnaie, f_clé, ...) chacun hérite de Field et contient une donnée typée et un méthode pour récupérer cette donnée (getter dans sa forme la plus simple)
    • 1 type abstrait Field qui contient l'attribut de longueur, des méthodes virtuelle pur (read, clone, display), des méthode (getter, constructeur)
    • 1 type ligneRec qui contiens un vecteur de pointeur (std::vector<std:unique_ptr<Field>>)vers les Field + un taille de ligne, et des méthode (constructeur, outils de paramétrage depuis un fichier, 2 getter)
    • 1 type table qui contient un vecteur de ligneRec.


    Algo d'execution :
    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
     
    Table.BuildFromFile() :
        /* Ici en plus de générer un model (inutile dans la suite de l'exemple) je 
         * crée une ligne model, ce model contient les longueur et type de chaque
         * colonne (attribut longueur + instance de f_***.
         */
        modelDeLigne = initStructFromCSV(uriCSVFile);
        loadDataFromBinary(uriBinFile);
    -------------------------------------
    Table.loadDataFromBinary(uriBinFile) :
        Ouvrir (uriBinFile);
        Tant que ( != fin de fichier)
            vecLigne.push_back(lineRec.readLine(modelDeLigne));
        FTQ
    -------------------------------------
    LineRec.readLine(modelDeLigne) :
        Nouvelle ligne de type lineRec
        Tant que (!= fin de ligne model)
            /* Ici la méthode Read est surchargé dans les f_*** ce qui fait que le 
             * traitement d'un ligne n'est connu qu'a l’exécution, par composition de 
             * traitement de champs
             */
            line.add(Field.read())
        FTQ
        retourner line
    Jusqu'ici tout fonctionne. Tout ce qui est écrit au dessus marche en l'état (avec les bon constructeur par copie, type etc ....)

    Ma question maintenant c'est que je souhaite récupérer mes données, Or Field n'as pas de méthode getData() puisque le type de retour dépend de l'instance appeler (f_chaine renverra un string, f_date la structure tm, f_monnaie un double , ...)
    Et c'est précisément sur ce point que je suis bloqué. J'aimerait définit une méthode virtuelle ou le type de retour n'est définit que dans les niveau inférieur.
    Sur le chat on m'as tout de suite énoncer un problème de conception. Mais comme toute personne borné que je suis, je ne voit pas comment faire autrement. Cette construction est issue d'un point clé auquel je tiens peut-être a tord qui est que le traitement d'une ligne n'est pas définit à la compilation mais à l’exécution (vecteur de Field sur lequel j'appelle la méthode délégué read).


    En tout cas, merci d'avance (et oui, c'est pas tout clair dans ma tête ^^)

  2. #2
    Membre émérite
    Profil pro
    Inscrit en
    Novembre 2004
    Messages
    2 764
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2004
    Messages : 2 764
    Points : 2 705
    Points
    2 705
    Par défaut
    Ça m'a l'air compliqué, ton histoire.

    Pourquoi ton lineRec n'est-il pas simplement une classe avec des membres (correspondant aux champs) de différent type, la classe se chargeant de renvoyer les données ?

    Si tu restes sur ta méthode, je te conseille de te renseigner sur le CRTP (évoqué dans la FAQ, je pense).

  3. #3
    Membre du Club
    Profil pro
    Inscrit en
    Mars 2008
    Messages
    39
    Détails du profil
    Informations personnelles :
    Âge : 36
    Localisation : France

    Informations forums :
    Inscription : Mars 2008
    Messages : 39
    Points : 52
    Points
    52
    Par défaut
    Car je travail pas seulement sur une table mais sur plusieurs (~120 table par base * 58 base)
    et que la structure même de ces tables n'est pas fixe dans le temps (changement de structure à chaque mise à jour)

    Pour le CRTP je vais voir de ce pas

    Edit :
    Mais du coup, quand je passe en CRTP, je doit passer sous template toute les classe qui utilise Field ?

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

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2004
    Messages
    4 264
    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 264
    Points : 6 683
    Points
    6 683
    Billets dans le blog
    2
    Par défaut
    C'est un problème assez récurrent: on a une donnée et on veut la proposer avec des types différents.

    En c++, il y a 3 solutions principales:
    1/ On gère tout avec des chaines de caractère, et on transforme qu'au moment des traitements. L'avantage, c'est que si c'est juste de l'affichage, tu n'auras jamais besoin de convertir. Le désavantage évident c'est les performances. Mais si les développeurs c++ aiment bien optimiser leur code, dans la vraie vie c'est rarement crucial.
    2/ L'implémentation du getValueAsXXX(); avec un get par type. C'est moche car peu évolutif et surtout, il faudra à un endroit ou à un autre poser un gros switch bien gras. Mais c'est techniquement la plus simple à mettre en place.
    3/ Utilisation de template. Sans aller jusqu'au CRTP, pourquoi ne pas templatiser ta classe Field? Si c'est pour une raison de stockage, alors tu peux faire un "chechire cat" (ou pimpl), ou peut-être une simple délégation par agrégation. Parce qu'en fait, déjà, si ta classe Field n'est pas template, comment est-ce que tu stocke ta donnée?

  5. #5
    Membre du Club
    Profil pro
    Inscrit en
    Mars 2008
    Messages
    39
    Détails du profil
    Informations personnelles :
    Âge : 36
    Localisation : France

    Informations forums :
    Inscription : Mars 2008
    Messages : 39
    Points : 52
    Points
    52
    Par défaut
    Bon du coup j'ai regarder du coté de CRTP, ça me permet effectivement de remonter data dans ma classe mère Field.

    Par contre je me retrouve face un un autre problème,
    lineRec contient
    std::vector<unique_ptr<Field>>
    et je ne peut pas spécialiser field puisque je fais avoir des field<std::string> des field<short int> des field<double> et d'autres.

    j'ai regarder du coté de Type Erasure, mais çà ne me conviens pas non plus puisque mon template Derived induit par le CRTP est utiliser comme Paramètre de retour de ma méthode getData()...
    Du coup en utilisant CRTP soit je perd l’accès à mes données, soit j'ai pas tout compris a type erasure

    Citation Envoyé par r0d Voir le message
    C'est un problème assez récurrent: on a une donnée et on veut la proposer avec des types différents.

    En c++, il y a 3 solutions principales:
    1/ On gère tout avec des chaines de caractère, et on transforme qu'au moment des traitements. L'avantage, c'est que si c'est juste de l'affichage, tu n'auras jamais besoin de convertir. Le désavantage évident c'est les performances. Mais si les développeurs c++ aiment bien optimiser leur code, dans la vraie vie c'est rarement crucial.
    Pour mon application, le temps d’exécution est un paramètre a prendre en compte. Etant donnée que mon programme tend à devenir une procédure de maintenance sur un système en production je n'aurais qu'une plage de temps pour l’exécution. Donc le temps joue contre moi.

    Citation Envoyé par r0d Voir le message
    2/ L'implémentation du getValueAsXXX(); avec un get par type. C'est moche car peu évolutif et surtout, il faudra à un endroit ou à un autre poser un gros switch bien gras. Mais c'est techniquement la plus simple à mettre en place.
    C'est mon précédent système et je cherche a trouver plus performant (pour ma part c’était pas un switch mais un if/else if/else if/... sur mes 7 types de base)


    Citation Envoyé par r0d Voir le message
    3/ Utilisation de template. Sans aller jusqu'au CRTP, pourquoi ne pas templatiser ta classe Field? Si c'est pour une raison de stockage, alors tu peux faire un "chechire cat" (ou pimpl), ou peut-être une simple délégation par agrégation. Parce qu'en fait, déjà, si ta classe Field n'est pas template, comment est-ce que tu stocke ta donnée?
    Jusqu’à présent je n'avais pas templatiser ma class Field car je ne m’était pas poser la question de récupéré les donnée, mais effectivement, je suis obliger de templatiser, et c'est bien le stockage qui me bloc une fois templatiser.


    Je vais de ce pas jeter un œil à "chechire cat", pimpl et la délégation par agrégation.

  6. #6
    Membre émérite
    Profil pro
    Inscrit en
    Novembre 2004
    Messages
    2 764
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2004
    Messages : 2 764
    Points : 2 705
    Points
    2 705
    Par défaut
    Citation Envoyé par nacrotic Voir le message
    j'ai regarder du coté de Type Erasure, mais çà ne me conviens pas non plus puisque mon template Derived induit par le CRTP est utiliser comme Paramètre de retour de ma méthode getData()...
    Du coup en utilisant CRTP soit je perd l’accès à mes données, soit j'ai pas tout compris a type erasure
    Tout dépend comment tu comtpes utiliser la donnée que tu récupères.
    Tu peux très bien faire :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    auto monChamp = monVecteur[5]->getData();
    Si c'est juste à envoyer dans un flux, par exemple, tu peux alors faire :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    std::cout << monChamp << std::endl;
    Plus généralement, tu templatises ce qui utilise la variable monChamp.

  7. #7
    Membre expérimenté Avatar de Trademark
    Profil pro
    Inscrit en
    Février 2009
    Messages
    762
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2009
    Messages : 762
    Points : 1 396
    Points
    1 396
    Par défaut
    En lisant comme ça, au niveau du design je pense que ce qui est le mieux est un CRTP + pattern visiteur. Par contre avant que je me lance dans des explications à rallonge, je te dis tout de suite, faut pas se leurrer, avec de l'héritage et des méthodes virtuelles ça sera plus lent qu'avec un switch (ou des if-else ce qui est pareil).

    Note : Transforme les pointeurs brutes en unique_ptr ou shared_ptr suivant 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
    // Classe de base, type erasure comme on dit.
    class FieldBase
    {
    public:
      virtual void accept(FieldVisitor* visitor) = 0;
    };
     
    // La fameuse classe appliquant le CRTP
    template <FieldData>
    class Field : public FieldBase
    {
    public:
      // Redéfinit la méthode accept.
      virtual void accept(FieldVisitor* visitor)
      {
        // On passe au visiteur le type sous-jacent de Field.
        visitor.visit(*static_cast<FieldData*>(this));
      }
    };
     
    class f_date : public Field<f_date>
    {
      // f_date details.
    };
     
    class f_string : public Field<f_string>
    {
      // f_string details.
    };
     
    // Un visiteur de base pour visiter les types de Field.
    class FieldVisitor
    {
    public:
      virtual visit(f_date&) = 0;
      virtual visit(f_string&) = 0;
      // ...
    };
     
    // Un visiteur concret qui transforme tes Field vers des Field PostGre (pure fiction).
    class VisitorField2PostgreField : public FieldVisitor
    {
      PostGreField &field;
    public:
      VisitorField2PostgreField(PostGreField& field)
      : field(field)
     
      virtual void visit(f_date& date)
      {
        // transforme date en champs postgre.
       field = ...;
      }
     
      virtual void visit(f_string& s)
      {
        // transforme string en champs postgre.
       field = ...;
      }
      // ...
    };
    Et à l'utilisation tu fais :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    std::vector<FieldBase*> fields;
    // ...
    VisitorField2PostgreField* visitor = new VisitorField2PostgreField(postgrefield);
     
    field[0]->visit(visitor);
     
    // postgrefield peut être utilisé...
    L'avantage c'est que tu n'as besoin que de recréer un visiteur à chaque fois que tu veux transformer tes Field vers quelques choses d'autres.

    PS : J'ai pas testé le code...

  8. #8
    Membre du Club
    Profil pro
    Inscrit en
    Mars 2008
    Messages
    39
    Détails du profil
    Informations personnelles :
    Âge : 36
    Localisation : France

    Informations forums :
    Inscription : Mars 2008
    Messages : 39
    Points : 52
    Points
    52
    Par défaut
    Citation Envoyé par Trademark Voir le message
    En lisant comme ça, au niveau du design je pense que ce qui est le mieux est un CRTP + pattern visiteur. Par contre avant que je me lance dans des explications à rallonge, je te dis tout de suite, faut pas se leurrer, avec de l'héritage et des méthodes virtuelles ça sera plus lent qu'avec un switch (ou des if-else ce qui est pareil).
    you break my dreams

    Je garde ton idée sous le coude quand même parce que c'est intéressant, mais du coup je rame pour rien depuis 2 jours

  9. #9
    Membre expérimenté Avatar de Trademark
    Profil pro
    Inscrit en
    Février 2009
    Messages
    762
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2009
    Messages : 762
    Points : 1 396
    Points
    1 396
    Par défaut
    Après, un code propre est souvent plus facile à optimiser (ou ouvre la porte vers des optimisations de plus grande envergure) ! Mais il faut que tu fasses des tests avec un profileur pour voir exactement où est-ce que ça prend du temps et optimiser cet endroit là, et je doute que ton profileur t'ait dit que c'était dans ton switch que le programme passait le plus de temps. En gros, fait un code correct et puis après tu profiles pour voir ce que donne les perfs et à ce moment là uniquement tu changes ce qu'il faut.

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

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2004
    Messages
    4 264
    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 264
    Points : 6 683
    Points
    6 683
    Billets dans le blog
    2
    Par défaut
    En fait on n'est pas forcément obligé d'utiliser de l'héritage. Moi je m'en étais sorti une fois juste avec des agrégations et des templates, mais je ne me souviens plus exactement. Il me semble que c'était un design dans lequel je ne stockais rien dans la structure de donnée (ici la classe Field), mais j'allais chercher la donnée lorsqu'on faisait le getValue(). C'est pour ça que je disais que l'implémentation concrète dépend vraiment des détails du projet.

  11. #11
    Membre du Club
    Profil pro
    Inscrit en
    Mars 2008
    Messages
    39
    Détails du profil
    Informations personnelles :
    Âge : 36
    Localisation : France

    Informations forums :
    Inscription : Mars 2008
    Messages : 39
    Points : 52
    Points
    52
    Par défaut
    Bonjour,

    excusez moi pour le temps de réponse, mais le fil de mon projet m'as mener vers d'autres horizons et j'ai oublier de revenir vous tenir au courant.

    En tous cas, merci à vous 3 d'avoir tenter de m'aider, mais comme il m'as était suggérer, il me fallais revoir l'objectif de l'utilisation des données. Dans mon cas puisque je formalise des requette sql apres, le format string me conviens parfaitement et j'ai pu implémenter un méthode display() pour récupérer la donnée correctement échappée.

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

Discussions similaires

  1. Méthode avec plusieurs types de retour
    Par baya1 dans le forum Langage
    Réponses: 14
    Dernier message: 10/03/2022, 00h04
  2. Travailler avec une méthode ayant plusieurs types de retour
    Par fafoula dans le forum Général Java
    Réponses: 4
    Dernier message: 27/07/2012, 10h23
  3. Réponses: 1
    Dernier message: 17/07/2011, 13h25
  4. Réponses: 3
    Dernier message: 01/02/2010, 15h54
  5. Réponses: 8
    Dernier message: 13/09/2007, 17h07

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