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 :

Lecture CSV en C++, le faire « comme il faut »


Sujet :

C++

  1. #1
    Membre émérite
    Avatar de white_tentacle
    Profil pro
    Inscrit en
    Novembre 2008
    Messages
    1 505
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2008
    Messages : 1 505
    Points : 2 799
    Points
    2 799
    Par défaut Lecture CSV en C++, le faire « comme il faut »
    Bonjour,

    Je vous présente un article qui, à partir d’un problème simple, présente la méthodologie et les techniques à mettre en œuvre pour arriver à une solution performante, réutilisable.

    http://julien-blanc.developpez.com/t...cture-csv-c++/

    Bonne lecture

  2. #2
    Membre émérite
    Avatar de prgasp77
    Homme Profil pro
    Ingénieur en systèmes embarqués
    Inscrit en
    Juin 2004
    Messages
    1 306
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : France, Eure (Haute Normandie)

    Informations professionnelles :
    Activité : Ingénieur en systèmes embarqués
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Juin 2004
    Messages : 1 306
    Points : 2 466
    Points
    2 466
    Par défaut
    Bonjour,

    c'est un très bon sujet d'article ; la lecture d'un fichier CSV (un vrai, avec tous les pièges qu'il peut contenir) est parfois faite de manière bien bancale .
    -- Yankel Scialom

  3. #3
    Membre éclairé
    Homme Profil pro
    nop
    Inscrit en
    Mars 2015
    Messages
    436
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Somme (Picardie)

    Informations professionnelles :
    Activité : nop
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : Mars 2015
    Messages : 436
    Points : 658
    Points
    658
    Par défaut importer un fichier CSV en c++
    très bon article !

    un BigUp! pour la partie qui traite de l'importance de la taille du fichier, qui va conditionner la méthode de lecture. On ne le dira jamais assez traiter un fichier de 400Go sur une machine perso n'est pas pareil que de le faire sur un serveur virtualisé mutualisé ou de prod. Sans parler du problème de cache, de mémoire, du temp, de ressources par utilisateur.....

    Une petite astuce personnel:
    maintenant je traite le problème d'encodage manuellement avec un éditeur à part, car souvent, trop souvent on tombe sur des fichiers CSV multi-encodage qui ont été mal gérés par les exportateurs....
    Oui alors avant l’entrée dans la boucle, penser à une fonction juste pour tester l’intégralité du bon codage (et par la même occasion l’intégralité du fichier au cas où il est défectueux)

    Une petite amélioration pour l'article: vous ne parlez pas de la source (disque dur, réseau, nas, lien dropbox, lien cloud..etc) du fichier et de son impact, de son traitement en tâche de fond plutôt qu'en foreground.

  4. #4
    Membre régulier
    Homme Profil pro
    Cocher moderne
    Inscrit en
    Septembre 2006
    Messages
    50
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 45
    Localisation : Oman

    Informations professionnelles :
    Activité : Cocher moderne

    Informations forums :
    Inscription : Septembre 2006
    Messages : 50
    Points : 118
    Points
    118
    Par défaut
    Excellent, j'en avais justement besoin !
    Merci beaucoup ! Ça va m'éviter quelques heures d'arrachage de cheveux et quelques litres de café...

  5. #5
    Expert confirmé
    Homme Profil pro
    Étudiant
    Inscrit en
    Juin 2012
    Messages
    1 711
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juin 2012
    Messages : 1 711
    Points : 4 442
    Points
    4 442
    Par défaut
    Citation Envoyé par MichaelREMY Voir le message
    Une petite amélioration pour l'article: vous ne parlez pas de la source (disque dur, réseau, nas, lien dropbox, lien cloud..etc) du fichier et de son impact, de son traitement en tâche de fond plutôt qu'en foreground.
    La partie VI en parle, ou du moins donne des éléments de réponse : la source des données n'a pas d'impact (autre que les performances : on peut pas traiter les données plus rapidement qu'elles arrivent).

    Si tu veux faire tes traitements de manière asynchrone, rien ne change non plus
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    Threadpool pool; // ou autre
     
    CSVParser parser;
    parser.onField([](std::string const& f) { std::cout << f << std::endl; }); // traitement synchrone
    parser.onField([&](std::string const& f) { pool.enqueue([f]() { std::cout << f << std::endl; }); }); // traitement asynchrone
     
    std::vector<char> data;
    for(auto c: data) {
       parser.consume(c);
    }

  6. #6
    Membre éclairé
    Homme Profil pro
    nop
    Inscrit en
    Mars 2015
    Messages
    436
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Somme (Picardie)

    Informations professionnelles :
    Activité : nop
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : Mars 2015
    Messages : 436
    Points : 658
    Points
    658
    Par défaut
    Citation Envoyé par Iradrille Voir le message
    La partie VI en parle, ou du moins donne des éléments de réponse : la source des données n'a pas d'impact (autre que les performances : on peut pas traiter les données plus rapidement qu'elles arrivent).

    Si tu veux faire tes traitements de manière asynchrone, rien ne change non plus
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    Threadpool pool; // ou autre
     
    CSVParser parser;
    parser.onField([](std::string const& f) { std::cout << f << std::endl; }); // traitement synchrone
    parser.onField([&](std::string const& f) { pool.enqueue([f]() { std::cout << f << std::endl; }); }); // traitement asynchrone
     
    std::vector<char> data;
    for(auto c: data) {
       parser.consume(c);
    }
    oui oui, je voulais juste dire qu'avant d'importer le fichier, il faut s'assurer, ou plutôt estimer la longeur de la large pour ne pas monopoliser (ou plutôt ennuyer l'utilisateur devant l'écran) et ainsi traiter le tout en background permettant ainsi d'afficher une chrono, une jauge. Comem fait maintenant Youtube pour l'import des vidéos. Rappelez-vous avant (dans les années 2000) où on attendez devant l'écran que l'upload se finisse pour remplir le formulaire et ensuite faire autre chose.... C'es ce point là que je voulais mettre en avant : estimer avant de travailler, même un traitement d'apparence simple...
    c'est un très bon article-tuto que vous avez écrit là.

  7. #7
    Expert éminent sénior

    Avatar de dragonjoker59
    Homme Profil pro
    Software Developer
    Inscrit en
    Juin 2005
    Messages
    2 031
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 42
    Localisation : France, Bas Rhin (Alsace)

    Informations professionnelles :
    Activité : Software Developer
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juin 2005
    Messages : 2 031
    Points : 11 388
    Points
    11 388
    Billets dans le blog
    11
    Par défaut
    Citation Envoyé par MichaelREMY Voir le message
    oui oui, je voulais juste dire qu'avant d'importer le fichier, il faut s'assurer, ou plutôt estimer la longeur de la large pour ne pas monopoliser (ou plutôt ennuyer l'utilisateur devant l'écran) et ainsi traiter le tout en background permettant ainsi d'afficher une chrono, une jauge...
    Ce qui n'a strictement aucun rapport avec le parseur, mais avec la manière dont il est utilisé, donc complètement hors de propos.
    Si vous ne trouvez plus rien, cherchez autre chose...

    Vous trouverez ici des tutoriels OpenGL moderne.
    Mon moteur 3D: Castor 3D, presque utilisable (venez participer, il y a de la place)!
    Un projet qui ne sert à rien, mais qu'il est joli (des fois) : ProceduralGenerator (Génération procédurale d'images, et post-processing).

  8. #8
    Membre émérite
    Avatar de white_tentacle
    Profil pro
    Inscrit en
    Novembre 2008
    Messages
    1 505
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2008
    Messages : 1 505
    Points : 2 799
    Points
    2 799
    Par défaut
    Citation Envoyé par dragonjoker59 Voir le message
    Ce qui n'a strictement aucun rapport avec le parseur, mais avec la manière dont il est utilisé, donc complètement hors de propos.
    En fait, oui et non. C’est pour ça que j’en ai parlé un peu, mais seulement un peu. Je me suis placé du point du vue du concepteur de bibliothèque. À mon sens, c’est celui qu’on devrait toujours appliquer (une application, c’est une bibliothèque métier avec une interface par-dessus). Et dans cette optique-là, ce qui est important, c’est surtout d’imposer le minimum de contraintes à son utilisateur. Devoir connaître à l’avance la taille des données (ou au moins une approximation), par exemple, c’est une fonctionnalité importante pour certaines application, mais c’est impossible pour d’autres (données lues depuis un flux quelconque, type socket ou stdin). Donc ce qui est important, c’est :
    * de ne rien imposer
    * de fournir les moyens pour l’applicatif de mettre en œuvre son besoin.

    Ici, la progression et l’information d’avancement de celle-ci se gérera dans le handler, qui pourra à son tour notifier l’interface par un moyen quelconque à chaque avancement. Toutefois, la bibliothèque ne notifie où elle en est du fichier qu’en cas d’erreur. Donc soit :
    - on décide que cette information doit être fournie par la personne en charge de la lecture du fichier (d’un certain point de vue, c’est même très discutable d’avoir intégré cette gestion de position courante directement dans notre parseur : c’est plutôt la commodité qui l’a dictée qu’autre chose). Lui se greffe pour être notifié des fins d’enregistrement par le parseur, et notifie à son tour qui de droit avec les données supplémentaires requises comme le %age d’avancement.
    - on décide que cette information doit être disponible à tout moment, via des méthodes publiques sur le parseur
    - on décide que cette information doit transiter dans les callbacks

    Ma préférence va à la première solution (c’est d’ailleurs la seule qui ne nécessite pas de modifier le code de l’article ), mais la troisième peut se révéler plus simple à mettre en œuvre si le lecteur et le consommateur final sont très distincts.

  9. #9
    Membre éprouvé
    Avatar de EpiTouille
    Homme Profil pro
    Étudiant
    Inscrit en
    Mai 2009
    Messages
    372
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Mai 2009
    Messages : 372
    Points : 917
    Points
    917
    Par défaut
    moi qui ai toujours fait un get_line et un split, je vais peut être regarder le tuto...

  10. #10
    Invité
    Invité(e)
    Par défaut
    Très bon tuto, merci.

    Je gère plusieurs fichiers csv dans mon programme.
    Et aucun d'entre eux n'est similaire.
    Vous me simplifiez la vie.

    Ou puis je trouver le fichier archive comme stipulé ?

    Merci encore.

  11. #11
    Nouveau Candidat au Club
    Homme Profil pro
    Ingénieur après-vente
    Inscrit en
    Octobre 2015
    Messages
    1
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Ingénieur après-vente
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Octobre 2015
    Messages : 1
    Points : 1
    Points
    1
    Par défaut
    Bonjour,

    Etant débutant en C++ je cherche un moyen "générique" de lire des fichiers csv.

    Cet article m'aide beaucoup dans la logique qui découle de la lecture du fichier csv. Par contre, tout comme fnallet94, je cherche le fichier archive stipulé plusieurs fois dans l'article sans pouvoir le trouver. Avez vous un lien ?

    J'ai néanmoins à partir des bouts de code réussit à reconstruire plus ou moins. A savoir que je ne compile pas avec le c++11 et que du coup je n'ai pas accès à "enum class", au wraper "function",...

    Par contre je n'arrive à comprendre comment sont utilisés (et codé) les fonctions de rappels tel que field_handler(c).

    Dans la classe csv_parser j'ai une méthode call_field_handler(). Je ne vois pas comment lié la methode field_handler(c) de csv_data_handler à call_field_handler() de csv_parser...

  12. #12
    Membre émérite
    Avatar de white_tentacle
    Profil pro
    Inscrit en
    Novembre 2008
    Messages
    1 505
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2008
    Messages : 1 505
    Points : 2 799
    Points
    2 799
    Par défaut
    Bonjour,

    J’ai mis à jour l’article avec le fichier manquant (sinon, il est ici). J’en ai profité pour changer la licence. Du coup, il y a des exemples d’utilisation.

    Pour « call_field_handler », de mémoire, c’est juste un helper qui teste si le foncteur field_handler est nul avant de l’appeler. Par contre, sans std::function, il faut passer par des pointeurs de fonctions, c’est beaucoup plus pourri au niveau de la syntaxe.

  13. #13
    Membre chevronné Avatar de Ehonn
    Homme Profil pro
    Étudiant
    Inscrit en
    Février 2012
    Messages
    788
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 34
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Février 2012
    Messages : 788
    Points : 2 160
    Points
    2 160
    Par défaut
    Citation Envoyé par white_tentacle Voir le message
    Par contre, sans std::function, il faut passer par des pointeurs de fonctions, c’est beaucoup plus pourri au niveau de la syntaxe.
    Pour simplifier la syntaxe, on peut prendre un paramètre template qui correspond à la fonction; ça fonctionne avec les pointeurs et références de fonction et les foncteurs (et on met de côté la tête de l'erreur si l'utilisateur ne passe pas une fonction avec la signature attendue).

  14. #14
    Membre chevronné

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Avril 2013
    Messages
    610
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Finance

    Informations forums :
    Inscription : Avril 2013
    Messages : 610
    Points : 1 878
    Points
    1 878
    Billets dans le blog
    21
    Par défaut
    @Paul_R:
    1. pour enum class, tu peux utiliser enum tout simplement, cela enlève un peu de sécurité mais ce n'est pas trop grave -tu peux aussi mettre l'enum dans son propre namespace pour éviter une confusion
    2. pour std::function c'est un peu plus embêtant, mais sans plus. Tu peux ajouter un membre de la classe csv_traits dans ton parser:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    template <class csv_traits>
    class Parser {
    ...
    csv_traits traits_;
    ...
    };
    et définir les fonctions is_seprator et autres dans ton csv_traits personnalisé:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    class my_traits {
    ...
    bool is_separator(const char_type c) { return c == ';' ; }
    etc.
    ...
    };
    Dans ta fonction 'consume' tu appelles "traits_.is_separator(c)."

    3. pour les fonctions call_handler, elles sont définies dans le parser, par exemple:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    class Parser {
    ...
    call_field_handler { data_handler.handle_field(last_value); traits_.truncate(last_value); }
    };

  15. #15
    Membre chevronné

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Avril 2013
    Messages
    610
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Finance

    Informations forums :
    Inscription : Avril 2013
    Messages : 610
    Points : 1 878
    Points
    1 878
    Billets dans le blog
    21
    Par défaut
    @white_tentacle:
    Excellent article vraiment, hyper complet avec le profilage!

    Une question sur la conception: pourquoi ne pas paramétrer le parser avec les csv_traits et le data_handler?

    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
    template <typename CsvTraits, typename DataHandler>
    class Parser {
     
    private:
      DataHandler& dh_;
      typename CsvTraits::string_type last_value;
      ST state;
     
    public:
      Parser(DataHandler& dh) : dh_(dh), state(INIT) {}
      void parse(const typename CsvTraits::string_type&);
     
    private:
      void consume(const typename CsvTraits::char_type& c);
      void end_of_data();
     
      void call_record_handler();
      void call_line_handler();
      void call_error();
     
    };
    Ensuite, en mettant un DataHandler qui affiche simplement le fichier sur la console:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    struct DefaultHandlers {
     
      void handle_record(const std::string& s) {
        std::cout << s << " | ";
      }
      void handle_newline() {
        std::cout << std::endl;
      }
      void handle_error() {
        std::cout << "!! an error has occured !!" << std::endl;
      }
    };
    on pourrait l'utiliser ainsi:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    int main() {
     
      std::string test = "123;\"123;\"\"123\"\"\";123\n123;123;123";
     
      DefaultHandlers dh;
      Parser<StlTraits, DefaultHandlers> p(dh);
      p.parse(test);
     
    }
    Il me semble que cela simplifie (moins de classes, entre autres) et qu'on ne perd pas en modularité?

  16. #16
    Membre émérite
    Avatar de white_tentacle
    Profil pro
    Inscrit en
    Novembre 2008
    Messages
    1 505
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2008
    Messages : 1 505
    Points : 2 799
    Points
    2 799
    Par défaut
    Citation Envoyé par stendhal666 Voir le message
    @white_tentacle:
    Une question sur la conception: pourquoi ne pas paramétrer le parser avec les csv_traits et le data_handler?

    [snip]

    Il me semble que cela simplifie (moins de classes, entre autres) et qu'on ne perd pas en modularité?
    Un choix. Comme tout choix, il a une part d’arbitraire .

    Il y a plusieurs raisons à ça :
    - sortir le data_handler permet d’exporter le parseur CSV paramétré pour std::string dans une bibliothèque (et donc, corriger les éventuels bugs du parseur sans recompiler l’utilisateur)
    - cela permet aussi de réduire la taille du code : le compilateur va sinon générer une classe par handler, et tout autant de symboles en plus.

    Néanmoins, ta remarque est très pertinente, et en procédant ainsi on doit probablement permettre des optimisations impossibles avec ce que j’ai proposé. Idéalement, je la ferai plutôt de la sorte :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    template <typename CsvTraits, typename DataHandler>
    class Parser : public DataHandler 
    {
    Pourquoi un héritage public ? Pour plusieurs raisons :
    - cela permet d’enrichir l’interface du Parser. Dans cette optique, il est possible d’écrire un ConfigurableDataHandler qui permet de retrouver le comportement actuel
    - le 0-size optimization : si sizeof(DataHandler) == 0, je n’augmente pas la taille de ma classe

    En procédant ainsi on a le beurre et l’argent du beurre : la flexibilité run-time ou compile-time.

  17. #17
    Membre éprouvé
    Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Mars 2009
    Messages
    552
    Détails du profil
    Informations personnelles :
    Localisation : France

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

    Informations forums :
    Inscription : Mars 2009
    Messages : 552
    Points : 1 060
    Points
    1 060
    Par défaut
    Citation Envoyé par white_tentacle Voir le message
    Il y a plusieurs raisons à ça :
    - sortir le data_handler permet d’exporter le parseur CSV paramétré pour std::string dans une bibliothèque (et donc, corriger les éventuels bugs du parseur sans recompiler l’utilisateur)
    - cela permet aussi de réduire la taille du code : le compilateur va sinon générer une classe par handler, et tout autant de symboles en plus.
    J'aurais bien une autre raison : Avec le traits, comment est-ce qu'on permet à l'utilisateur de choisir le séparateur dans une interface graphique? On fait du type erasure pour masquer un typage trop fort?

    Je pense qu'il faut toujours se poser la question : Est-ce que je veux pouvoir changer au runtime. Si oui, la template est rarement la bonne idée.

    Quitte à sortir les paramètres, je ferais plutôt un "CSVFormat" (separator, closure, charset, etc.). C'est un type à part entière, son contenu et son code ne varie pas : Pas besoin de template.

  18. #18
    Membre émérite
    Avatar de white_tentacle
    Profil pro
    Inscrit en
    Novembre 2008
    Messages
    1 505
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2008
    Messages : 1 505
    Points : 2 799
    Points
    2 799
    Par défaut
    Citation Envoyé par bretus Voir le message
    Je pense qu'il faut toujours se poser la question : Est-ce que je veux pouvoir changer au runtime. Si oui, la template est rarement la bonne idée.
    En effet, mais le template n’empêche pas forcément la configuration au run-time. La technique que j’ai décrite, consistant à hériter publiquement de la classe de traits/politique, permet à celle-ci d’enrichir l’interface et donc de rendre configurable ce qui au départ était statique. Le tout sans type-erasure. J’ai la configuration run-time, si je la désire uniquement.

    Quitte à sortir les paramètres, je ferais plutôt un "CSVFormat" (separator, closure, charset, etc.). C'est un type à part entière, son contenu et son code ne varie pas : Pas besoin de template.
    Il y a besoin de template car le code (généré) ne peut pas être raisonnablement le même en fonction du charset (si sizeof(char_type) change, ça devient compliqué — sauf à tout convertir vers le plus grand (int32)). Ici, j’ai décidé de déporter la gestion du charset en-dehors de la classe, car l’analyse faite s’y prête bien. Le choix serait différent si ce n’était pas le cas. Encore une fois, dans tout choix il y a une part d’arbitraire, ça ne veut pas dire que les autres solutions sont mauvaises.

    Pour la petite histoire, ce choix de template vient à l’origine de la volonté (besoin réel) de pouvoir gérer à la fois std::string et QString comme classe de chaîne de caractères (et les deux de manière efficace). Ce serait compliqué sans templates (loin d’être impossible cela dit) et vraisemblablement moins efficace au final.

  19. #19
    Membre à l'essai
    Profil pro
    Inscrit en
    Janvier 2005
    Messages
    16
    Détails du profil
    Informations personnelles :
    Localisation : France, Gironde (Aquitaine)

    Informations forums :
    Inscription : Janvier 2005
    Messages : 16
    Points : 16
    Points
    16
    Par défaut
    Bonjour

    Est ce que je code que tu proposes est sous la même licence que ton article ?
    J'aimerai m'en servir d'en une appli et je voudrai être sûr de bien respecter la licence que tu as choii.

    Frédéric

  20. #20
    Membre à l'essai
    Profil pro
    Inscrit en
    Janvier 2005
    Messages
    16
    Détails du profil
    Informations personnelles :
    Localisation : France, Gironde (Aquitaine)

    Informations forums :
    Inscription : Janvier 2005
    Messages : 16
    Points : 16
    Points
    16
    Par défaut
    Ne pas tenir compte de cette question ...

    Re

    Est ce que dans :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    class csv_data_handler {
    	bool has_headers_;
    	csv_data& data_;
    	bool at_beginning_of_line;
    	bool at_first_line;
    public:
    	bool field_handler(std::string const& s)
    	{
            double value;
    		if(at_first_line && has_headers_)
    		{
    			data_.headers_.push_back(s);
    		}
    Il ne faudrait pas un ! ici ? :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    		if(at_first_line && !has_headers_)
    Merci d'avance
    Frédéric

Discussions similaires

  1. Que peux-t'on faire comme application ?
    Par lalystar dans le forum Java ME
    Réponses: 2
    Dernier message: 11/12/2004, 05h36
  2. [maintenance][performance] Que faire comme maintenance ?
    Par woodwai dans le forum PostgreSQL
    Réponses: 5
    Dernier message: 06/11/2003, 15h39

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