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 :

Votre avis sur ma bibliothèque


Sujet :

C++

  1. #1
    Futur Membre du Club
    Homme Profil pro
    Étudiant
    Inscrit en
    Avril 2014
    Messages
    10
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Avril 2014
    Messages : 10
    Points : 7
    Points
    7
    Par défaut Votre avis sur ma bibliothèque
    Bonjour à tous,

    Alors pour la petite histoire, j'ai touché à pas mal de langages informatiques. A la base j'étais un peu du genre pro-java et le C++ me faisait pousser des boutons, mais je pense que la fac nous amène un peu à penser ce genre de choses... Enfin bref, dans un soucis de performances (exécution et mémoire) je suis passé au C++ il y a un peu plus de 3 ans. À la base s'était surtout pour faire un petit test de performance et je dois dire que je suis franchement resté sur le cul, surtout pour la mémoire où j'ai vu la consommation fondre comme neige au soleil.

    Le C++ réponds à mes attentes à ce niveau là et en bonus j'ai de plus été extrêmement séduit par la puissance des templates et les nouveautés du C++11. Sauf que depuis le début j'ai beaucoup de mal avec la bibliothèque standard. Non pas que je n'arrive pas à m'en servir ou que je ne la comprenne pas, mais je la trouve assez lourde à utiliser. Je pense clairement tout d'un point de vue orienté objet, design pattern, etc... et ce qui est proposé par la bibliothèque stantard ou boost ne me satisfait pas complètement. J'aime l'approche orienté objet mais aussi l'approche langage "fonctionnel" qu'apportent les lambdas avec C++11.

    Du coup je me suis développé une petite bibliothèque durant mon temps libre. Le but est le suivant: grâce au C++11 simplifier un maximum ce que parfois la bibliothèque standard ou bien l'association avec boost prends plusieurs lignes à écrire, tout en gardant un code clair et parlant. Pour présenter rapidement l'approche (car la bibliothèque commence à devenir assez grosse, avec de nombreux concepts que je ne vais pas présenter ici), je vais prendre un exemple simple où l'on souhaite manipuler un fichier texte et à récupérer toutes les lignes qui contiennent des mots qui commencent par "bb", et pour finir afficher les lignes sélectionnées.

    En C++ classique :

    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
     
    std::ifstream file("MonFichier.txt"); 
     
    std::vector<std::string> interesting_lines;
     
    if(file) {
        std::string line;
        boost::char_separator<char> separator(";");
     
        while(std::getline(file, line)) {
            boost::tokenizer<boost::char_separator<char> > token(line,separator);
            boost::tokenizer<boost::char_separator<char> >::iterator word_iterator=token.begin();
     
            while(word_iterator!=token.end()) {
                if(boost::starts_with( *word_iterator, "bb")) {
                    interesting_lines.push_back(line);
                    break;
                }
                ++word_iterator;
            }
        }
     
        file.close();
    }
    else
        throw std::logic_error( "Impossible d'ouvrir le fichier" );
     
    for(std::string line : interesting_lines) {
        std::cout << line << std::endl;
    }
    Avec ma bibliothèque :
    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
     
    oo::File file("monFichier.txt")
     
    oo::Vector<oo::String> interesting_lines;
     
    interesting_lines = file.collect_if([] (oo::String& line) {
     
        return line.split(";").is_any([] (oo::String& word) {
     
            return word.start_with("bb");
        });
    });
     
    interesting_lines.each([](oo::String& line) {
        std::cout << line << std::endl;
    });
    Si certains connaissent Ruby, vous y aurez sûrement reconnu une claire influence, j'use et j'abuse des lambdas. Bref, c'est en fin de compte très proche de ce que l'on peut faire avec un std::copy_if ou bien un std::any_of, mais il n'y a pas la gestion des pointeurs de début et de fin (que je trouve pénible à écrire et à lire). En surchargeant les lambdas, je peux également faire :

    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
     
    oo::File file("monFichier.txt")
     
    oo::Vector<oo::String> interesting_lines;
     
    interesting_lines = file.collect_if([] (oo::Sentence& sentence) {
     
        return sentence.split(";").is_any([] (oo::String& word) {
     
            return word.start_with("bb");
        });
    });
     
    interesting_lines.each([](oo::String& line) {
        std::cout << line << std::endl;
    });
    Ici nous ne prenons plus le problème ligne par ligne, mais phrase par phrase (donc séparé par un point). En fait j'utilise ce système car il s'avère extrêmement flexible. Dans ce cas ci, j'ai deux classes abstraites IterableElement et CollectableElement qui implémentent les fonctions is_any() et collect_if() respectivement. Ces deux classes définissent une méthode abstraite each() utilisée par is_any() et collect_if() et qui doit être implémentée dans la classe fille, et qui permet d'itérer sur des éléments d'un type T (ici String et Sentence). Pour info, je précise que IterableElement et CollectableElement possèdent bien plus de fonctions, mais j'ai simplifié.
    -> Toute classe qui hérite de IterableElement<T> et CollectableElement<T> et qui implémente la fonction each() pour l'élément T pourra utiliser les lambas implémentées dans les classes mères
    -> Du coup il est possible d'hériter plusieurs fois d'une de ces classes avec un type différent pour multiplier les possibilités.

    Pour un exemple peut être plus concret, prenons un multigraphe (plusieurs arêtes possibles entre deux sommets) non dirigé qui peut avoir des arêtes colorés et des sommets colorés. Le problème est de supprimer tous les sommets bleus qui ont toutes leurs arêtes connectées qui sont rouges :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     
    oo::MultiGraph<oo::Color, oo::Color> g = fonctionQuiGenereMonGraphe();
     
    g.remove_if([] (oo::MultiGraph::Vertex<oo::Color, oo::Color> vertex) {
     
        return (vertex.label() == oo::Color::Blue) 
            && vertex.is_all([] (oo::MultiGraph::Edge<oo::Color, oo::Color> edge) {
     
                return edge.label() == oo::Color::Red;
            });
    });
    Inversement si l'on souhaite supprimer toutes les arêtes rouges qui ont tous leurs sommets connectés qui sont bleus :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     
    oo::MultiGraph<oo::Color, oo::Color> g = fonctionQuiGenereMonGraphe();
     
    g.remove_if([] (oo::MultiGraph::Edge<oo::Color, oo::Color> edge) {
     
        return (edge.label() == oo::Color::Red) 
            && edge.is_all([] (oo::MultiGraph::Vertex<oo::Color, oo::Color> vertex) {
     
                return vertex.label() == oo::Color::Blue;
            });
    });
    J'ai mes propres versions des containers de la bibliothèque standard : Vector, Set, List, Map que j'ai seulement encapsulé et enrichi des nouvelles méthodes. Puis j'ai d'autres structures de données plus complexes : Sequence, Graphe, MultiGraph, DirectedGraph, DirectedMultiGraph, Matrix, MapMatrix. Au niveau perf il faudrait que je fasse des benchmarks mais honnêtement je n'ai pas noté de grosses différences à utiliser les lambdas. De plus lorsque l'on voit un objet oo::MultiGraph::Edge pour un graphe, on peut se dire que derrière il y a beaucoup d'objets. En fait non, derrière j'ai par exemple simplement une matrice d'adjacence et les objets sont simplement générés à la volée pour la lambda et cela reste transparent pour l'utilisateur.

    Ma question est que pensez vous d'un tel design ? Y voyez vous des inconvénients ou bien avez vous des remarques ?

    Je précise que j'ai appris le C++ tout seul, ne me maltraitez pas trop svp.

  2. #2
    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
    Salut,

    Ta bibliothèque semble te convenir et c'est très bien :-) Néanmoins elle n'est pas du tout dans l'esprit C++, quand la STL a été créée l'idée générale était d'avoir d'un côté les structures de données et d'un autre les algorithmes, dans ta bibliothèque tu mixes les deux. Néanmoins le chaînage peut s'avérer très intéressant et plus clair à lire comme tu le démontres mais dans ce cas j'aurais plutôt fait des wrappers génériques qui permettent le chaînage comme :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    std::ifstream file("monFichier.txt");
    std::vector<std::string> interesting_lines;
    oo::stream_algo_wrapper<std::ifstream> file_wrapper(file);
     
    interesting_lines = file_wrapper.collect_if([] (const std::string& line) {
        return line.split(";").is_any([] (const std::string& word) {
            return word.start_with("bb");
        });
    });
    Là je serais plus intéressé vu que j'utiliserais toujours les containers standards et qu'il me suffit de les wrapper (j'entend que le wrapper contienne une référence à l'objet) pour faire du chaînage.

    À part ça, il va sans dire que "designer" une bibliothèque est très difficile car il faut rester générique ce que ne fait pas ta bibliothèque (exemple : wchar_t ?), tu résous le cas générale, ou plutôt ton cas général, ce qui en fait une bibliothèque puissante pour toi mais pas forcément pour les autres. Ce n'est pas mal mais il faut s'en rendre compte :-)

    As-tu un dépôt github ou autre pour qu'on voit plus en détail le reste ?

  3. #3
    Futur Membre du Club
    Homme Profil pro
    Étudiant
    Inscrit en
    Avril 2014
    Messages
    10
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Avril 2014
    Messages : 10
    Points : 7
    Points
    7
    Par défaut
    Salut,

    Merci pour ton retour Trademark, je trouve ta remarque très intéressante !

    Mes classes comme File ou Vector encapsulent en fait leur équivalent C++ mais je n'avais pas du tout pensé au wrapper et j'aime beaucoup cette idée. Du coup je me dis que cela pourrait être envisageable sans trop de changements pour avoir une référence encapsulée plutôt qu'une variable classique, tout du moins pour les types qui ont leur équivalent en C++. Idée a creuser.

    Tu peux m'en dire plus sur wchar_t (je ne connais pas) et sur les problèmes que cela soulève ?

    Non je n'ai pas de github, c'était justement en perspectives au cas où mon approche pouvait intéresser quelqu'un.

  4. #4
    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
    wchar_t c'est juste pour stocker des caractères sur deux bytes (UTF-8 si c'est max 2 bytes ou UTF16), et il y a plein de détail du genre comme la localisation, etc. qui font que ta lib ne sera pas générique, ça prend des mois voir des années avant d'avoir une vraie bibliothèque.

  5. #5
    Futur Membre du Club
    Homme Profil pro
    Étudiant
    Inscrit en
    Avril 2014
    Messages
    10
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Avril 2014
    Messages : 10
    Points : 7
    Points
    7
    Par défaut
    Bien entendu mon but de base n'est pas de rivaliser avec ce qui se fait de mieux dans boost ou bien la librairie standard. J'imagine bien qu'avoir une généricité parfaite est difficile surtout avec un langage riche comme C++, et ce n'est pas mon objectif.

    Comme tu l'as dit c'est générique dans mon cas et j'ai surtout fait cela pour me simplifier la tâche dans mes algos. En fait je travaille dans la recherche en fouille de données, les algos peuvent devenir extrêmement complexes et les optimiser entraîne un bordel monstre, un maximum de lisibilité est primordial. Je développe dans mon temps libre pour m'aider dans le boulot on va dire, puis surtout pour le fun. Par exemple là je prévois d'intégrer une notion de visualisation de mes structures de données, ça peut m'être très utile pour le debug. Si par exemple j'utilise un DirectedGraph qui s'avère être un arbre, de pouvoir générer le javascript qui corresponds à ça http://mbostock.github.io/d3/talk/20111018/cluster.html, si j'ai une Matrix de générer quelque chose comme ça http://bost.ocks.org/mike/miserables/ et si je fais un clustering de générer un truc de ce genre http://bl.ocks.org/mbostock/1747543

    Même si cela ne corresponds pas aux principes du C++ et de la bibliothèque standard, mon objectif c'est d'essayer de me simplifier la tache concernant les problèmes que je rencontre. Je me disais que cela pouvait peut être (modestement) servir à d'autres.

  6. #6
    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 tout cas c'est pas une mauvaise idée, je trouve aussi que le chaînage peut parfois rendre les choses plus clairs. Je pense que ça pourrait déjà être plus utile si tu utilises un système de wrapper, je viens de penser à un truc :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    std::ifstream file("xxx");
    prepare(file).filter(...).remove_if(...);
    Le prepare (désolé pour le nom...) renvoit le wrapper sur le type passé en paramètre. Après je me demande si ce genre de chose n'a pas déjà été faite, il faudrait se renseigner. En plus niveau perfs ces méthodes peuvent renvoyer des vues (et donc ne pas calculer immédiatement) et on pourrait tout calculer d'un coup à la fin.

  7. #7
    Membre expert
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2011
    Messages
    739
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hérault (Languedoc Roussillon)

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

    Informations forums :
    Inscription : Juin 2011
    Messages : 739
    Points : 3 627
    Points
    3 627
    Par défaut
    Je peux voir l’intérêt mais n'en suis vraiment pas convaincu.
    Par exemple, pour s’évite les begin/end: boost::range
    Simplifier les types à rallonge de boost: make_* (+ éventuellement auto) ou typedef

    On peut se retrouve avec quelque chose de relativement simple et générique
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    while(std::getline(file, line)) {
      if (boost::any_of(oo::make_tokenizer(line,';'), [](auto & word) {
        return boost::starts_with(word, "#");
      })) {
        interesting_lines.push_back(std::move(line));
      }
    }
    On peux encore simplifier en fournissant des fonctions qui retournent des foncteurs. Exemple avec is_starts_with qui remplace la lambda:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    while(std::getline(file, line)) {
      if (boost::any_of(oo::make_tokenizer(line,';'), oo::is_starts_with("bb"))) {
        interesting_lines.push_back(std::move(line));
      }
    }
    En fait, se que je reproche au tout objet est ça non évolutivité (mhouarf).
    J'entends par là que pour ajouter une méthode de chaînage il faut soit faire un héritage sur chaque objet où mettre la fonction, soit faire une fonction/foncteur libre avec une manière différente de chaîner.
    Personnellement je préfère toujours la seconde possibilité. Ce qui peut donner:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    while_[get_line(file)]
    > if_[any_of(make_tokenizer(_1, ';'), is_starts_with("bb"))]
    > back_inserter(line);
    Il existe quelque chose s'y approchant dans Boost.Lambda, Boost.Proto et une dont je ne me souvient pas du nom.

  8. #8
    Expert éminent sénior
    Homme Profil pro
    Analyste/ Programmeur
    Inscrit en
    Juillet 2013
    Messages
    4 630
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Analyste/ Programmeur

    Informations forums :
    Inscription : Juillet 2013
    Messages : 4 630
    Points : 10 556
    Points
    10 556
    Par défaut
    Citation Envoyé par Trademark Voir le message
    En tout cas c'est pas une mauvaise idée, je trouve aussi que le chaînage peut parfois rendre les choses plus clairs.
    À voir si c'est lisible aussi

    Dans le post initial, la différence entre le deuxième et troisième exemple c'est le passage d'un type String ou d'un type Sentence

    Dans le code chargé de lambdas, il faut être très vigilant parce qu'avec cet exemple le résultat est assez différent.

  9. #9
    Membre expert
    Profil pro
    Inscrit en
    Mars 2007
    Messages
    1 415
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Mars 2007
    Messages : 1 415
    Points : 3 156
    Points
    3 156
    Par défaut
    Salut

    Je suis grave d'accord avec la première remarque de Trademark : les interfaces que tu proposes peuvent tout à fait être mappées sur la lib standard, sans sacrifier à la lisibilité proposée. Je plussoie également le github, qu'on puisse un peu lire du code et/ou proposer des choses. Quant à la complexité d'écriture d'une STL, il suffit de lire son code pour se rendre compte que ses développeurs gèrent une grande quantité de cas particuliers auxquels il est difficile de penser et qui ne sont pas tous simples à adresser.

    C'est intéressant de voir que C++11 a en partie motivé ton déplacement vers C++, beaucoup de personnes m'ont dit la même chose. En tout cas, c'est ce genre d'initiatives qui font de C++ un langage toujours vivant, bravo pour cet effort.
    Find me on github

  10. #10
    Futur Membre du Club
    Homme Profil pro
    Étudiant
    Inscrit en
    Avril 2014
    Messages
    10
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Avril 2014
    Messages : 10
    Points : 7
    Points
    7
    Par défaut
    Merci pour toutes vos réponses !

    Je peux voir l’intérêt mais n'en suis vraiment pas convaincu.
    Par exemple, pour s’évite les begin/end: boost::range
    Oui je suis tombé sur boost::range et les boost::adaptors qui ont l'air de permettre le chaînage.
    Le problème, dans mon cas, c'est que ces algorithmes ont besoin d'itérateurs. Donc ça fonctionne très bien sur des containers simples, mais je désire également pouvoir itérer sur une "vue" d'un container, je ne sais pas comment dire ça.

    Par exemple, prenons une matrice statique de taille 10*10 naïvement implémenté std::array <std::array<int, 10>, 10>. Cette matrice je peux la voir de différentes manières : en la considérant par ses lignes, par ses colonnes ou bien directement par ses cellules. De la même manière les lignes et colonnes peuvent être ensuite décomposées en une "vue" cellule.

    Alors imaginons maintenant que l'on souhaite seulement itérer sur le container sur ces trois aspects :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
     
    std::array <std::array<int, 10>, 10> matrix;
     
    for(std::array<int, 10>& line : matrix) {
        // J'itère sur mes lignes
        ....
    }
     
    for(std::array<int, 10>& line : matrix) {
        for(int cell : line) {
            // J'itère sur mes cellules
            .....
        }
    }
    Mais comment faire de manière simple pour itérer sur mes colonnes, sauf à faire un for de 1 à 10 pour dire je suis sur la colonne i ? Et que dire de cette approche si l'on représente une matrice de 10*10 par un std::array < int, 10*10> pour optimiser l'approche, itérer sur les lignes devient alors bien moins évident (même si ce n'est pas insurmontable bien évidement).

    En fait mon idée c'était de considérer d'une même manière fonctionnelle l'itération sur les lignes, les colonnes ou les cellules.
    Dans mon cas, ma classe Matrix me permet cela (aussi voir l'exemple dans mon premier post pour les graphes). Actuellement, j'y encapsule une variable représentant le "modèle concret" de ma matrice, mais on peut très bien imaginer un WrapperMatrix qui prends comme paramètre une référence vers ce même type comme l'ont souligné Trademark et jblecanard. Et à l'aide de mes divers héritages et surcharges de mes lambdas each(), je peux faire :

    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
     
    Matrix<int,10,10> matrix;
     
    matrix.each([&]( Matrix<int,10,10>::Line& line ) {
        // J'itère sur mes lignes
        ....
    });
     
    matrix.each([&]( Matrix<int,10,10>::Column& column ) {
        // J'itère sur mes colonnes
        ....
    });
     
    matrix.each([&]( Matrix<int,10,10>::Cell& cell ) {
        // J'itère sur mes cellules
        ....
    });
    Sachant qu'il est ensuite possible d'itérer les types Column et Line pour avoir accès aux cellules.

    Là où je veux en venir c'est que les types Line, Column et Cell ne sont que des "vues" de ma matrice. Par exemple, un type Column est itérable comme l'est un vector classique mais Column n'est pas un container "concret". Il contient en fait une référence au "modèle concret" de ma matrice avec l'index qui corresponds à son emplacement, et un column.each() va jouer avec cet index pour retourner la bonne cellule en faisant penser à l'utilisateur que Column est une séquence puisque qu'il se manipule comme tel, alors que concrètement ce n'est pas le cas. Les types Column, Line et Cell sont générés à la volée et manipulent seulement mon "modèle concret".

    En fait je pense que lorsque l'on défini des containers plus évolués, il peut être utile de les voir et les traiter sous différents aspects. Ici ma matrice peut être vue par lignes, colonnes ou cellules, mais un arbre orienté pourra être vu par ses noeuds, par ses feuilles, ou bien par les liens qu'il y a entre ces noeuds. Bien évidemment, toutes les opérations ne doivent pas être permises. Pas de remove_if sur les différents aspects d'une matrice statique par exemple.

    Je plussoie également le github, qu'on puisse un peu lire du code et/ou proposer des choses.
    Si j'ai le temps ce weekend je vais mettre en place un petit dépôt.

    C'est intéressant de voir que C++11 a en partie motivé ton déplacement vers C++, beaucoup de personnes m'ont dit la même chose. En tout cas, c'est ce genre d'initiatives qui font de C++ un langage toujours vivant, bravo pour cet effort.
    Honnêtement j'ai grave galéré au début, déjà parce que je faisais tout comme en Java et ça m'a pris du temps pour remettre en question ma manière de voir et faire les choses (références et pointeurs à tout va). Même après ça comme j'avais besoin de bonnes performances j'utilisais toujours pas mal les références ou pointeurs pour éviter les copies en paramètre ou retour de fonction par exemple... la gestion mémoire était assez laborieuse. Puis j'ai découvert C++11 avec ses std::move et rvalue par exemple, et franchement ça à tout changé je n'utilise qu'exceptionnellement les pointeurs. Je trouve que le C++11 est clairement le truc qu'il manquait à C++ en gommant ce qui pouvait en faire un langage pénible. Je trouve même qu'il est devenu bien plus puissant que beaucoup d'autres langages (avec les variadic template par exemple) avec une flexibilité de dingue, j'ai même hâte de voir ce que vont proposer les futurs standards.

  11. #11
    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 Mikkkka Voir le message
    je vais prendre un exemple simple où l'on souhaite manipuler un fichier texte et à récupérer toutes les lignes qui contiennent des mots qui commencent par "bb", et pour finir afficher les lignes sélectionnées.
    En "C++ classique", il y à plus simple (overkill les regex pour ça ? :p)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    std::ifstream f("file.txt");
    if(!f) {
    	throw std::logic_error("Impossible d'ouvrir le fichier");
    }
    std::string str((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());
     
    std::smatch m;
    std::regex e(".*bb.*\\n");
     
    while(std::regex_search(str, m, e)) {
    	std::cout << *m.begin(); // la ligne contient déja '\n'
    	str = m.suffix().str();
    };
    Mais sinon, même si le chaînage est des-fois plus lisible, ça reste dur à maintenir, il risque d'y avoir un énorme arbre d'héritage soit directement au niveau des conteneurs, soit au niveau des wrappers.
    C'est la principale motivation pour les fonctions libres
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    conteneur.any_of(...);
    // vs
    std::any(conteneur);
    Dans le cas d'une fonction libre, c'est seulement une surcharge / spécialisation à fournir.

    Par contre la STL devrait fournir des helpers pour agir sur un conteneur complet, du type
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    std::vector<int> vi;
    std::find(vi, 42);
    // qui serait équivalent à 
    std::find(vi.begin(), vi.end(), 42);
    C'est peut être existant dans boost, je sais pas.

  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
    Citation Envoyé par Mikkkka Voir le message
    Mais comment faire de manière simple pour itérer sur mes colonnes, sauf à faire un for de 1 à 10 pour dire je suis sur la colonne i ? Et que dire de cette approche si l'on représente une matrice de 10*10 par un std::array < int, 10*10> pour optimiser l'approche, itérer sur les lignes devient alors bien moins évident (même si ce n'est pas insurmontable bien évidement).

    En fait mon idée c'était de considérer d'une même manière fonctionnelle l'itération sur les lignes, les colonnes ou les cellules.
    Dans mon cas, ma classe Matrix me permet cela (aussi voir l'exemple dans mon premier post pour les graphes). Actuellement, j'y encapsule une variable représentant le "modèle concret" de ma matrice, mais on peut très bien imaginer un WrapperMatrix qui prends comme paramètre une référence vers ce même type comme l'ont souligné Trademark et jblecanard. Et à l'aide de mes divers héritages et surcharges de mes lambdas each(), je peux faire :
    En fait, sur ce point, c’est juste une question de qu’est-ce que tu fournis comme itérateurs. Fondamentalement, ta classe matrice pourrait exposer des itérateurs différents pour itérer selon les lignes, les colonnes, les cellules. Je ne vois pas trop en quoi le concept d’itérateur est incompatible avec ce que tu décris. Que les itérateurs des conteneurs standards ne conviennent pas, oui, mais que tu ne puisses pas utiliser d’itérateurs, non, pas d’accord .

    En fait je pense que lorsque l'on défini des containers plus évolués, il peut être utile de les voir et les traiter sous différents aspects. Ici ma matrice peut être vue par lignes, colonnes ou cellules, mais un arbre orienté pourra être vu par ses noeuds, par ses feuilles, ou bien par les liens qu'il y a entre ces noeuds. Bien évidemment, toutes les opérations ne doivent pas être permises. Pas de remove_if sur les différents aspects d'une matrice statique par exemple.
    Ça ne change rien au fait que si ces vues sont itérables, autant qu’elles le soient par la méthode « standard ». Cela permet de réutiliser beaucoup plus de code.

    Honnêtement j'ai grave galéré au début, déjà parce que je faisais tout comme en Java et ça m'a pris du temps pour remettre en question ma manière de voir et faire les choses (références et pointeurs à tout va). Même après ça comme j'avais besoin de bonnes performances j'utilisais toujours pas mal les références ou pointeurs pour éviter les copies en paramètre ou retour de fonction par exemple... la gestion mémoire était assez laborieuse. Puis j'ai découvert C++11 avec ses std::move et rvalue par exemple, et franchement ça à tout changé je n'utilise qu'exceptionnellement les pointeurs. Je trouve que le C++11 est clairement le truc qu'il manquait à C++ en gommant ce qui pouvait en faire un langage pénible. Je trouve même qu'il est devenu bien plus puissant que beaucoup d'autres langages (avec les variadic template par exemple) avec une flexibilité de dingue, j'ai même hâte de voir ce que vont proposer les futurs standards.
    Oui . La move semantic et les unique_ptr, plus les lambdas ça change vraiment la vie. Dur de revenir en arrière quand on doit bosser sur du code C++03 après ça.

  13. #13
    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 Iradrille Voir le message
    Par contre la STL devrait fournir des helpers pour agir sur un conteneur complet, du type
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    std::vector<int> vi;
    std::find(vi, 42);
    // qui serait équivalent à 
    std::find(vi.begin(), vi.end(), 42);
    C'est peut être existant dans boost, je sais pas.
    Je crois que c’est Alexandrescu qui a bien résumé le problème en disant que les itérateurs, c’est de la merde et qu’on devrait utiliser des « range » à la place. Les range ayant une garantie très forte par rapport aux itérateurs, qui est que le first et le last appartiennent au même conteneur.

    Du coup, c’est implémenté dans D si je ne plante pas. Jamais eu l’occasion de l’utiliser en revanche, donc je ne sais pas si c’est vraiment un gain au final ou pas.

  14. #14
    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 white_tentacle Voir le message
    Du coup, c’est implémenté dans D si je ne plante pas. Jamais eu l’occasion de l’utiliser en revanche, donc je ne sais pas si c’est vraiment un gain au final ou pas.
    Et c'est en partie ce qui permet à une fonction prenant un tableau (ou simplement un range ?) en premier argument d'être utilisée de 2 façons
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    fonction(tableau, ...);
    tableau.fonction(...);
    (Ce qui, au passage, répond directement au problème dont on parle : le chaînage d'appel est possible, sans payer le prix de la maintenabilité)

  15. #15
    Membre averti
    Profil pro
    Inscrit en
    Janvier 2007
    Messages
    301
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Janvier 2007
    Messages : 301
    Points : 345
    Points
    345
    Par défaut
    Avec boost, on peut faire quelque chose de très proche de ce que tu proposes avec ta lib (sur le premier 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
    #include <iostream>
    #include <string>
     
    #include <boost/tokenizer.hpp>
    #include <boost/range/istream_range.hpp>		//istream_range
    #include <boost/range/adaptor/filtered.hpp>		//filtered
    #include <boost/range/adaptor/tokenized.hpp>	//tokenized
    #include <boost/range/algorithm/for_each.hpp>	//for_each
    #include <boost/algorithm/string/predicate.hpp>	//start_with
    #include <boost/algorithm/cxx11/any_of.hpp>
     
    int main()
    {
      using namespace boost;
      using namespace adaptors;
      using namespace algorithm;
     
      typedef char_separator<char> separator_t;
      typedef tokenizer<separator_t> tokenizer;
      auto interesting_lines = istream_range<std::string>(std::cin) | filtered([](std::string const& line) {
        return any_of(tokenizer(line, separator_t(";")), [](std::string const& word) {
          return starts_with(word, "bb");
        });
      });
     
      for_each(interesting_lines, [](std::string const& l) {
        std::cout << l << std::endl;
      });
    }
    Edit: théoriquement y'a même moyen de faire un truc comme ça:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
      auto interesting_lines = istream_range<std::string>(std::cin) | filtered([](std::string const& line) {
        return any_of(line | tokenized(";"), [](std::string const& word) {
          return starts_with(word, "bb");
        });
      });
     
      for_each(interesting_lines, [](std::string const& l) {
        std::cout << l << std::endl;
      });
    Mais j'avoue que chez moi ça ne fonctionne pas (tout les exemples à base de tokenized ne marchent pas...)

  16. #16
    Futur Membre du Club
    Homme Profil pro
    Étudiant
    Inscrit en
    Avril 2014
    Messages
    10
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Avril 2014
    Messages : 10
    Points : 7
    Points
    7
    Par défaut
    En fait, sur ce point, c’est juste une question de qu’est-ce que tu fournis comme itérateurs. Fondamentalement, ta classe matrice pourrait exposer des itérateurs différents pour itérer selon les lignes, les colonnes, les cellules. Je ne vois pas trop en quoi le concept d’itérateur est incompatible avec ce que tu décris. Que les itérateurs des conteneurs standards ne conviennent pas, oui, mais que tu ne puisses pas utiliser d’itérateurs, non, pas d’accord .
    En effet, très intéressante ta remarque. En fait je m’aperçois que je connais mal le fonctionnement interne des itérateurs. Je vais regarder comment ça fonctionne, si c'est simple à implémenter et voir ce que cela pourrait donner.

    En fait vous m'avez tous donné pleins d'idées, je vais préparer un petit code avec un exemple minimal dans le weekend. Par contre cette fois ci je prendrai comme exemple les matrices et pas le chargement d'un fichier. En fait j'ai l'impression que beaucoup ont bloqué la dessus alors qu'en fait à la base je cherchais juste un exemple tout bête pour montrer ma méthodologie, mais je sais qu'il y a d'autres approches/méthodes plus adaptées comme l'utilisation des expressions régulières.

    En gros là je pars sur une structure de données std::array<int, nombre_lignes*nombre_colonnes> pour représenter une matrice. Cette structure sera envoyée dans un wrapper qui permettra de renvoyer des itérateurs sur les différentes vues de la matrice. Ces vues devront permettre d'explorer chaque lignes, chaque colonne, chaque diagonale et chaque cellule de la matrice. Les vues pourront elles mêmes renvoyer des itérateurs vers d'autres vues. Par exemple à partir d'une vue ligne je dois pouvoir itérer sur les vues cellules de la ligne, ou à bien l'inverse repartir vers les vues lignes, colonnes, diagonales à partir d'une vue cellule. Également les vues pourront renvoyer des infos sur le fait concerné, comme les indices i,j d'une cellule (bon c'est déjà le cas en fait).

    Est-ce que ça vous semble pertinent une approche de ce type ?

    Au pire même si cela donne une utilisation bizarre, j'aurai au moins appris à implémenter des itérateurs. Et je suis toujours preneur de ce genre de choses.

    En fait je crois que si j'ai du mal avec l'utilisation des algos dans la librairie standard c'est que j'ai toujours été habitué au "tout objet". Du coup utiliser une fonction externe à ma classe pour la modifier je n'y suis pas du tout habitué, ce n'est pas naturel pour moi.

    Oui . La move semantic et les unique_ptr, plus les lambdas ça change vraiment la vie. Dur de revenir en arrière quand on doit bosser sur du code C++03 après ça.
    Oui c'est clair, avec le C++11 on a presque un tout nouveau langage.

  17. #17
    Membre expert
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2011
    Messages
    739
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hérault (Languedoc Roussillon)

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

    Informations forums :
    Inscription : Juin 2011
    Messages : 739
    Points : 3 627
    Points
    3 627
    Par défaut
    Concernant le tout objet réfléchit à "ce que doit faire la classe ?" et "ce qui peut être fait avec ?".
    La seconde question répond à des besoins que la classe n'a pas besoin de connaître et qui de façon générique s'appliquer probablement sur d'autres objets -> fonction libre
    Après avec la surcharge (ou de façon barbare en regardant si la méthode existe) la fonction libre appel une méthode de la classe. Par exemple un trie sur une liste appel list::sort, dans le cas contraire std::sort.

  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 Mikkkka Voir le message
    En gros là je pars sur une structure de données std::array<int, nombre_lignes*nombre_colonnes> pour représenter une matrice. Cette structure sera envoyée dans un wrapper qui permettra de renvoyer des itérateurs sur les différentes vues de la matrice. Ces vues devront permettre d'explorer chaque lignes, chaque colonne, chaque diagonale et chaque cellule de la matrice. Les vues pourront elles mêmes renvoyer des itérateurs vers d'autres vues. Par exemple à partir d'une vue ligne je dois pouvoir itérer sur les vues cellules de la ligne, ou à bien l'inverse repartir vers les vues lignes, colonnes, diagonales à partir d'une vue cellule. Également les vues pourront renvoyer des infos sur le fait concerné, comme les indices i,j d'une cellule (bon c'est déjà le cas en fait).

    Est-ce que ça vous semble pertinent une approche de ce type ?
    C’est en tout cas dans les grandes lignes comme ça que je le ferais aussi.

    Par contre, la limitation à deux dimensions est totalement arbitraire. Puisque c’est, je suppose, un exercice que tu fais pour l’apprentissage (sinon, je te recommande plutôt d’utiliser l’existant, en particulier sur les matrices il y a énormément d’optimisations subtiles qui font qu’une implémentation maison sera au mieux plus lente, au pire dramatiquement inefficace), je te recommande de réfléchir à comment te passer de cette limite à deux dimensions. Tu dois pouvoir avoir des matrices de dimension n, et itérer selon n’importe quelle dimension.

  19. #19
    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,

    je n'ai pas le temps de lire toute la discussion alors pardonnez-moi si je dis des choses qui ont déjà été dites.
    Tout d'abord, je trouve ton approche, Mikkkka, très positive. La STL et boost ont été conçu de façon à séparer les conteneurs et les algorithmes. Cette approche a beaucoup d'avantages, et elle a fait ses preuves, mais elle n'est pas vraiment conforme à l'idée initiale du paradigme objet, qui consiste à penser les classes en termes de services (ha oui et, on fera comme si la classe std::string n'existe pas). Or ton approche est plus proche de cette philosophie, et je trouve que c'est une très bonne chose de prendre le problème de ce côté et de voir où ça va mener. Et je suis très curieux de voir où ça va mener justement.

    En plus, à part la typo java que je déteste (mais c'est personnel), ton code semble propre et efficace (je n'ai pas regardé dans le détail). Bref, je vais suivre ça de près.
    « 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

  20. #20
    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
    Pour ce qui est des algorithmes qui prendraient un range ou un conteneur entier, je crois que ce que tout le monde attend, c'est les concepts, car sans ça, il y a risques de confusion entre un algo avec un conteneur et un foncteur et un algo avec deux itérateurs.
    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.

Discussions similaires

  1. Réponses: 29
    Dernier message: 04/11/2014, 08h02
  2. votre avis sur les bibliothèques Cappuccino et SproutCore
    Par ClarusAD dans le forum Autres langages pour le Web
    Réponses: 0
    Dernier message: 15/11/2008, 15h23
  3. Donnez votre avis sur les articles de Developpez.com
    Par Geronimo dans le forum C++Builder
    Réponses: 13
    Dernier message: 14/01/2007, 22h00
  4. Donnez votre avis sur les articles de Developpez
    Par Anomaly dans le forum Contribuez
    Réponses: 37
    Dernier message: 29/05/2006, 21h48

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