Conseils écriture Bibliothèque

Conseils écriture Bibliothèque - C++ - Programmation

Marsh Posté le 18-04-2013 à 09:52:43    

Bonjour à tous !
 
J'ai écrit il y a à peu près un an une bibliothèque multiplateforme (si on peut appeler ça une bibliothèque vu q'il n'y a qu'une classe ^^) permettant de traîter des fichiers de configuration (ie lire un fichier et charger dans la RAM, récupérer/modifier une valeur spécifique, réécrire le fichier en conservant les commentaires et les modifications)
Je l'ai un peu remise au goût du jour, notamment en réécrivant pas mal de fonctions pour les optimiser et les rendre "c++11"
 
J'aurais aimé avoir l'avis et les conseils de personnes plus expérimentées que moi sur:
- Le design de la classe (Nommage des fonctions, intuitivité dans l'utilisation, ...)
- L'utilisation du c++11 (Nottament si il y a moyen d'en utiliser plus)
- L'optimisation du code / la robustesse
- L'évolutivité du code
 
Voici ce qui me dérange un peu :
- l'écriture des fonctions template dans le .hpp. J'ai cru comprendre qu'on pouvais pas vraiment faire autrement sans avoir à déclarer quels types pouvaient être utilisés.
- les #include dans le .hpp qui sont donc inclus dans le fichier Main. J'aurais préféré mettre des foward declarations dans le .hpp et faire les #include dans le .cpp, afin de laisser au programmeur gérer lui même ses #include.
 
Voila la page du projet sur GitHub : https://github.com/CromFr/ConfigFile-parser
(une doc est incluse, avec des exemples d'utilisation)
 
Merci d'avance ! ;)
 
 
 
 
PS: Voici le code qui m'a permit de tester l'optimisation :

Code :
  1. #include "ConfigFile.hpp"
  2. #include <iostream>
  3. #include <cstdlib>
  4. #include <chrono>
  5. using namespace std;
  6. int main()
  7. {
  8.     auto temps1 = chrono::system_clock::now();
  9.     const char* sNames[13]={    "NET_Port","PIN_Buzzer","PIN_Motors","PIN_Selector",
  10.                                 "PIN_ADC","MOT_MinSpeed","MOT_PwmFreq","MOT_PwmPrecision",
  11.                                 "SEN_SelectionDelay","SEN_IntegrationDelay","STA_Sensibility",
  12.                                 "STA_RotSensibility","STA_SleepTime"};
  13.     ConfigFile cfg;
  14.     cout<<"Loading config.cfg : "<<ConfigFile::GetErrorString(cfg.Load("config.cfg" ))<<endl;
  15.     cfg.Print();
  16.     for(long i=0 ; i<1000000 ; i++)
  17.     {
  18.         cfg.GetValueString(sNames[rand()%13]);
  19.         cfg.SetValueString(sNames[rand()%13], "ValueChanged" );
  20.         cfg.SetValue<int>(sNames[rand()%13], rand()%13);
  21.         cfg.GetValue<int>(sNames[rand()%13]);
  22.     }
  23.     cfg.Print();
  24.     auto temps2 = chrono::system_clock::now();
  25.     cout<<(temps2-temps1).count()/1000.0<<"ms"<<endl;
  26.     return 0;
  27. }


Avec le fichier config.cfg suivant : https://github.com/CromFr/Aerodrone [...] config.cfg


---------------
Mods: HAF922 | Shinobi XL White    GitHub     Admin de La Colère d'Aurile, serveur RP-Action Neverwinter Nights 2
Reply

Marsh Posté le 18-04-2013 à 09:52:43   

Reply

Marsh Posté le 18-04-2013 à 13:34:02    

Tant que tu distribues ton cpp avec, ca va, mais en règle générale, si tu fais une lib, il vaut mieux considérer que ton utilisateur pourra vouloir  changer plein de trucs sur lesquels tu as l'habitude de reposer.

 

En gros, une paire de consignes totalement génériques :
 * Pas d'appel directs à new/delete et malloc/free : si ton utilisateur veut rediriger ta lib vers un pool mémoire dédié à ta lib, il doit pouvoir le faire.
 * Pas d'appels directs à des créations ou destructions de streams : pareil, si ton utilisateur veut te fichier un stream qui va décompresser un buffer qu'il a déjà chargé en mémoire ou qui traine dans une rom quelconque, il va faire sa classe de stream custom, il faut que ta bibliothèque soit capable de supporter ca.

 


Du reste, je ne suis pas sur de l'intérêt de faire encore un nouveau format de fichier pour de la configuration. Si tu veux du concis, souvent, le INI est suffisant. Si tu veux quelque chose de plus élaboré, tu peux souvent passer directement au XML.
Dans ton cas, le support des tableaux est quelque chose qui n'est pas ultra générique, tu ne peux pas faire un tableau de tableau, par exemple. Du coup en découle une interface qui va nécessairement exposer les types que tu supportes. Vu qu'ils sont en nombre limités, tu peux sans doute t'en sortir avec de la surcharge plutôt qu'avec des templates (et du coup, plus de problème de devoir implémenter ton template dans ton header)

 

Ton test comporte un tout petit jeu de données qui tiennent probablement intégralement en cache de ton processeur. Faire des tests de perfs sur quelque chose d'aussi petit n'est probablement pas représentatif.

 

Autre remarque mineure : tes protections contre l'inclusion multiples pourraient être mieux nommées. Y'a des chances que quelqu'un qui voudrait utiliser ton fichier aie déjà utilisé les mêmes dans un fichier à lui.

 

Voilà pour mes quelques remarques en vrac. J'ai pas fait le tour de tout, cela dit.


Message édité par theshockwave le 18-04-2013 à 14:08:28

---------------
last.fm
Reply

Marsh Posté le 22-04-2013 à 10:48:05    

L'intérêt n'est autre que de m'entraîner à prendre de meilleurs décisions concernant la structure de mes programmes, et de me mettre au C++11.
Entre autres, j'avais fais quelques recherches pour une librairie c++ simple pour des fichiers de configuration simples, mais je n'avais rien trouvé.
 

Citation :

Pas d'appel directs à new/delete et malloc/free : si ton utilisateur veut rediriger ta lib vers un pool mémoire dédié à ta lib, il doit pouvoir le faire.

Je n'ai jamais regardé comment assigner une plage mémoire, et je me demande du coup si unique_ptr et shared_ptr sont quand même alloués dans la plage définie?
 

Citation :

Pas d'appels directs à des créations ou destructions de streams : pareil, si ton utilisateur veut te fichier un stream qui va décompresser un buffer qu'il a déjà chargé en mémoire ou qui traine dans une rom quelconque, il va faire sa classe de stream custom, il faut que ta bibliothèque soit capable de supporter ca.

Si j'ai bien compris il faudrait du coup que j'implémente une fonction Load(streambuf) en utilisant (en gros) sbumpc() à la place de get(char& )
 
merci de tes remarques, je vais regarder dans ce sens ;)


