Partage
  • Partager sur Facebook
  • Partager sur Twitter

Template c++

    25 juillet 2020 à 13:17:06

    Bonjour à tous,

    Je m'exerce à créer des template.

    Pour commencer j'ai voulu créer une fonction qui permet de faire des inserts dans une base de données.

    J'utilise Qt et voici mon code je fais donc un appel à init ou je fais mes test (je devrais ptt renomer cette methode :D)

    MainWindow::MainWindow(QWidget *parent)
        : QMainWindow(parent)
        , ui(new Ui::MainWindow)
    {
        ui->setupUi(this);
       
        init(); // erreur du au template
    
    }
    template<typename T>
    bool MainWindow::init()
    {
    
    
          QList<QString> listColl;
        
          QList<T> listValue;
    
          listColl.append("Nom");       listValue.append("Raf");
          listColl.append("Prenom");    listValue.append("RafLeguerrier");
          listColl.append("Adresse");   listValue.append("Raf ou il habite");
          listColl.append("NumTel");    listValue.append("Lui même ne le sait");
          listColl.append("Login");     listValue.append("test");
          listColl.append("Password");  listValue.append("password");
          QMap<QList<QString>,QList<T>> mapInsert;
          mapInsert[listColl] = listValue;
          appUtil::insertDb("Personne",mapInsert);
    }

    Ma fonction qui gère l'insert est une fonction static de la classe appUUtil qui ne récupère les données.

    template<typename T>
    bool appUtil::insertDb(QString Table,QMap<QList<QString>,QList<T>> collumAndValues)
    {
        QString query="";
    
    
        QMapIterator<QList<QString>,QList<T>> i(collumAndValues);
        while (i.hasNext()) {
            QList<QString> collumns=i.key();
            QList<T>values=i.value();
            if((collumns.size() == values.size()) && collumns.size()>0)
            {
                for (int i=0;i<collumns.size();i++) {
                    if(i==0)query.append("INSERT INTO " +Table+" ("+collumns.takeAt(i));
                    if(collumns.size()>1)
                    {
                        query.append(","+collumns.takeAt(i));
                    }
                }
                query.append(")");
                for(i==0;i<values.size();i++)
                {
                    if(i==0)query.append("VALUES(" +values.takeAt(i) );
                    if(values.size()>1)
                    {
                        query.append(","+values.takeAt(i));
                    }
                }
                query.append(");");
            }
    
            i.next();
    
        }
    }

    Mon problème provient de l'init qui dans ça déclaration n'as pas de type T. Mais elle n'en a pas besoin. J'ai rater certaiinement un truc.

    Si vous avez des idées je suis preneur.

    Si vous avez de meilleurs idée pour gérer un insert dans une DB c'est le bien venu aussi :)


    • Partager sur Facebook
    • Partager sur Twitter
      25 juillet 2020 à 16:50:09

      Salut,

      Déjà, il faut savoir que les template ne sont utiles que ... lorsque tu veux pouvoir faire varier le type de tes données à la compilation. 

      Autrement dit, lorsque tu te trouves face à une fonction pour laquelle tu te dis que

      Je ne sais pas encore quels types de donnée cette fonction aura à manipuler, mais voici déjà comment elle devra les manipuler

      Dans le code que tu présente, il semble clair que ta donnée QList<T> listValues soit destinée à contenir des données de type ... chaines de caractères.  Il n'y a donc -- a priori -- aucune raison de venir "jouer" avec les templates ici : listValues devrait donc être, tout simplement, une QList<QString>  ;)

      De plus, tu pars très certainement d'un très mauvais pied en plaçant tes données dans deux listes séparées, car, si on y regarde un peu plus près, les données qui se retrouvent dans listCol sont intrinsèquement liées aux données correspondantes que l'on trouve dans listValues.

      En effet, les données que l'on trouvent dans listCol décrivent l'usage auquel est destinée la données correspondante dans listValues: si tu parles du nom, tu veux obtenir "Raf", à l'exception de toute autre valeur, et tu ne veux surtout pas obtenir "là ou raf habite" comme réponse :p

      Tu dois donc faire en sorte que la "description" (la donnée qui se trouve dans listCol) et la donnée désignée par cette description (la donnée équivalent dans listValues) ne puissent pas se balader de manière séparée.

      Pour cela, il y a deux solutions:

      Soit tu utilises une std::pair<QString, QString>; où la première QString représente la description et la deuxième représente la donnée en question, et tu stocke donc le tout dans un QList<std::pair<QString, QString>> listDatas.

      Soit tu crées, carrément, une structure proche de

      struct CharacterData{ // parce qu'elle maintient les données
                            // d'un personnage particulier
          QString characteristic; // Que représente la donnée que j'observe?
          QString value;          // Quelle est la valeur de cette donnée?
      
      };

      et tu place cette structure dans une QList<CharacterData> listDatas.

      Et, par la suite, si tu as plusieurs personnages, tu peux bien sur créer une liste des personnages reprenant leurs caractéristiques sous la forme de
      // dans le premier cas
      QMap<QString, // ca, c'est le nom du personnage
           QList<std::pair<QString, QString>>> characters; 
      
      /*  OU   OU   OU */
      QMap<QString, // ca c'est le nom du personnage
           QList<CharacterData>> characters;

      Le mieux étant d'ailleurs sans doute de créer un alias de type pour représenter la list des données de personnages, sous une forme proche de

      /* Dans le premier cas */
      using CharacterData_t = std::pair<QString, QString>;
      using ChartacterDataList_t = QList<CharacterData_t>;
      using CharacterList_t = QMap<QString, CharacterDataList_t>; // tant qu'à faire, ce sera plus facile par la suite
      
      CharacterList_t  characters;
      
      /* OU OU OU, dans le deuxième cas */
      struct CharacterData{
          QString characteristic;
          QString value;
      };
      using ChartacterDataList_t = QList<CharacterData>;
      
      using CharacterList_t = QMap<QString, CharacterDataList_t>; // tant qu'à faire, ce sera plus facile par la suite
      
      CharacterList_t  characters;

      Bon, bien sur, tout cela ne répond pas à ta question d'origine :P.  Cependant, il me semblait important de mettre ce point en évidence, car cela te facilitera le travail par la suite ;)

      Pour ce qui est de l'insertion dans la base de données...

      L'un dans l'autre, au point ou tu en est, tu n'est plus dans une situation dans laquelle l'utilisation des template peut se justifier.  Je m'explique:

      Si tu veux rajouter les données qui se trouvent dans ta map characters, tu sais pertinemment que tu vas rajouter ... des personnages, à l'exception de toute autre possibilité (comme le fait de rajouter des  armes, des sorts ou des potions dans ta base de données).

      Il n'y a donc aucune raison de se faire du mal à essayer de faire quelque chose de générique, alors que tu as toutes les informations nécessaires te permettant de faire quelque chose de spécifique ;)

      Crées toi donc une fonction insertCharactersInDB qui prendra ta map de personnages, et fais en sorte d'insérer les différents personnages, sous une forme proche de

      void insertCharactersInDb(CharacterList_t  const & list){
         /* La syntaxe d'une insertion dans une DB est généralement
          * insert into <table> set (field1, field2, field3, ..)
          *        values 'value1', 'value2','value3', ...;)
          *
          * il existe d'autres pssibilités de s'y prendre, mais 
          * le plus facile ici est peut être de générer des 
          * chaines de caractères distinctes qui seront regroupées
          * par la suite
          */
          QString insert{"insert into characters "};// le début de la requête, qui ne changera pas
          for(auto const & [name, values] : list){ 
             /* Pour chaque perso, il faut une chaine "set(...)"
              *  et une chaine "values( ... )"
              */
             QString set{"set("};
             QString val{"values ("};
             /* parce qu'il y a un traitemet spécial
              * pour le dernier élément
              */
             int index{1};
             for(auto const & [characteristic, value] : values){
                 set.append(characteristic);
                 val.append(value);
                 if(index < values.size()){
                     set.append(",");
                     val.append(",");
                 }
             }
             /* n'oublions pas de fermer les parenthèses  */
             set.append(") ");
             val.append(") ");
             /* on peut maintenant créer la requete complète */
             insert.append(set)
                   .append(val)
                   .append(";");
             /* on a le bon texte pour la requete, y a plus qu'à 
              * la faire exécuter, je te laisse faire  
              */
          } 
      }

      notes que tu pourrais aussi plutot faire en sorte de n'insérer qu'un seul personnage à la fois, ce qui serait sans doute plus intelligent, d'ailleurs.

      on pourrait donc avoir quelque chose qui serait proche de

      void insertCharacterInDb(ChartacterDataList_t const & list){
          QString insert{"insert into characters "};
          QString set{"set("};
          QString val{"values ("};
          int index{1};
          for(auto const & [characteristic, value] : list){
          set.append(characteristic);
                 val.append(value);
                 if(index < values.size()){
                     set.append(",");
                     val.append(",");
                 }
             }
             set.append(") ");
             val.append(") ");
             insert.append(set)
                   .append(val)
                   .append(";");
             /* on a le bon texte pour la requete, y a plus qu'à 
              * la faire exécuter, je te laisse faire  
              */
          } 
      }
      void insertAllCharactersInDb(CharacterList_t const & list){
          for(auto const & [name, datas] : list){
              insertCharacterInDb(datas);
          }
      }
      

      Et si tu veux, plus tard, pouvoir rajouter des sorts, des potions, des armes ou que sais-je dans ta base de données, il faudra créer les fonctions adaptées ;)

      Comprends moi bien: on pourrait -- effectivement -- s'amuser à créer une fonction template qui s'occupe, selon le type de la donnée à insérer dans la base de donnée, de générer la requête adéquate.

      Seulement, cela demandera un travail de fou, ca rendra le code plus complexe et sans doute beaucoup moins compréhensible, pour pas grand chose au final.

      Le fait est que, pour pouvoir le faire, il faudrait commencer par créer des traits de politique, un trait par type de donnée que tu veux insérer.  En effet, les données qui permettent de représenter un sort n'ont rien à voir avec les données qui permettent de représenter un personage ou une arme :p

      Si donc, tu avais des structures qui  ressemblent plus ou moins à quelque chose comme

      enum HandUsage{
          leftHand,    // utilise la main gauche exclusivement
          rightHand,   // utilise la main droite exclusivement
          handDontCare,// on peut l'utiliser indifférement de la
                       // main gauche ou de la main droite
          twoHands     // il faut les deux mains pour l'utiliser
                       
      };
      enum DamageTypes{
          heal     = 0,
          slash    = 1<<0,
          blunt    = 1<<1,
          piercing = 1<<2
          electric = 1<<3,
          fire     = 1<<4,
          /* et tous les autres*/
         
      };
      struct Weapon{
          QString name;       // c'est toujours sympa d'avoir le nom
          HandUsage hand;     // quelle main est utilisée
          DamageTypes damages;// le type de dégâts occasionnés
          int baseDamages;    // la quantité de dégâts occasionnés
          /* il manque peut-être quelque chose */
      };
      
      struct Spell{
          QString name;
          DamageTypes damages;
          int baseDamages;
          /* il manque peut être quelque chose */
      };
      enum PotionType{
         heal,
         mana,
         strengh,
         agility,
         /* et tous les autres */
      }
      struct Potion{
         QString name;
         PotionType type;
         int gain;
         int duration;
         /* il manque peut être quelque chose */
      };
      enum CharacterType{
         warrior,
         thief,
         wizzard,
         /* et tous les autres */
      };
      struct Character{
          QString name;
          CharacterType type;
          int strong;
          int mana;
          QList<Weapon> weapons;
          QList<Spell> spells;
          QList<Potion> potions;
      };

      (ne les prend surtout pas comme exemple, car elle n'ont rien d'efficace ;) ) il faudrait en arriver, pour chaque structure envisagée, à fournir un trait de politique indiquant la manière dont les données qu'elle contient devront être sauvegardées en mémoire.

      En gros, cela revient de toutes façons à fournir l'équivalent de la boucle

      for(auto const & [characteristic, value] : list){
          set.append(characteristic);
                 val.append(value);
                 if(index < values.size()){
                     set.append(",");
                     val.append(",");
                 }
             }

      (sans oublier de fournir, également, la table avec laquelle il faudra travailler)pour chacune des structures que tu envisagera. Et, au final, tu pourrais donc arriver à  avoir une fonction qui serait proche de

      template <typename DATA>
      void insertInDB(DATA const & d){
          /* on choisi le trait de politique adapté */
          using trait_t = InsertTrait<DATA>;
          /* le début de la requête */
          QString insert{"insert into"};
          /* on utilise les valeurs fournies par le trait utilisé  
           * pour la donnée à insérer
           */
          insert.append(trait_t::table)
                .append(" set(")
                .append(trait_t::fields() )
                .append(") VALUES (")
                .append(traits_::values(d) )
                .append(");");
          /* le texte de la requête est complet, on peut l'exécuter 
           * (je te laisse t'en occuper  )
           */
      }

      Mais cela signifie que, pour chacune des structures dont tu voudras ajouter un élément dans ta base de données, il faudra fournir:

      • le nom de la table (cela peut être une valeur statique constante)
      • une fonction qui fournit le nom des  champs impactés sous forme de chaine de caractères et séparés par une virgule
      • une fonction qui fournit les valeurs à utiliser sous la forme d'une chaine de caractères et séparées par une virgule

      C'est faisable.  Mais cela ne demandera pas moins de temps que de créer une fonction spécifique pour chaque type de donnée que tu souhaites insérer dans ta base de données ;)

      • Partager sur Facebook
      • Partager sur Twitter
      Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs  à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
        25 juillet 2020 à 18:13:37

        Merci de ta réponse je vais la relire quelque fois pour bien comprendre :)

        Pour l'usage d'un template c'est parce que la syntaxe change par rapport au type les string il faut '' et pas les int

        INSERT INTO personne (string,int) value('string',123) 

        C'est pour cela que je voudrais détecter le type

        Je regardais du coter QVariant mais en apprentissage :D

        C'est vrais que le faite de séparer les liste n'est pas une bonne idée je ne connaissait pas std::pair.

        Je voulais faire un système d'insert différent de ce que j'ai vu qui était de créer une structure pour chaque table et puis de l'envoyer en db. Mais je trouve que se système prend de la place dans un code (pour chaque table une structure)

        Maintenant je ne connais que cette manière la. Peux-être connais tu d'autre méthode?

        • Partager sur Facebook
        • Partager sur Twitter
          25 juillet 2020 à 19:22:32

          De toutes manières tu dois te dire que tu devras "décrire" les données que tu veux insérer dans ta base de données.

          Et, comme tous  les types données que tu voudras insérer ont -- a priori -- une composition différente et devront être insérées dans des tables différentes, tu n'auras pas beaucoup le choix: tu devras, pour chaque type de données, fournir une requête SQL adaptée au type de la donnée que tu veux  insérer.

          Et ca, il n'y a rien à faire, ca implique que tu devras écrire -- d'une manière ou d'une autre -- le code correspondant pour chacun des types de données que tu envisages d'utiliser ;)

          QVariant peut s'avérer intéressant pour fournir une représentation "homogène" de données de types différents.

          Cependant, il faut garder en tête le fait que, pour pouvoir utiliser la valeur représentée par un QVariant, il faudra quand même toujours tôt ou tard savoir de quel type est la valeur contenue par ce QVariant, et qu'il faudra donc choisir de la "reconvertir" en QString, en int ou -- pourquoi pas -- en float

          J'ai l'impression que, pour l'instant, tu vas déjà beaucoup trop loin dans ce que tu veux faire par rapport à tes connaissances ( de Qt et du C++ de manière générale), et qu'il serait sans doute "beaucoup plus sage" de mettre le projet actuel "en attente" le temps que tu comble tes lacunes ;)

          • Partager sur Facebook
          • Partager sur Twitter
          Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs  à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait

          Template c++

          × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
          × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
          • Editeur
          • Markdown