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 :
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
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; }
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::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; });
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é.
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; });
-> 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 :
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::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; }); });
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.
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; }); });
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.
Partager