---------------
Mods: HAF922 | Shinobi XL White    GitHub     Admin de La Colère d'Aurile, serveur RP-Action Neverwinter Nights 2
Reply

Marsh Posté le 22-04-2013 à 19:01:22    

crom29 a écrit :

L'intérêt n'est autre que de m'entraîner à prendre de meilleurs décisions concernant la structure de mes programmes, et de me mettre au C++11.


 
Pour une gestion de fichiers de configuration, souvent, c'est ton besoin qui va définir le format vers lequel tu vas t'orienter. Si tu veux faire quelque chose de puissant et générique, tu vas finir par réécrire une sorte de parser XML.
 

crom29 a écrit :

Entre autres, j'avais fais quelques recherches pour une librairie c++ simple pour des fichiers de configuration simples, mais je n'avais rien trouvé.


 
Ah nouveau, comme dit plus haut, ton besoin réel pour ton appli va déterminer le type de fichier que tu vas utiliser. Je ne sais pas pour les fichiers INI que j'ai mentionnés plus haut, mais pour le XML, tu peux assez facilement trouver des libs (genre TinyXML)
 

crom29 a écrit :

Citation :

Pas d'appel directs à new/delete et malloc/free : si ton utilisateur veut rediriger ta lib vers un pool mémoire dédié à ta lib, il doit pouvoir le faire.

Je n'ai jamais regardé comment assigner une plage mémoire, et je me demande du coup si unique_ptr et shared_ptr sont quand même alloués dans la plage définie?


Si tu passes par les fonctions type allocate_shared, elles acceptent un allocateur en argument. Il te suffit donc de proposer dans ta bibliothèque une interface pour définir l'allocateur à utiliser et de t'en servir pour chacune de tes structures de données.
 

crom29 a écrit :

Citation :

Pas d'appels directs à des créations ou destructions de streams : pareil, si ton utilisateur veut te fichier un stream qui va décompresser un buffer qu'il a déjà chargé en mémoire ou qui traine dans une rom quelconque, il va faire sa classe de stream custom, il faut que ta bibliothèque soit capable de supporter ca.

Si j'ai bien compris il faudrait du coup que j'implémente une fonction Load(streambuf) en utilisant (en gros) sbumpc() à la place de get(char& )


 
sbumpc(), ca ne me dit rien du tout. Techniquement, ca ne devrait pas changer l'utilisation que tu fais de tes streams. Il faut simplement que, au lieu d'accepter un chemin vers en fichier prendre directement un stream. Rien ne t'empêche à côté d'avoir une surcharge qui prend un chemin et rappelle directement la fonction avec le stream ouvert comme tu le fais. Le tout, c'est de proposer plus de flexibilité pour ton utilisateur.


---------------
last.fm
Reply

Sujets relatifs:

Leave a Replay

Make sure you enter the(*)required information where indicate.HTML code is not allowed