Archives pour la catégorie Développement

Tout ce qui touche au développement informatique en général.

Développement Web en C++

En attendant MeetingCpp j’ai commencé cet article, car j’étais étonné qu’il n’y ait aucune session prévue au sujet du développement web en C++. Je le finalise ce soir en rentrant de cette magnifique conférence, afin de me donner le temps de faire de mes nombreuses notes de véritables articles sur les sujets abordés et discutés.

En effet on ne trouve pas des masses de tutoriaux, ni de solutions pour le développement web en C++, du fait de la prédominances des langages tels que Php, Spring, Django, Ruby on Rails etc. dans ce domaine.

J’aimerai présenter dans cet article une des solutions existantes en tant que framework de développement web, permettant de parvenir très rapidement à de bons résultats, comme on peut y être habitué avec CodeIgniter, Zend, Django ou Ruby on Rails, mais avec un gain de performance.

Pourquoi du développement web en C++ ?

Avant de rentrer dans des détails d’implémentation, il est possible de se poser la question pourquoi du dévéloppement web en C++ ? En effet avec la pléthore de langages et de frameworks existants destinés à ce problème, on pourrait penser que cela n’a pas d’autre intérêt que la préférence de ce langage.

En réalité, il y a trois raisons qui peuvent mener à choisir de développer une application web ou un site internet en C++ :

  • Hautes performances & haute disponibilité : Benchmark CppCms vs Java, C#, PHP
  • Réduction du nombre de serveurs, ayant pour conséquence la réduction des coûts et de l’impact sur l’environnement : HipHop for Php: Move Fast
  • Ajout d’une interface web (html ou web service) pour une base C++ existante

CppCms parmi d’autres solutions

J’ai eu le loisir de tester et de comparer différentes solutions (i.e. POCO HttpServer, CppCms, FastCgiQt, WebToolkit, QtWebApp, inetd…) et bien qu’il y ait différents avantages et inconvénients pour chaque solution, la librairie la plus flexible et la plus riche qu’il m’ait été donnée de trouver est CppCms.

CppCms est bel et bien un framework et non un système de gestion de contenu comme son nom souhaiterait l’indiquer, il est très semblable au framework Django en Python ou encore à CodeIgniter en PHP, n’ayant pas grand chose à envier à ces derniers en matières de fonctionnalités disponibles (i.e. Moteur de template statique/dynamique, systèmes de cache, json, jsonrpc, url mapping, génération/validation de forms HTML, modules multilingues/multiencodage, validation des entrées, fonctions cryptographiques/hash, gestion des sessions, gestion des vues, logging, accès aux bases de données avec cppdb, connexion avec apache/lighttpd/nginx via SCGI/FCGI ou HTTP…)

En addition le code et la documentation de CppCms est très claire, à tel point que Boost.Locale qui provient de CppCms a été accepté très rapidement.

Boost ou booster ?

CppCms peut laisser un peu sceptique au premier abord, car ce dernier contient une bibliothèque : booster, qui correspond à une petite partie de boost sur lequel le framework se base en addition à des extensions. Il est donc possible d’utiliser boost ou directement booster dans le code client ou de mélanger les deux sans que cela ne pose problème. Il y a des parties de booster qui font double emploi avec boost tel que booster::shared_ptr qu’il m’est impossible de justifier, mais d’autres qui sont très justifiées tel que booster::aio et qui se différencie de Boost.Asio par différents points abordés plus après.

CppCms : Télécharger & Installer

Je pars du principe que vous êtes dans un environnement unix, même si les étapes sont semblables sous windows.

  • Récupérez le code source : http://sourceforge.net/projects/cppcms/files/
  • Récupérez quelques dépendences :
    apt-get install libpcre3-dev zlib1g-dev libgcrypt11-dev libicu-dev python
  • Compilez :
    tar xf cppcms-1.0.2.tar.bz2
    cd cppcms-1.0.2/
    mkdir build
    cd build/
    cmake ..
    make -jX (Où X correspond à votre nombre de coeurs processeur + 1)
    sudo make install

Hello World

Nous allons pour ce premier article visant à présenter CppCms, implémenter l’infatigable “Hello World”.

Un design pattern que l’on retrouve de nos jours dans la quasi-totalité des frameworks webs est le MVC, utile dans le cas de sites webs, il permet de séparer les données ou la logique (i.e. le Modèle) de l’interface graphique (i.e.la Vue) à l’aide d’un glue code traitant les interactions utilisateurs (i.e. le Contrôleur).

Bien que pour ce premier “Hello World” nous n’allons pas utiliser cette organisation, CppCms ne fait pas exception et met à disposition le concept de contrôleur à l’aide de la classe mère cppcms::application, le modèle avec cppcms::base_content et les vues à l’aide du compilateur de fichier .tmpl : cppcms_tmpl_cc qui permet de mélanger C++ et le format de sortie : JSON, HTML…

Une cppcms::application synchrone simple

CppCms propose différents modèles de programmation, celui auquel un développeur web connaissant PHP, Django, Ruby on Rails ou Java est habitué est le modèle synchrone usant différents threads afin de traiter les requêtes reçues. C’est très simple à comprendre et scalable verticalement comme horizontalement via les systèmes de load balancing proposés avec FCGI ou SCGI (e.g. lighttpd mod_fcgi permet de définir plusieurs processus de gestion des requêtes, locaux ou distants).

Ici nous allons utiliser le serveur web intégré à CppCms en créant un petit projet organisé de la façon suivante:

hello_world/config.js :

hello_world/CMakeLists.txt :

Et dans le dossier src, le CMakeLists qui actuellement produit l’executable, hello_world/src/CMakeLists.txt

Et enfin le code de notre application, hello_world/src/SomeController.hpp :

  1. #ifndef SOMECONTROLLER_HPP
  2. #define SOMECONTROLLER_HPP
  3.  
  4. #include <cppcms/application.h>
  5. #include <cppcms/http_response.h>
  6. #include <cppcms/url_dispatcher.h>
  7.  
  8. class SomeController : public cppcms::application {
  9.     public:
  10.         SomeController(cppcms::service &srv) : cppcms::application(srv) {
  11.             dispatcher().assign("/hello",&SomeController::hello,this);
  12.             dispatcher().assign("/bye",&SomeController::bye,this);
  13.             dispatcher().assign(".*",&SomeController::redirect,this);
  14.         }
  15.  
  16.         void redirect() {
  17.             response().set_redirect_header("/hello");
  18.         }
  19.  
  20.         void hello() {
  21.             response().out() <<
  22.                 "<html>"
  23.                 "<body>"
  24.                 " <h1>Hello World</h1>"
  25.                 "</body>"
  26.                 "</html>\n";
  27.  
  28.         }
  29.  
  30.         void bye() {
  31.             response().out() <<
  32.                 "<html>"
  33.                 "<body>"
  34.                 " <h1>Bye</h1>"
  35.                 "</body>"
  36.                 "</html>\n";
  37.         }
  38. };
  39.  
  40. #endif

Afin de démarrer le service web et monter notre application à une url particulière il nous faut un point d’entrée, hello_world/src/main.cpp :

  1. #include <cppcms/applications_pool.h>
  2. #include <cppcms/service.h>
  3. #include <iostream>
  4.  
  5. #include <SomeController.hpp>
  6.  
  7. int main(int argc, char **argv) {
  8.  
  9.     try {
  10.         cppcms::service srv(argc,argv);
  11.         srv.applications_pool().mount(cppcms::applications_factory<SomeController>());
  12.         srv.run();
  13.     } catch(std::exception const &e) {
  14.         std::cerr << e.what() << std::endl;
  15.     }
  16.  
  17.     return 0;
  18. }

Configuration et mount()

Ici l’instance de cppcms::service que nous créons au sein de la fonction int main(int argc, char **argv) de l’application va justement définir sa configuration en fonction du fichier config.js que l’on passe à l’application. Ce fichier respecte un format JSON avec la possibilité en plus de commenter des lignes à l’aide de //.

Il est possible de configurer à l’aide de la clé “service” la façon dont CppCms doit servir des requêtes. Pour cet exemple nous resterons sur la configuration présente, mais pour une mise en production un petit changement vers fcgi ou scgi sera nécessaire (c.f. Configurer CppCms avec des serveurs webs).

Avec cette configuration, CppCms instancie un serveur http utilisant le nombre de threads donnés par worker_threads pour traiter les requêtes, il utilise ainsi la fabrique : cppcms::applications_factory() pour créer SomeController une fois par worker thread qui traite ce type de requêtes. La méthode mount() permet de définir sous quel chemin racine la cppcms::application sera servie, sans second paramètre, mount() utilise SomeController pour répondre à n’importe quelle requête entrante.

L’appel un peu plus loin à srv.run() lance le serveur HTTP ou FCGI/SCGI selon la configuration et démarre une event loop qui distribue les différentes requêtes au threads sur la base du reactor pattern (i.e. Il est possible de configurer quel backend utiliser via la clé “reactor”, avec l’une des valeurs suivantes : “default”, “select”, “poll”, “epoll”, “devpoll”, “kqueue”, cependant cela n’a rien de nécessaire sur les plateformes communes parce que default choisi toujours la meilleure option pour la plateforme en cours. Vous noterez que l’option iocp est manquante, cela résulte dans l’utilisation de select() sur windows).

Plutôt que des threads il est possible d’utiliser des processus pre-forkés en spécifiant la clé : worker_processes. Cela a deux principaux avantages : un crash d’une des instance de SomeController n’affecte que l’unique requête servie par ce dernier et sous la plupart des systèmes d’exploitations, au dessus un certain nombre de thread la gestion de ces derniers devient si complexe que leur coût en performance devient exponentiel (c.f. Scalable Network Programming, Or: The Quest For A Good Web Server (That Survives Slashdot)).

Cependant en restant sous un nombre réaliste de threads, la rapidité sera meilleure qu’avec fork, tout dépends de combien de clients simultanés doivent pouvoir être supportés et de la longueur du traitement de chaque requête, dans cet exemple nous n’utilisons que 10 threads.

Pour exécuter cet exemple sur votre pc, il vous suffit de taper les commandes suivantes :

Assignement d’url

Comme il est possible de le voir dans le fichier SomeController.hpp lors de l’instanciation de ce dernier il est possible de définir quelle méthode doit être appellée à quelle instance à l’aide du header : cppcms/url_dispatcher.h.

Ce dernier ne se limite pas à cette utilisation simpliste, en effet le premier paramètre est une expression régulière, il est ainsi possible de mapper des éléments capturés de l’url en tant que paramètre de la méthode à appeller, de telle sorte que l’on pourrait écrire pour une méthode avec la signature SomeController::getArticle(string categoryName, string articleId):
dispatcher().assign("/getArticle/(s+)/(d+)", &SomeController::getArticle,1,2);

Ainsi la méthode chargée de traiter la requête n’est appellé que si le format de l’url correspond. Cela n’empêche pas pour autant d’accèder directement à l’url complète ou aux paramètres GET, POST etc. à l’aide de la méthode request() retournant un objet de type cppcms::http::request représentant la requête.

Ecriture de la réponse

Comme il est possible de le voir dans les méthodes redirect(), hello() & bye on manipule la réponse http à l’aide de la méthode response() qui retourne un object de type cppcms::http::response et qui possède une API très riche pour manipuler les réponses, avec des méthodes confortables. Vous trouverez également une méthode io_mode() qui peut être définie sur normal, nogzip, raw, asynchronous ou asynchronous_raw.

De nombreuses autres fonctionallités à découvrir

Comme vous avez pu le constater il y a différents modes d’entrées/sorties possibles dans cppcms. Un mode très intéressant, mais peut-être le moins commun pour les personnes ne connaissant pas boost::asio est le mode asynchrone.

Chaque thread qui fait de l’entrée/sortie a la problèmatique de devoir attendre sur l’achèvement d’une entrée ou d’une sortie pour pouvoir continuer les traitements. On peut résoudre ce problème d’entrées/sorties bloquants les thread la majorité du temps, en délégant le transfert des données aux drivers et au matériel. Laissant ces derniers effectuer les entrées/sorties lorsque c’est actuellement le moment ou possible, et répondre ainsi à d’autres entrées/sorties avec le même thread. Cela décuple de façon très importante le nombre de requêtes pouvant être traitées avec le même nombre de threads, cela implique cependant plus de complexité et nécessite de gérer soit-même le nombre de connexions par thread, afin de rendre l’application scalable.

Pour ceux connaissant boost::asio il y a cependant certaines différences dans la façon avec laquelle CppCms implémente les mêmes principes avec sa librairie booster::aio. En effet io_service::run() ne peut être appellée par plusieurs threads à la fois et laisse tourner sa boucle d’évènement jusqu’à ce que stop() soit appellée et non pas lorsqu’il n’y a plus de tâches postées.

booster::aio à l’instar de boost::asio n’utilise pas iocp sur Windows, parce qu’il utilise toujours le Reactor pattern. Un autre point est qu’il y a moins de fonctionnalités que dans boost::asio et est orienté objet plutôt que concepts.

Je posterai sous peu un article expliquant cela plus en détails et présenterai dans quels cas avec CppCms les applications webs asynchrones sont intéressantes, cela ne ferait aucun sens pour notre hello_world, mais plutôt dans le cas d’implémentation de server-push ou de long-polling à l’aide de la classe : cppcms::http::context permettant de conserver une requête en cours sur le serveur et de la servir partiellement sans bloquer un thread par utilisateur, à l’aide de fonctions telles qu’async_flush_output(handler).

Conclusion

Développer des applications webs en C++ n’est plus un problème et a différents avantages, je n’ai présenté ici que la base la plus simple de ce framework, mais il y a de nombreuses fonctionnalités qui méritent d’être connues comme le support de Json::Rpc, le compilateur de vues/templates, les mécanismes de cache intégrés, les classes de sérialisation/désérialisation, la gestion des sessions, des formulaires et de l’internationalisation.

Si vous souhaitez en savoir plus n’hésitez pas à visiter les exemples et la documentation officielle : CppCMS 1.x.x — Stable. Je serai également ravi de vous répondre ou de vous aider.

Meeting C++ – Conférence

Les 9 et 10 novembre 2012 la plus grosse des conférences sur le C++ en europe aura lieu à Düsseldorf en Allemagne.

Alors que j’ai déjà mon ticket et mon hôtel, et qu’il n’y a malheureusement plus de places libres disponible (i.e. elles se sont écoulée très rapidement), je vais me faire la joie de vous rapporter toutes les sessions auxquelles je vais assister durant ces deux jours.

En effet je vous retransmettrai mes notes sous forme d’articles car la conférence va être très riche en enseignements sur le C++11, Qt, Boost, Clang mais également sur la programmation parallèle, telles que les architectures SIMD (i.e. Single Instruction Multiple Data) ou SMP (Symmetric Multiprocessing) et bien plus encore!

C’est un rêve éveillé pour les amoureux du C++, avec la présence de Michael Wong (Directeur de la délegation d’IBM et Canadian pour le standard C++) et Peter Gottschling (Directeur de la délégation allemande du comité de standardisation ISO du C++), mais également des personnalités, tel que l’organisateur de la conférence qui est l’un des Ambassadeurs Qt : Jens Weller.

Etant donné que plusieurs sessions auront lieu en même temps, je ne pourrai assister à toutes (à moins que je ne tente de courrir d’une salle à l’autre, mais je ne suis pas certain que les articles qui en résulteront seront intéréssants :D). Voici donc mon programme que j’ai choisis pour le premier jour :

  • Registration
    • J’ai pensé que ce serait utile ;)
  • Good C++11 Coding Style
    • Le nouveau standard change radicalement les bonnes pratiques que l’on connaissait en C++98 et bien qu’elles se rapprochent de celles de Boost avec C++03, il intègre de nombreux éléments qui vont nous amener à repenser notre façon d’écrire du code, cette présentation de Michael Wong va donner des conseils sur les premières étapes à prendre pour migrer vers le C++11.
  • R-Value References and Move-Constructors in C++11
    • On parle beaucoup des R-Value References et du nouveau Move operator, ainsi que de leurs limitations, cette présentation me permettra d’écrire un article afin de les utiliser au mieux pour améliorer les performances parfois gaspillées par les copies inutiles.
  • Task-based Concurrency for C++ by using Intel TBB
    • La librarie Thread Building Block d’Intel est très intéressante pour la parallélisation des traitements en C++, durant cette session des exemples seront donnés sur la parallélisation via l’usage d’algorithmes génériques parallèles, de conteneurs concurrents mais également sur le paradigme de programmation FBP (flow-based programming).
  • C++11 in Qt5: Challenges and Solutions
    • Avec C++11 Qt va devoir s’adapter pour utiliser efficacement les nouveaux concepts que le langage offre, Thiago Macieira (Responsable de QtCore & QtDBus) et Marc Mutz (Contributeur Qt depuis plus de 10 an) vont mettre en exergue comment, tout en restant compatible avec les différentes plateformes et compilateurs existants, Qt va s’adapter pour le C++11.
  • C++11: An Overview
    • Le mot clé auto du C++11 (i.e. Fini BOOST_AUTO :D), les fonctions lambda, les initializers lists, les variadic templates… Tout ce qui nous passionne sur ce nouveau standard sera présenté par Rainer Grimm, très connu par les lecteurs du Linux-Magazin allemand pour ses excellents articles.
  • LibTooling – building tools for C++ with Clang API
    • Pour ceux qui ont toujours rêvé d’améliorer la suite d’outils réservés au développement en C++, il est de nos jours possible d’utiliser Clang pour parser l’AST de ce langage magnifique et de travailler à l’aide de celui-ci de façon très aisée. Je suis très intéréssé par ce sujet, étant donné que je travaille ces problèmes à l’aide de plugins pour gcc.

Et à la fin du premier jour, il nous sera possible d’avoir un repas tous ensemble grâce à l’un des sponsors de la conférence, qui prouve un véritable engagement pour le C++ : think-cell.

Des sessions très intéressantes telles que la programmation réseau avec boost.asio, la programmation fonctionnelle en général en C++11 et plus particulièrement à l’aide des mécanismes de polymorphisme mais sans notion d’héritage en C++11 seront données le second jour.

Et encore plus concret, un thème pour lequel la communauté Qt a beaucoup d’intérêt en ce moment : le développement d’applications mobiles pour BlackBerry 10. En effet en sus d’Intel, think-cell et KDAB, RIM sponsorise l’évènement, car ces derniers affichent pour leur nouvelle plateforme une ferme volonté de rendre possible le développement en C++/Qml/Javascript avec Qt.

Cette conférence va être géniale, elle signe avec le nouveau standard C++11 et la grande activité du comité depuis lors, une nouvelle ère pour le C++ et la programmation en général. J’espère pouvoir vous en rapporter le plus possible!

Pour en savoir plus : Meeting C++.

apache

Installation d’un serveur Git avec Apache sous Linux

Cet article a pour but de vous montrer comment mettre en place rapidement et simplement un serveur Git sous Apache, avec un ou plusieurs dépôts, et une authentification par identifiant/mot de passe.

Démarrage

Nous partirons du principe que vous avec un Linux moderne (j’utilise Debian, mais la démarche devrait être similaire sur n’importe quelle autre distribution).

Installez tout d’abord les paquets nécessaires :

Vérifiez ensuite que git est installé :

Si vous obtenez une sortie similaire, c’est que Git est bien installé.

Création du dépôt

Nous allons commencer par attribuer un répertoire à nos futurs dépôts Git. J’ai personnellement choisi “/home/git” mais libre à vous de choisir autre chose. Notez simplement ce chemin, car nous allons y faire référence quelques fois dans les prochaines étapes.

Tapez les commandes suivantes :

www-data est le nom du user sous lequel tourne le démon apache.

Nous placerons tous les dépôts Git dans le répertoire /home/git/repositories. Dans notre exemple, nous créons un dépôt nommé “depot.git” :

La commande “git init --bare” créé un dépôt Git “bare”, c’est à dire un dépôt qui ne possède pas de copie de travail : c’est un dépôt de “stockage” uniquement; vous n’y ferez jamais de commit mais seulement des “push” ou des “pull”.

La commande “git update-server-info” met à jour les données du serveur dans le dépôt, pour lui permettre d’être accédé à distance.

Vous devrez répéter cette dernière étape pour chaque dépôt que vous souhaiterez créer. Notez que vous pouvez également créer une arborescence de répertoires pour organiser plus finement les dépôts.

Paramétrage d’Apache

Nous devons ensuite informer Apache de la présence de nos dépôts Git. Pour ce faire, nous créons un fichier nommé “git” dans le répertoire “/etc/apache2/sites-available” qui contient les paramètres suivants :

Nous définissons une authentification de type “identifiant/mot de passe” mais libre à vous de choisir une autre méthode d’authentification.

Nous supposons ici que le serveur apache possède déjà au moins un “virtual host”. Si vous souhaitez par exemple limiter l’accès à un “virtual host” en particulier (utile dans le cas où l’on souhaite forcer le passage en HTTPS par exemple), il suffit d’imbriquer le code ci-dessus dans la déclaration de ce “virtual host”.

Lorsqu’une personne tentera d’accéder au sous répertoire “/git/” de notre serveur web, elle devra saisir un identifiant et un mot de passe ayant été renseigné dans le fichier “/home/git/passwd“.

Créons maintenant ce fichier grâce à la commande suivante :

Le programme vous invite à saisir un mot de passe pour votre utilisateur. Créez autant d’utilisateurs que vous le souhaitez à l’aide de la même commande (omettez le paramètre “-c” lors des fois suivantes).

Vérifiez que l’utilisateur www-data ait accès à ce fichier, uniquement en lecture.

Vous n’avez plus qu’à activer le site, les modules dav et dav_fs puis à redémarrer apache pour mettre en ligne le dépôt :

Le dépôt devrait désormais, être accessible.

Un petit test pour la route

Testons l’accès au dépôt depuis un autre poste sur le réseau. Vous pouvez bien évidemment utiliser un poste Windows ou Linux, graphique ou en ligne de commande.

Pour la simplicité de l’exemple (et parce que j’adore ça), nous utiliserons la ligne de commande :

Cette commande devrait réussir et créer un répertoire nommé “depot” dans le répertoire courant.

Ignorez l’avertissement qui dit que le dépôt est vide : cela est tout à fait normal.

Remarque : si vous avez choisi d’héberger votre dépôt en HTTPS, git refusera peut-être de s’y connecter si votre certificat n’est pas signé par une autorité de certification reconnue. Vous pouvez définir la variable d’environnement “GIT_SSL_NO_VERIFY=1” pour ignorer l’erreur temporairement. Ne vous servez évidemment pas de cette variable comme solution à long terme !

Conclusion

La mise en place d’un serveur Git accessible sous Apache est donc plutôt simple. Notre configuration ici est minimale et pourrait encore être améliorée, notamment en permettant une affectation des droits plus précise pour les différents dépôts au sein d’Apache.

Vos recherches sur l’Internet vous auront peut-être conduit à d’autres solutions, utilisant gitosis ou encore ssh. Ces approches proposent d’autres modes d’authentification (par certificat ou en ssh avec un compte sur le système) qui peuvent être très intéressantes mais je n’ai personnellement pas réussi à les faire fonctionner correctement pour l’instant. Si vous avez des ressources à partager à ce sujet, n’hésitez pas à me les transmettre :D

J’espère en tout cas que cet article vous aura été utile, et comme d’habitude, n’hésitez pas à me faire part de vos commentaires ! :)

Git-logo

Quelques astuces Git

Ce petit article rapide a pour but de me servir de mémo pour quelques astuces Git que j’ai découvertes récemment et qui serviront peut-être à d’autres personnes.

Supprimer tous les fichiers non-versionnés

J’ai pris l’habitude dans mes projets (surtout ceux en C++) de faire un “scons -c” de temps en temps pour supprimer les fichiers issus de la compilation. Cependant, cette commande ne supprime évidemment pas tous les autres fichiers, eux-aussi générés mais qui proviennent d’autre part (par exemple le “.sconsign.dblite” généré par SCons).

Si votre projet utilise Git, vous pouvez aussi faire :

Qui supprime tous les fichiers non-versionnés du dépôt.

Notez que par défaut, la configuration de Git prévient l’utilisation de “git clean“, en obligeant la spécification du paramètre “-f“.

Vous devrez donc probablement faire :

Pour que ça fonctionne.

Pour ma part, j’ai créé l’alias suivant :

Qui me supprime du dépôt tous les fichiers non-versionnés, ignorés et les répertoires vides.

Ajouter tous les fichiers non-versionnés au .gitignore

Ayant récemment du travailler sur un projet automake/autoconf, j’ai été confronté au problème suivant :

automake/autoconf génèrent tout un tas de fichiers qui ne doivent pas être versionnés, et qui ont donc tout intérêt à être ignorés.

La commande git status m’affichait quelque-chose de ce genre :

Très informatif, certes, mais peu exploitable. Et je n’avais pas envie de jouer du parseur pour traiter une liste de quelques fichiers.

Notez que dans ce cas, vous pouvez simplement utiliser :

Qui va ajouter dans le fichier .gitignore tous les fichiers non-versionnés :)

Je vous invite par ailleurs à regarder l’aide de la commande git ls-files pour voir toutes les options d’affichage qu’elle propose : une vraie mine d’or !

Mais encore…

N’hésitez pas à commenter si avez vous aussi des astuces à partager. Je me ferai une joie de mettre à jour cet article !

Logo officiel de Boost

Boost::Python, dates et conversions

Ceux qui ont déjà joué avec Boost::Python le savent : cette API est très complète mais malheureusement trop peu documentée. Si elle permet de rendre accessible du code C++ depuis un environnement Python avec parfois une déconcertante simplicité, certaines tâches triviales sont en revanche plus difficiles à réaliser qu’on aurait pu l’imaginer.

Si vous êtes sur cette page, c’est très probablement parce que vous aussi devez jouer avec des dates et des durées, de C++ vers Python, inversement ou bien les deux et que vous vous êtes vous aussi heurté à la complexité de Boost::Python.

L’article qui suit est le résultat de mes recherches en la matière, et la synthèse de la solution à laquelle je suis parvenu.

Le code de base

Pour l’ensemble de l’article, nous partirons du principe que vous utilisez la classe boost::posix_time::ptime côté C++, et datetime.datetime côté Python.

Remarque : Si jamais vous utilisiez une autre classe pour vos dates (quelle soit “maison” ou issue d’une autre bibliothèque), vous devriez pouvoir adapter le principe sans trop de problèmes.

Le code minimal pour un module Python avec Boost::Python dont nous partirons est le suivant :

Rien de fou donc, pour l’instant.

Ajoutons une fonction qui prend en paramètre et retourne un boost::posix_time::ptime :

Cette fonction ajoute bêtement 5 secondes à toute date qui lui est passée, sauf si celle-ci n’est pas une date valide.

Si le code actuel compile et donne une bibliothèque Python valide, l’appel de add_five_seconds() depuis l’interpréteur Python provoque la levée d’une exception :

En effet : à aucun moment nous n’avons indiqué à Boost::Python comment convertir une date Python en date C++, ni même que cette conversion était possible.

Pour que ceci fonctionne, il faut ajouter deux “converters” : un de C++ vers Python, et l’autre de Python vers C++.

Conversion de C++ vers Python

Si Boost::Python ne prend pas nativement en charge la conversion de date, il fournit néanmoins des outils puissants pour nous permettre d’y arriver.

Ajoutons le code de conversion à notre exemple précédent :

Analyse

Regardons ligne par ligne les changements apportés.

Le premier include est nécessaire pour utiliser boost::python::to_python_converter; le second pour rendre disponibles les types Python natifs, tels que PyObject ou PyDateTime.

Ici nous déclarons un “converter”. Au sens de Boost::Python, un “converter” est une simple structure ou classe qui contient une méthode statique nommée convert() qui prend un paramètre le type natif C++ à convertir, et qui retourne un PyObject*.

Dans le cas où notre date n’est pas une date valide, nous choisissons ici de renvoyer None. Libre à vous de modifier ce comportement pour satisfaire vos propres besoins.

Remarque : La valeur retournée doit avoir un compteur de référence strictement positif.

Enfin nous déclarons notre “converter” en spécifiant le type natif et la structure/classe à utiliser pour la conversion.

À partir de ce moment là, notre module Python sera capable de convertir implicitement tout boost::posix_time::ptime en datetime.datetime Python.

Conversion de Python vers C++

La conversion dans l’autre sens demande un peu plus de travail :

Analyse

Regardons encore une fois, ligne par ligne les modifications apportées :

Nous ajoutons une structure qui va contenir les routines de conversions. Contrairement à tout à l’heure, et comme nous le verrons plus tard, ceci n’est pas indispensable. Pour effectuer des conversions de Python vers C++, Boost::Python a juste besoin de deux fonctions. Que celles-ci soient statiques au sein d’une classe ou libres n’a aucune incidence.

La fonction is_convertible() sera utilisée par Boost::Python pour déterminer si l’instance Python à convertir peut l’être.

Dans notre cas nous acceptons tout d’abord None comme valeur “valide” pour respecter la symétrie, puis nous testons si l’instance est de type datetime.datetime grâce à la fonction PyDateTime_Check().

En cas de succès, nous renvoyons la valeur passée en paramètre telle quelle. En cas d’erreur, nous renvoyons NULL.

C’est ici que se passe le gros du travail : lorsque cette fonction est appelée, cela signifie que l’instance obj_ptr a passé l’appel à is_convertible() et est prête à être convertie.

Le paramètre data lui contient entre autres l’adresse de la zone mémoire où nous devons instancier notre résultat de conversion. Notez que pour ce faire, nous utilisons le “placement new” qui permet de construire une instance à un emplacement mémoire donné. Le delete correspondant sera automatiquement appelé par Boost::Python au besoin.

Pour finir, nous renseignons le champ convertible du paramètre data pour y indiquer où nous avons alloué notre résultat.

Comme auparavant, la dernière étape consiste à enregistrer le “converter” pour le faire connaître de Boost::python.
On remarque ici que comme énoncé précédemment, l’appel prend en paramètre deux fonctions qui n’ont pas nécessairement besoin de faire partie d’une classe ou d’une structure.

Résultat

Compilez le code ci-dessus (voir le script SConstruct en annexe), puis chargez votre module au sein de l’interpreteur Python :

Ça y est ! La conversion boost::posix_time::ptime <=> datetime.datetime fonctionne parfaitement. :)

Conclusion

Boost::Python est définitivement une bibliothèque très puissante. On peut certes regretter la qualité de sa documentation, mais fort heureusement les ressources à son sujet sur l’Internet ne manquent pas. Son extensibilité la rend utilisable dans toutes les situations et facilite grandement la vie du développeur.

J’espère que cet article vous aura aidé et/ou donné envie de découvrir/utiliser Boost::Python. Comme toujours, n’hésitez pas à me signaler toute coquille, erreur ou optimisation qui m’aurait échappé.

Annexe

Voici le script SConstruct que j’ai utilisé pour la compilation :

Sources

Ces pages m’ont été très utiles lors de la rédaction de cet article :

  • L’API datetime sur python.org (en Anglais);
  • l’API Boost::Posix Time sur boost.org (en Anglais);
  • l’API de Boost::Python sur boost.org (en Anglais);
  • cet article de misspent (en Anglais).
Hiérarchie de classe à héritage multiple

L’héritage en C++

Tous les langages modernes offrent aujourd’hui, par leur nature orientée objet, un moyen puissant de concevoir des hiérarchies : l’héritage. La plupart des programmeurs sont bien entendu déjà familiers avec le principe de dériver d’une classe pour en étendre les possibilités (ou les restreindre) dans une classe fille. Cependant, dans ce domaine, C++ va un peu plus loin que les autres langages en proposant différentes notions d’héritage public, privé, protégé et virtuel. Nous allons tenter, dans cet article, d’expliquer en détails leurs rôles et en quoi elles peuvent se rendre utiles.

Un cas classique

Ce code n’évoque probablement rien de nouveau pour la plupart d’entre vous : nous déclarons une classe Base, qui possède une méthode publique, une méthode protégée et une variable membre privée. Nous déclarons également une classe Derived, qui dérive de Base au travers d’un héritage public. Ce principe d’héritage public est généralement simple à comprendre et il est présent dans pratiquement tous les langages objets : lorsque Derived dérive de Base au travers d’un héritage public, on dit aussi que Derivedest un” Base. Toute instance de Derived peut être considérée comme une instance de Base. Comme nous l’avions vu, dans un article précédent, il est également possible de tester si une instance de Base est une instance de Derived grâce à un dynamic_cast<>. De façon résumée, un héritage publique offre les garanties suivantes :

  • Toutes les membres (variables et méthodes) publics dans la classe de base le sont aussi dans la classe dérivée.
  • Toutes les membres protégés dans la classe de base le sont aussi dans la classe dérivée.
  • Les membres privés de la classe de base ne sont pas accessibles à la classe dérivée.
  • On peut convertir implicitement un pointeur (respectivement une référence) sur la classe dérivée vers un pointeur (respectivement une référence) sur la classe de base.
  • On peut convertir explicitement un pointeur (respectivement une référence) sur la classe de base vers un pointeur (respectivement une référence) sur la classe dérivée.

Ainsi dans notre exemple, Derived peut, dans sa méthode foo() (et indépendamment de la visibilité de cette méthode), appeler la méthode protégée de son parent, set_x(). Elle ne peut pas en revanche, directement accéder à m_x qui est dans une section privée de la classe de base.

Le rôle de l’héritage public

Une concept erroné va généralement de pair avec l’utilisation de l’héritage public : il consiste à dire que ce type d’héritage est utilisé principalement pour favoriser la réutilisation de code. Ceci n’est pas (ou ne devrait pas) être la raison : en C++, il existe beaucoup de moyens de réutiliser son code, la façon la plus efficace étant la composition. Le fait de d’utiliser un héritage public doit être une décision fonctionnelle, et pas une décision technique : ainsi, la classe Table dérive de la classe Meuble parce qu’une table “est un” meuble, et pas parce qu’en faisant ainsi on évite de retaper du code. L’héritage (public ou non) s’il apporte quelque-chose, c’est principalement de la flexibilité.

Bonnes pratiques

Certains auront peut être pensé la chose suivante : “La méthode set_x() ne sert à rien. Autant déclarer m_x protégé directement : il y aura moins de code”. Si cette remarque part surement d’une bonne intention (après tout, avoir moins de code est en général une très bonne idée), elle risque ici d’avoir des conséquences fâcheuses : En procédant de la même façon que dans l’exemple, nous ajoutons certes une méthode, mais nous réduisons également le couplage. En optant pour la solution “simplifiée”, si pour une raison ou pour un autre, je suis amené à modifier l’implémentation de Base et à renommer par exemple m_x en m_first_x, je devrais alors modifier non seulement tout le code de Base mais aussi tout le code de Derived (puisque nous y faisons référence à m_x directement). Le fait d’ajouter une méthode protégée à Base permet de figer son interface “publique” et réduit donc drastiquement le couplage. Il en résulte un code bien plus maintenable. En règle générale, on retiendra que les variables au sein d’une classe seront toujours soit publiques, soit privées, mais très rarement protégées. Et si dans un cas particulier vous sentez avoir vraiment besoin d’un accès à un membre privé, préférez la directive friend qui augmentera bien moins le couplage.

Cette règle n’est évidemment pas absolue et vous risquez de rencontrer un scénario où déclarer une variable membre protégée est la bonne chose à faire. Cependant, ça ne devrait logiquement pas être votre premier réflexe.

L’héritage privé : une alternative à la composition

Qui n’a jamais écrit par erreur, au cours d’une soirée (nuit ?) un peu trop longue quelque-chose de ce genre :

Il en résulte en général de longues minutes très agaçantes où l’on tente de comprendre pourquoi le compilateur refuse systématiquement d’utiliser les variables de la classe parente. La raison est plutôt simple : en l’absence d’un attribut de visibilité, l’héritage par défaut en C++ est privé. Le code précédent est donc équivalent à :

Voilà une bonne occasion d’expliquer à quoi sert ce type d’héritage. L’héritage privé offre les garanties suivantes :

  • Tous les membres publics dans la classe de base sont privés dans la classe dérivée.
  • Tous les membres protégés dans la classe de base sont privés dans la classe dérivée.
  • Les membres privés de la classe de base ne sont pas accessibles à la classe dérivée.
  • La classe dérivée peut redéfinir les méthodes virtuelles de la classe de base.
  • Toutes les méthodes de la classe dérivée peuvent convertir un pointeur (respectivement une référence) sur Derived en pointeur (respectivement une référence) sur Base. Ceci n’est en revanche normalement pas possible en dehors de la classe dérivée. (Voir tout de même la remarque).

Là où un héritage public traduit une relation de type “est un”, un héritage privé lui traduit une relation de type “est implémenté en tant que”. En pratique, un héritage privé est comparable à une composition de type 1-vers-1. Prenons un exemple plus parlant :

Nous avons déclaré une classe Engine (moteur) et Car (voiture) qui en dérive de façon privée. Dans la déclaration de Car, nous indiquons, au sein d’une section publique et grâce au mot clé using, que nous utilisons la méthode start() de la classe parente. Celle-ci devient donc publique pour la classe fille et en dehors. Dans ce cas, on voit bien qu’un héritage public n’aurait aucun sens puisqu’une voiture “n’est pas” un moteur, elle “possède un moteur”. Certains se demandent peut être si une composition n’est pas plus indiquée dans ce cas et la réponse n’est pas évidente :

  • En règle générale, oui, préférez la composition à l’héritage…
  • … mais dans le cas que nous présentons ici, la composition est assez “forte” : une voiture ne peut toujours avoir qu’un seul moteur, et le fait de démarrer la voiture revient à démarrer le moteur.

Les cas où l’on peut légitimement accepter un héritage privé en lieu et place d’une composition restent très limités. Et personne ne vous blâmera si même dans ce cas, vous optez pour la composition. L’héritage privé est surtout une facilité du langage pour éviter au programmeur de saisir du code inutile : dans notre exemple, en utilisant une composition, on aurait obtenu quelque-chose de ce genre :

Pas vraiment plus difficile à comprendre, mais un peu plus long à taper. Et si ce n’était pas une mais vingt méthodes de Engine qu’il avait fallu “transporter” dans l’interface publique de Car, je vous laisse imaginer le temps perdu à saisir toutes les méthodes triviales qui ne font qu’appeler la “vraie” méthode du sous-objet. Dans tous les cas, notez qu’en t’en qu’utilisateur de la classe Car, vous ne devez jamais prendre en compte la présence d’un héritage privé dans sa définition : cet héritage est privé et relève de l’implémentation : l’auteur de la classe peut à tout moment décider de réécrire sa classe pour utiliser une composition classique, ou encore de redéfinir à la main chacune des méthodes, sans vous en notifier !

Remarque

Un lecteur assidu aura peut être remarqué une contradiction avec un de mes articles précédents : en effet, si j’ai indiqué un peu plus haut qu’il n’était pas possible de convertir un Car* en Engine* en dehors des méthodes de Car, ce n’était pas tout à fait vrai : cela est possible en C++, par l’intermédiaire d’une “conversion à la façon C”. Évidemment, si on respecte la clause énoncée précédemment de ne jamais se baser sur le détail d’implémentation que représente l’héritage privé, il ne s’agit en fait que d’un argument supplémentaire contre l’utilisation de ce type de conversion. En un mot : faites-le une fois pour vous amuser, puis plus jamais !

À propos du mot clé using

S’il est très courant de rencontrer le mot clé using lors de l’utilisation d’héritage privé, il est également possible de l’utiliser lors d’un héritage “classique” public :

Ici, nous avons augmenté la visibilité de la méthode set_x() en la rendant publique dans la classe dérivée, alors qu’elle n’était que protégée dans la classe de base. Bien que peu courante, cette syntaxe n’en demeure pas moins tout à fait correcte.

L’héritage protégé

L’héritage protégé ressemble énormément à l’héritage privé. En fait, sa seule différence est la suivante :

  • Tout membre public ou protégé hérité au travers d’un héritage protégé est également accessible de façon protégée aux classes filles de la classe dérivée.

Ce qui se traduit par :

Ici Robot peut accéder à la méthode protégée de Engine parce que Car hérite de Engine au travers d’un héritage protégé. On voit que si l’héritage privé peut s’avérer utile dans certains cas, l’héritage protégé lui a un intérêt beaucoup plus limité, car le fait de pouvoir accéder aux méthodes de sa classe grand-parente ne fait qu’augmenter le couplage.

Je n’ai encore jamais rencontré ou eu besoin d’un héritage protégé depuis que je développe en C++.

L’héritage virtuel

Pour expliquer en quoi consiste l’héritage virtuel, replaçons avant tout dans le contexte quelques lieux communs :

L’héritage multiple

C++ est l’un des rares langages à autoriser l’héritage multiple là où les autres langages préfèrent imposer la notion d’interfaces. La légitimité de l’une ou de l’autre de ces techniques est un sujet à part entière et sort du cadre de cet article. Si vous vous intéressez à ce débat, il existe sur l’Internet bien des discussions à ce sujet.

La minute “rebelle”

Vous avez certainement déjà entendu quelqu’un dire une chose du genre : “L’héritage multiple c’est toujours une hérésie, ça n’aurait jamais du exister ! Je n’ai jamais réussi à en faire quoi que ce soit d’utile.”

Face à ce genre de remarque simpliste, j’ai tendence à avoir la même réaction que Marshall Cline : les gens qui vous disent ça ne connaissent à priori pas votre problème ou vos besoins, et pourtant ils prétendent y répondre : comment le pourraient-ils ? Comment peuvent-ils savoir que dans votre situation, l’héritage multiple n’est pas la meilleure solution ? Si vous rencontrez ce genre de personnes, soyez prudents : le manque d’ouverture d’esprit et de réflexion fait en général de bien piètres programmeurs. Une phrase que j’aime bien citer dans cette situation est celle-ci : “Toutes les phrases qui dénoncent un principe de façon absolue et générale sont absolument et généralement fausses”. Si l’héritage multiple semble être la solution à votre problème, n’hésitez pas à l’utiliser.

Dans tous les cas, réfléchissez toujours bien à ce que vous faites : si l’héritage multiple est tant victime d’à priori négatifs, c’est qu’il est parfois difficile de comprendre son utilisation.

La démonstration par l’exemple

Pour illustrer les notions d’héritage multiple et virtuel, créons tout d’abord une hiérarchie de classes :

Hiérarchie de classe à héritage multiple

Comme nous le voyons ici, nous partons d’une classe Shape (“forme” en anglais), de laquelle dérivent deux classes Rectangle et Diamond (“losange” en anglais). Une quatrième classe, Square (“carré” en anglais) hérite à la fois de Rectangle et de Diamond.

Une implémentation naïve de cette hiérarchie en C++ pourrait ressembler à :

Cela a du sens : un carré (si on s’en réfère aux lois de la géométrie) est à la fois un rectangle et un losange, et il est aussi une forme.

Cependant, ce code n’a en pratique pas la structure souhaitée :

En effet, en utilisant un héritage classique (non virtuel) Square hérite en pratique deux fois de Shape : une fois par la branche de gauche (Rectangle), et une fois par la branche de droite (Diamond). Il en résulte que chaque instance de Square possède deux instances de m_identifier. Lorsqu’on souhaite utiliser identifier() ou m_identifier dans ou en dehors de la classe Square, il faut préciser par quelle branche on passe :

  • Soit en préfixant m_identifier par Diamond ou Rectangle (“Rectangle::m_identifier“);
  • soit en effectuant auparavant une conversion de this vers Rectangle* ou Diamond* (“static_cast(this)->m_identifier“)

Ce n’est généralement pas ce qui est souhaité.

Pour résoudre ce problème, nous avons besoin de l’héritage virtuel :

En spécifiant que Rectangle et Diamond héritent tous deux virtuellement de Shape, nous empêchons la multiple instanciation du type de base : il n’y a alors plus qu’une seule instance de m_identifier et on peut y référer directement sans avoir recours à des conversions.

Bonnes pratiques

L’utilisation de l’héritage virtuel peut se faire conjointement avec tout autre type d’héritage (privé, protégé et public), mais est habituellement rencontré principalement avec l’héritage public.

Indépendamment de l’utilisation de l’héritage virtuel, on constate assez logiquement que les classes les plus en haut de la hiérarchie devraient dans l’idéal être virtuelles pures. Si c’est le cas, cela ne signifie pas forcément qu’il faille supprimer l’héritage virtuel : outre le fait de ne pas dupliquer inutilement les instances des membres, l’héritage virtuel permet également de s’assurer que la classe de base n’a qu’une seule adresse dans les instances filles. Il est en pratique très probable que vous deviez user d’héritage virtuel lorsque vous utilisez l’héritage multiple.

Conclusion

Nous avons vu au travers des différentes sections que C++ est très complet en matière d’héritage. Élément incontournable de la programmation orientée objet moderne, c’est un principe qu’il convient de manier avec la plus grande précaution et une bonne réflexion : si l’héritage peut résoudre bien des problèmes, il n’est de loin pas la solution universelle. Que vous optiez pour l’héritage, la composition ou autre chose pour la résolution de vos problèmes, réfléchissez toujours et envisagez chacune des possibilités.

Comme toujours, n’hésitez pas à me faire part de vos remarques, questions ou corrections dans les commentaires.

Sources

Voici une série de liens en anglais qui m’ont inspiré pour la rédaction de cet article. N’hésitez pas à les consulter, ils sont extrêmement intéressants :

Necessitas : Qt pour Android – Alpha Release

Dans un article précédent j’ai pu vous présenter le port de Qt pour Android dans une de ses premières versions. Depuis lors j’ai pu participer au projet en matière de documentation, et j’ai le plaisir de vous annoncer que ce dimanche Qt pour Android est sorti en version Alpha.

Il est désormais possible de mettre en place l’environnement de développement en 5 minutes et l’intégration de Qt Creator pour Android est intuitive comme jamais.

Integration Android pour Qt Creator

Cette Release comme l’a dit BogDan Vatra sur la mailing list est une des plus importantes, car le sdk Necessitas met à disposition tout les outils nécessaires au développement sur Android avec Qt. En effet ce sdk propose Qt précompilé pour les différentes versions du système d’exploitation mobile de Google, Qt Creator pour Android, et l’application Ministro. Ministro est déjà disponible sur le Market de Google, elle télécharge et installe les bibliothèques Qt sur votre système dès qu’une application Qt pour Android en a besoin.

Il est possible de tester Qt sur Android en installant Demo – Aminated tiles qui vous demandera d’installer Ministro afin de télécharger les dépendances de Qt.

Pourquoi des noms latins ?

Les termes Qt et Android ne sont pas utilisés suite à un conseil de Nokia de ne pas utiliser le nom Qt pour des raisons légales, Google quant à lui n’a pas répondu au sujet d’Android.

Necessitas

Le nom Necessitas a été choisi par le fondateur de Qt pour Android pour différentes raisons. Necessitas est la déesse Romaine de la nécessité, et la personnification de la destinée et de la fatalité. Le choix de la déésse de la fatalité est vraisemblablement un clin d’oeil à la récente décision du PDG de Nokia (Stephen Elop) de basculer la stratégie principale de Nokia (Propriétaire actuel de Qt) en matière de téléphonie mobile de MeeGo avec Qt vers Windows Phone 7 sans Qt.

Ministro

Ministro est également un mot latin qui signifie assister, rendre un service, et c’est véritablement ce que cette application fait en téléchargeant et installant sur le système les librairies Qt pour les applications qui en ont besoin. En effet sans lui le projet est quasiment inutile, c’est la garantie que toutes les applications Qt qui seront déployées fonctionneront sur toutes les versions et périphériques Android sans aucunes modifications. Ministro télécharge les librairies Qt nécessaire sur les serveurs de sourceforge, dans une prochaine version cela utilisera les différents mirroirs de sourceforge et ainsi celui le plus proche du périphérique Android connecté.

Industius

eu.licentia.necessitas.industius est le nom du package java de Qt pour Android. Cela signifie assidu et travailleur, c’est la partie du code qui contient les différents bindings JNI et qui charge les librairies Qt, les autres librairies natives et enfin l’application Qt choisie.

Version Alpha

Actuellement il n’y a pas de limitations ni de bugs critiques connus, cependant l’API Qt 4.8.0 n’est pas encore considérée comme stable et c’est pourquoi il ne faudrait pas, pour le moment, envoyer les applications faites avec Necessitas sur le Market d’Android avant que Nokia ou que l’équipe de Qt pour Android ne propose une version stable de Qt 4.8. En effet une version stable aura la garantie d’être toujours disponible à l’aide de l’outil Ministro.

Framework Qt

L’ensemble du Framework Qt est porté (ou alors de petites parties sont encore en cours), toutefois cette version Alpha est distribuée avec le sw plugin platform. sw vient de SingleWindow, c’est un plugin Android qui utilise uniquement une fenêtre Android native pour afficher toutes les fenêtres Qt. Cela ne veut pas dire que vos applications ne peuvent comporter qu’une seule fenêtre, cela signifie simplement que les fenêtres sont affichées au travers de l’implementation du Framebuffer de Qt. Le plugin sw de Qt pour Android fait ainsi simplement la connexion entre le système d’exploitation et l’implémentation du Framebuffer de Qt.

Le seul point négatif de ce plugin est qu’il ne supporte pas les accélerations matérielles telles que l’Open GL. Une fois que le plugin sw sera stable pour une utilisation en production, le plugin mw de Qt pour Android sera mis à jour et permettra l’utilisation des accélérations matérielles OpenGL.

Ainsi hormis QtMobility qui est actuellement en train d’être porté, il vous est possible d’utiliser l’ensemble des bibliothèques Qt pour vos applications Android. ;)

Qt Creator pour Android

“Créez, Gérez, Compilez, Déployez & Deboguez.” – BogDan Vatra, c’est ainsi que ce dernier a présenté l’intégration Android pour Qt Creator. Soit une intégration parfaite de Qt Creator qui vous permet de développer pour Android comme si vous développiez une application de bureau.

Debogage d'application Qt sur Android

SDK & NDK Officiels d’Android + Débogage

Les points qui gênaient un peu dans les premières versions d’android lighthouse ont été modifiés. En effet désormais Necessitas ne fait plus usage d’un Native Development Kit non-officiel et tire pleinement partie des outils officiels d’Android. Aussi le débogage des applications est maintenant possible en remplaçant la version de gdb du ndk officiel par une version plus récente et en choisissant une version d’Android supportant le débogage d’applications natives multi thread.

Installer Necessitas

La documentation du sdk de necessitas est disponible en anglais et est mise à jour sur le wiki officiel du projet: ici.
Elle explique étape par étape comment installer le sdk et comment l’utiliser pour créer une première application. Il ne fait pas sens de la traduire en français (l’ayant moi-même écrite en anglais) car elle est d’une part encore très vivante et d’autre part du fait de sa légèreté il n’est pas nécessaire de comprendre la langue de shakespeare pour la lire. L’installation et la configuration, téléchargement compris est possible en 5 minutes.

Contribuer

Le projet est Open Source et c’est pourquoi je vous invite à y contribuer. En effet il vous est maintenant possible à l’aide de Necessitas d’installer en trois clics le Sdk pour développer des applications Qt sur Android. Les lignes à suivre pour la contribution sont disponibles sur le wiki du projet.
Une liste des tâches est disponible ici ainsi qu’au travers des gestionnaires de tickets du projet sur google code et sur sourceforge.

Conclusion

Afin d’augmenter mes contributions auprès de Freelan et de Necessitas, je me lance dans la réalisation d’un prototype de freelan pour Android. Cela inclus le portage pour Android de la fameuse libsystools. Je pense que l’apport de libsystools sur Android sera intéressant et surtout cela permettra d’avoir freelan: un vpn décentralisé facilement & simplement au bout des doigts. J’ai hâte de vous tenir au courant de l’avancement de ce petit projet. ;)

Logo de Git

Découverte de Git sous Windows

J’ai commencé à entendre parler de Git il y a quelques temps déjà mais je n’avais jamais eu l’occasion de vraiment travailler avec. Pour moi, son seul intérêt était qu’il était distribué, mais sans comprendre exactement ce que ça impliquait. Aujourd’hui les choses ont changé et j’ai, pour mon plus grand bonheur découvert un outil incroyablement puissant et efficace. À mon sens, git n’est pas simplement un autre gestionnaire de sources, c’est une nouvelle façon de penser le développement. Dans cet article, nous allons brièvement présenter Git en comparaison avec d’autres gestionnaires de sources plus “classiques” puis expliquer son installation sous Windows.

Présentation

Il existe de très nombreuses ressources sur l’Internet (mais pas que) qui présentent Git de façon très complète. L’objectif de cet article n’est pas de faire de vous un expert Git en quelques pages mais simplement de vous présenter l’outil et de, j’espère, vous donner l’envie de l’utiliser. si vous connaissez déjà suffisamment Git, vous pouvez sauter la section suivante et directement vous rendre à la partie “Installation sous Windows”.

Un gestionnaire de sources

Git est un gestionnaire de sources, comme Subversion (SVN), CVS ou encore Visual Source Safe (VSS). Un outil qui assure principalement les rôles suivants :

  • Il permet de stocker différentes versions du code source.
  • Il permet un travail collaboratif en facilitant la synchronisation entre différents développeurs.
  • Il permet de savoir qui est à l’origine d’une modification

Les trois derniers outils que j’ai cités ont en commun leur architecture centralisée : tous les développeurs synchronisent leurs fichiers de code source auprès d’un dépôt central. Cette approche, très simple à comprendre est souvent plébiscitée par les entreprises, pour différentes raisons :

  • L’administration d’un dépôt central est plutôt simple : on peut régler finement et individuellement les droits d’accès.
  • Tout le monde comprend la notion de “publier” ses sources dans un dépôt central.
  • En entreprise, en général, les liaisons réseaux sont fiables et robustes; l’accès 24H/24 au dépôt est quasi-garanti.
  • Les clients ne disposent que du code source sur lequel ils travaillent actuellement; l’historique reste, lui, dans le dépôt.

Git se distingue principalement par son architecture distribuée. Avec Git, il n’y a pas de dépôt central, principal, ou autre : chaque dépôt Git contient tout l’historique du dépôt, de façon autonome. Il n’y a pas d’entité supérieure. Les dépôts peuvent communiquer entre eux selon des règles très simples. Ses avantages principaux sont les suivants :

  • Le dépôt, puisque local et autonome, est toujours accessible et extrêmement rapide, même lorsque déconnecté du réseau.
  • Git ne stocke pas les fichiers, mais les différences entre les fichiers : ce principe lui permet de disposer d’un des mécanismes de fusion (ou “merge”) les plus efficaces.
  • Contrôle total sur le dépôt : on ne dépend plus d’une entité externe. Avec une solution centralisée, si le dépôt nous lâche, adieu l’historique.
  • Un système de branchage extrêmement efficace.

Note : Il n’y aucun mal à utiliser un gestionnaire de sources centralisé; c’est d’ailleurs dans bien des cas la solution la plus adaptée. Cependant, puisque cet article parle essentiellement de Git, nous allons nous concentrer sur des cas où l’utilisation de Git semble plus opportune. Vous trouverez ici un historique plus précis de git et des raisons de sa création.

Avant de commencer

Si vous êtes déjà habité à utiliser un gestionnaire de sources “classique”, avant de continuer prenez quelques minutes pour vous rappeler de leur fonctionnement puis oubliez tout ! En tout cas temporairement : l’utilisation de Git est drastiquement différente et lui appliquer la logique d’un gestionnaire de source centralisé n’aurait aucun sens. Pour ma part, je suis habitué à utiliser SVN et il m’a fallu une certaine période d’adaptation pour comprendre que son fonctionnement était plus différent qu’il n’y paraissait.

Note : Bien que différents, il est tout à fait possible d’utiliser un dépôt SVN avec la ligne de commande git, grace à “git svn” qui s’avère être un moyen très simple de découvrir git sans changer ses dépôts existants. Je l’utilise depuis plusieurs semaines maintenant et j’en suis vraiment très satisfait.

Concepts

Chaque dépôt Git conserve un historique de tous les changements (ou “commits”) depuis sa création, potentiellement au sein de plusieurs branches. Au sein d’un dépôt, on ne peut travailler à la fois que dans une seule branche et sur un seul commit. Lorsqu’on a effectué des changements au sein d’une branche, on peut librement les enregistrer à la suite de la branche (on parle de faire un “commit”) et continuer (éventuellement en allant travailler dans une autre branche). Il est par la suite possible de fusionner (faire un “merge”) de plusieurs branches pour répercuter les changements de l’une dans l’autre. La philosophie de Git, c’est de faire des commits très souvent, même (et surtout) pour de petites modifications. Plus vous “commiterez” souvent, plus il sera facile pour Git de fusionner vos changements avec ceux des autres.

Installation sous Windows

J’ai longtemps travaillé avec Subversion (SVN) sous Windows en utilisant TortoiseSVN. Cet outil graphique est plutôt bien pensé et m’a toujours satisfait. En voulant essayer git, je me suis donc naturellement porté vers TortoiseGIT mais je dois avouer que j’ai été déçu. Je pense que les gens derrière TortoiseGIT ont fait un travail remarquable et ils continuent d’améliorer le logiciel, mais à l’heure actuelle l’outil me paraît plus contraignant à utiliser que sa version classique en ligne de commande (comme sous Linux). C’est donc sur cette dernière que va porter l’installation.

Téléchargements

Commencez par télécharger git en vous rendant sur cette page. Prenez la dernière version existante de git (Version “Git-1.7.3.1-preview20101002.exe” à l’heure actuelle). Exécutez l’installation :

Installation de git

Installation de git

Choisissez les modules que vous souhaitez installer. Pour ma part, j’ai choisi ces modules :

Modules à installer

Modules à installer

Enfin, l’installeur vous demande de quelle façon vous souhaitez installer git. Les deux premières options nécessitent l’utilisation d’une console de type UNIX (msys/cygwin) pour l’utilisation de Git, la dernière permet d’utiliser git depuis une console native (type “cmd.exe” ou encore Powershell).

Mode d'installation de git

Mode d'installation de git

C’est cette dernière option que nous choisirons pour les raisons suivantes :

  • La console UNIX est vraiment très puissante et efficace… mais, à mon sens, assez peu adaptée à Windows.
  • De nombreux développeurs Windows utilisent déjà Powershell et donc il faut pouvoir utiliser git depuis n’importe quelle console. Changer de console juste pour commit ses changements dans git n’est pas envisageable.

Notez que comme il est indiqué dans l’installeur, la dernière option va remplacer une partie des outils Windows (tels que find.exe et sort.exe) par leur équivalent GNU. Si vous utilisez ces outils, vous pourrez toujours les utiliser, mais il faudra les préfixer par leur chemin complet. Une fois l’installation terminée, lancez une console puis saisissez la commande :

Si vous obtenez la sortie suivante :

Obtenir la version installée de git

Obtenir la version installée de git

C’est que git est correctement installé et fonctionnel. Félicitations !

Configuration préliminaire

Il est possible de configurer un bon nombre de choses dans git. Cela passe du nom d’utilisateur au proxy à utiliser ou à l’activation des couleurs lors de l’affichage des informations du dépôt. Les configurations de git sont soit globales, soit propres à chaque dépôt. En pratique, si une valeur de paramètre n’est pas trouvée au sein du dépôt, git va regarder la configuration globale. Voici les paramètres les plus couramment modifiés :

Le deux premiers sont assez explicites; les quatre suivant activent les couleurs lors des commandes associées (ce qui est quand même plus sympathique). Il existe évidemment encore bien d’autres paramètres, je vous invite à consulter la page de manuel de git-config pour les découvrir.

Utilisation basique

Le moyen le plus efficace, c’est de pratiquer. Commençons par créer un dépôt git :

Création d'un dépôt git

Création d'un dépôt git

Note : Plutôt que de créer un dépôt vide, on aurait également pu choisir de cloner un dépôt existant avec la commande “git clone”.

Créons un fichier à ajouter au dépôt :

Création d'un fichier à ajouter au dépôt

Création d'un fichier à ajouter au dépôt

La commande “git status” permet d’interroger le dépôt sur le statut actuel de la copie de travail (ou “working copy”). Ici, on voit que le fichier main.cpp a été créé mais n’est pas suivi (ou “tracked”) par le dépôt. Indiquons à git que ce fichier doit être suivi et faire partie du prochain commit :

Ajout d'un fichier au dépôt

Ajout d'un fichier au dépôt

Pour ce faire, nous avons utilisé la commande “git add” qui permet d’indiquer qu’un fichier (même si il est déjà suivi) doit faire partie du prochain commit.

Après “git add”, “git status” nous indique bel est bien que le fichier doit être suivi. Nous pourrions à ce moment là, ajouter d’autres fichiers, en modifier, voire en supprimer ou en déplacer certains. Il faut simplement signaler à git que le changement (quel que soit sa nature) doit faire partie du prochain commit.

Nous nous contentons de ce simple ajout pour l’instant.

Historisation d'un changement

Historisation d'un changement

La commande “git commit” permet de sauver le changement au sein du dépôt. Ici le paramètre “-m” permet de spécifier un message à associer au commit. Sans ça, git aurait ouvert un éditeur de texte pour nous demander de saisir notre message.

Note : La saisie d’un message est extrêmement importante. Les messages ne doivent pas forcément être très longs, mais il ne devraient jamais être vides. À quoi sert-il de mettre ses changements sous historiques si on est incapable de dire pourquoi ils ont été faits ? Même lorsque vous travaillez seul, prenez l’habitude de toujours renseigner des messages informatifs pour chacun de vos commits. C’est une habitude que vous ne regrettez pas.

Après le commit, on constate que la “working copy” a été mise à zéro : “git status” indique qu’aucun changement n’a été apporté depuis le commit actuel. Nous pouvons bien entendu dès à présent consulter l’historique de notre dépôt grâce à la commande “git log” :

Consultation de l'historique

Consultation de l'historique

Le commit est bien dans l’historique. Il s’est vu assigner un identifiant unique “c0d281e3532a4970415bba1e9159b1dc7ed816b1″. Modifions maintenant le fichier main.cpp, de la façon suivante :

Le fichier main.cpp modifié

Le fichier main.cpp modifié

Après sauvegarde des modifications, on utilise “git status” qui nous informe qu’en effet, main.cpp a été modifié par rapport à la version actuelle :

Le fichier main.cpp a été modifié

Le fichier main.cpp a été modifié

Notons que bien que modifié, le fichier n’est pas automatiquement marqué comme faisant partie du prochain commit.

Nous pouvons afficher la liste des modifications apportées au fichier grâce à la commande “git diff” :

Utilisation de git diff pour lister les changements

Utilisation de git diff pour lister les changements

Nous souhaitons archiver cette nouvelle version de main.cpp. Pour ce faire, nous pouvons faire “git add main.cpp” suivi de “git commit” ou bien directement :

Archivage de la correction de main.cpp

Archivage de la correction de main.cpp

L’argument “-a” permet de dire à “git commit” qu’il doit automatiquement ajouter tous les fichiers déjà suivis qui ont été modifiés depuis le dernier commit.

Encore une fois, un appel “git log” nous donne la sortie suivante :

Affichage des logs du dépôt

Affichage des logs du dépôt

Le changement a bien été archivé. Il est possible de revenir à un état antérieur du dépôt grâce à la commande “git checkout” :

Utilisation de git checkout

Utilisation de git checkout

Et bien entendu de revenir à la dernière version en date en utilisant : “git checkout master”, où “master” est le nom de la branche à utiliser. Note : par défaut, la branche principale d’un dépôt git est nommée “master”. Il s’agit d’une convention, que vous êtes libre de ne pas suivre, mais qu’il est tout de même recommandé de respecter.

Aller plus loin

Nous n’avons vu ici qu’un petit aperçu de l’utilisation et du principe de git : il y a bien plus à voir et à découvrir. Il est également possible de (liste loin d’être exhaustive) :

  • Partager ses modifications avec un ou plusieurs autres dépôts (“push”, “pull”, “fetch”, etc.).
  • Effacer localement certains commit intermédiaires ou de réécrire totalement l’historique (lorsque cela est absolument nécessaire).
  • Utiliser git avec un dépôt SVN (“git svn”) pour par exemple faciliter la transition.
  • Rechercher quelle modification a introduit un bogue (“git bissect”)

Pour tout découvrir, je vous recommande notamment le livre Pragmatic Guide to Git qui est à la fois très facile d’accès et très complet. N’hésitez pas non plus à consulter les pages de manuel de git qui sont très bien fournies. Comme toujours, n’hésitez pas à poser vos questions si j’ai manqué de clarté sur certains aspects.

casts

Les casts en C++

Nombreux sont les programmeurs C++ qui ont d’abord été confrontés au C. Les deux langages partagent en effet bien des fonctionnalités… mais ont également de grandes différences.

Parmi ces différences, on trouve les opérateurs de conversion C++. Ils sont certainement l’un des points les plus mal compris par les développeurs C qui voient souvent en eux un verbiage inutile. L’objectif de cet article est de (dé)montrer l’utilité des opérateurs de conversion C++, en comparaison avec les conversions classiques, dites : “à la C” et de comprendre ce qu’ils peuvent apporter au programmeur en termes de maintenabilité et de sécurité.

Un petit mot sur les conversions

Les conversions (ou “cast” en anglais) sont un des outils incontournables du programmeur C++. Mais comme tout outil, il faut savoir les utiliser à bon escient.

Dans l’idéal, un programme doit contenir le moins possible de “casts” : les types doivent s’interfacer naturellement les uns avec les autres. Cela garantit un découplage du code et donc une meilleure maintenabilité. Cela ne signifie pas qu’il faille à tout prix éviter les “casts” mais simplement qu’il faut les utiliser avec parcimonie.

Dans les sections qui suivent, nous allons expliquer le rôle de chaque opérateur de conversion. Pour l’ensemble des sections, nous considérerons les classes suivantes lorsqu’il sera question de hiérarchie :

static_cast<>

Il permet plusieurs choses :

  • Expliciter les conversions implicites, supprimant du même fait tout avertissement que donnerait le compilateur si la conversion peut entraîner un risque. Exemple : double vers int.
  • Convertir vers et depuis n’importe quel type pointé à partir d’un void*. Exemple : void* vers unsigned char*.
  • Convertir au travers d’une hiérarchie de classe, sans effectuer de vérification préalable. Exemple : Base* vers Derived* ou Base& vers Derived&.
  • Ajouter l’attribut constant au type converti. Exemple : char* vers const char*.

Dans le dernier cas, notez que puisqu’il n’y a aucune vérification et que static_cast<> n’échoue jamais, le code suivant a un comportement indéfini (communément nommé en anglais “undefined behavior” ou “UB“) :

Notez que la notion de comportement indéfini n’offre par définition aucune garantie : le code peut avoir le comportement espéré, faire crasher le programme ou provoquer l’envoi d’un missile nucléaire sur Cuba.

Il ne permet pas de :

  • Convertir vers ou depuis un type pointé à partir d’un autre type pointé autre que void*. Exemple : unsigned char* vers char*.
  • Tester qu’une instance est celle d’un type dérivé. Exemple : tester qu’un Base* est en fait un Derived*.
  • Supprimer l’attribut constant du type converti. Exemple : const char* vers char*.

En bref

static_cast<> est sans doute l’opérateur de conversion que vous serez amené à utiliser le plus. Il ne permet que de réaliser des conversions sûres et à pour rôle principal celui d’expliciter les conversions implicites.

Dans le cas du polymorphisme, il est à préférer à dynamic_cast<> lorsque l’on a la garantie que la conversion va réussir.

dynamic_cast<>

Le seul rôle de dynamic_cast<> est de tester à l’exécution si un pointeur d’un type de base est en fait un pointeur vers un type dérivé.

Exemple :

Note : pour que dynamic_cast<> fonctionne, le type de base doit posséder au moins une méthode virtuelle.

Un appel à dynamic_cast<> est plus coûteux qu’un appel à static_cast<>car dynamic_cast<> effectue une recherche dans la “v-table” de l’instance à l’exécution pour déterminer son type exact.

On veillera donc à n’utiliser dynamic_cast<> que lorsqu’il n’y a aucune autre solution.

En bref

dynamic_cast<> est le seul opérateur de conversion à avoir un effet “indéterminé” jusqu’à l’exécution. Son utilisation n’a de sens que lorsque confronté à du polymorphisme. Dans les cas où la conversion est assurée de réussir, on lui préfèrera static_cast<> plus rapide et ne nécessitant pas que les classes possèdent une méthode virtuelle.

const_cast<>

const_cast<> permet de supprimer l’attribut constant ou volatile d’une référence ou d’un type pointé. Exemple : const char* vers char* ou volatile int vers int.

C’est notamment le seul opérateur de conversion à pouvoir le faire : même reinterpret_cast<> n’a pas ce pouvoir.

L’importance d’écrire un code “const-correct”

Directement relié aux opérateurs de conversion, l’écriture d’un code const-correct est un autre aspect du C++ souvent mal perçu par les programmeurs C. Le C est plus ancien et le mot clé const n’y a pas toujours existé; il a été emprunté au C++ par la suite.

Le fait d’indiquer qu’une variable est constante est un outil puissant permettant au compilateur de nous signaler certaines de nos erreurs qui auraient autrement passé la barrière de la compilation.

Qui ne s’est jamais trompé dans l’ordre des arguments d’un memcpy() ?

Les mots clé “const” ou “volatile” appliqués aux classes

En C++, les mots clé const et volatile s’appliquent évidemment aussi aux instances de classes mais ont des sémantiques différentes :

Le caractère const ou volatile s’applique récursivement aux membres de l’instance.

Il n’est possible d’appeler une méthode d’une classe que dans les cas suivants :

  • l’instance n’est pas const.
  • l’instance est const et la méthode est déclarée const.
  • l’instance est déclarée volatile et la méthode est déclarée volatile.
  • l’instance est déclarée const et volatile et la méthode est elle aussi déclarée const et volatile.

À propos de “volatile”

Certains lecteurs peuvent être perdus à la lecture du mot clé volatile qui, il faut bien l’avouer, n’est pas utilisé très souvent. Décrire précisément le rôle de volatile mériterait un article bien à part mais je vais tout de même dire en deux mots à quoi il sert :

Lorsqu’une variable est déclarée volatile, le compilateur n’a pas le droit d’optimiser sa valeur (mise en cache processeur) lors de tests.

Ainsi sans volatile sur la variable do_loop, le code suivant :

Risquerait d’être optimisé en tant que :

Ce qui est correct dans la plupart des cas… sauf si do_loop peut être modifié par un autre thread. C’est principalement dans ce genre de cas que volatile trouve son utilité.

Erreurs courantes

Une erreur courante concernant const_cast<> consiste à supposer que l’on peut toujours supprimer le caractère constant d’une variable.

Ceci est évidemment faux : on ne peut supprimer le caractère constant (respectivement volatile) d’une variable que lorsque celle-ci a été déclarée non-const (respectivement non-volatile).

Ainsi le code suivant a un comportement indéfini :

Un autre cas courant est celui des variables membres qui servent à mettre en cache un résultat :

L’utilisation de const_cast<> ici est erronée : si on déclare une instance const de MyClass, m_value_cache est aussi const lors de sa définition. L’utilisation de const_cast<> est la même que dans l’exemple précédent et a comportement indéfini.

La bonne solution est d’utiliser le mot clé mutable, qui permet à une variable membre de ne pas avoir les mêmes contraintes const/volatile que son instance parente :

En bref

const_cast<> est le seul opérateur de conversion à pouvoir supprimer le caractère const ou volatile d’une variable. L’utilisation de const_cast<> doit rester très rare : le contraire indique souvent une importante erreur de design. Son seul usage habituellement toléré est l’interfaçage avec des bibliothèques historiques qui ne sont pas const-correct.

reinterpret_cast<>

Il s’agit de l’opérateur de conversion le plus dangereux, et du plus mal utilisé. Son rôle est de dire au compilateur : “réinterprète-moi la représentation binaire de ce type en tant qu’un autre type”.

Il permet :

  • De convertir n’importe quel type pointé en une autre, même lorsque ceux-ci n’ont aucun rapport. Exemple : int* vers double*.
  • De convertir un type pointé en sa représentation intégrale et vice et versa. Exemple : int* vers int.

Il est à noter que ces conversions sont dépendantes de l’implémentation. En d’autres termes, le compilateur est libre de faire ce qu’il veut concernant la conversion basée sur reinterpret_cast<> mais ce comportement doit être constant : il ne s’agit pas de comportement indéfini; le comportement est bien défini, simplement pas par le standard C++ mais votre version du compilateur. Si vous vous basez sur cette dépendance de l’implémentation, votre code est donc non-portable.

La seule garantie délivrée par le standard C++ concernant reinterpret_cast<> est que si vous convertissez un type A en un type B, puis de nouveau en un type A, le comportement est bien défini et vous récupérez bien la valeur de départ.

On comprend dès lors facilement le danger que peut représenter reinterpret_cast<>.

Voici un exemple d’utilisation :

Cas particuliers

Le peu de garanties associées à reinterpret_cast<> rendent celui-ci quasiment inutile dans la plupart des cas. Il y a cependant certaines exceptions de fait qui justifient une utilisation de reinterpret_cast<> sans nuire à la portabilité :

Les conversions entre les types char* et unsigned char* bien que non spécifiées par le standard, sont en pratique supportées par tous les compilateurs et produisent le comportement attendu. Le compilateurs ont par ailleurs de plus fortes contraintes à leur égard (spécifiquement au niveau de leur représentation) pour des raisons de compatibilité ascendante avec le C.

Vous pouvez donc clairement supposer qu’un reinterpret_cast<> entre un char* et un unsigned char* sera à la fois portable et défini.

Polymorphisme

reinterpret_cast<> utilisé dans le cadre d’une conversion faisant intervenir du polymorphisme a un comportement non défini. Il n’est ainsi pas correct d’effectuer un reinterpret_cast<> entre par exemple un Base* et un Derived*.

En bref

reinterpret_cast<> est l’opérateur de conversion le plus dangereux : permettant de faire ce qu’aucun autre ne peut faire (des conversions entres des types non liés) il convient de l’utiliser avec la plus grande prudence. En pratique, on lui préfèrera static_cast<> qui permet d’effectuer des conversions plus sûres, y compris vers et depuis des types pointés génériques (void*). Son seul usage toléré est l’interfaçage avec du code C ancien qui utilise pour ses paramètres de “buffer” des char* ou unsigned char* au lieu des void*.

Old-school : les conversions “à la C”

Le C++ supporte toujours l’ancienne syntaxe concernant les conversions “à la façon C”. Cependant, le standard précise clairement l’effet d’une telle conversion :

Le “cast” suivant : (Type)valeur ou Type(valeur)

Sera équivalent à, par ordre de préférence :

  1. un const_cast<>
  2. un static_cast<>
  3. un static_cast<> suivi d’un const_cast<>
  4. un reinterpret_cast<>
  5. un reinterpret_cast<> suivi d’un const_cast<>

Les bonnes pratiques indiquent souvent que l’utilisation de ce type de conversion est à bannir, principalement parce qu’il peut résulter silencieusement en un reinterpret_cast<>, qui comme nous l’avons vu, peut se révéler extrêmement dangereux. De plus, l’usage des alternatives modernes aux opérateurs de conversion permet de spécifier clairement l’intention du programmeur et de protéger contre les erreurs involontaires (comme celles que nous avons vu avec const_cast<>).

Une autre utilité

Les “casts” à la C offrent également une possibilité qui n’est permise par aucun autre opérateur de conversion : celle de convertir vers une classe de base au travers d’un héritage privé. Ce type d’héritage est très souvent critiqué et fortement déconseillé. Je ne détaillerai pas ici les conséquences et les raisons de ce type d’héritage; c’est un sujet qui mérite son propre article.

Conclusion

Il y a beaucoup à dire sur les opérateurs de conversion et encore plus à apprendre. Nous avons vu que bien utilisés, ils sont un outil puissant et un allié du programmeur. Protégeant contre les erreurs involontaires et révélant les erreurs de conception, ils restent pour certains dangereux et sont tout de même à utiliser avec la plus grande précaution.

Une bonne connaissance de ces opérateurs de conversion et de leurs limites reste indispensable à la réalisation de programmes maintenables en C++.

Références

Voici une série de liens (en anglais, pour la plupart) qui m’ont inspiré dans la rédaction de cet article.

N’hésitez pas à les consulter pour obtenir d’autres informations. Je vous recommande également de vous inscrire sur Stack Overflow qui est à mon sens le meilleur site de questions/réponses concernant la programmation : le niveau des questions et surtout des réponses y est vraiment très élevé.

Comme toujours bien sûr, vous pouvez également utiliser les commentaires pour obtenir des précisions sur un point ou l’autre.

Merci pour votre lecture !

inline

La directive “inline” démystifiée

Le C++ est sans conteste l’un des langages les plus complets mais aussi les plus complexes existant dans le monde du développement en entreprise. Ses grandes flexibilité et diversité en font à la fois un langage puissant et dangereux. Il ne s’agit pas ici d’en faire une nouvelle présentation; de nombreux ouvrages lui sont déjà consacrés : qu’il s’agisse des “design patterns” ou de fonctionnalités générales, il y en a vraiment pour tous les goûts.

Cependant, j’ai décidé aujourd’hui de traiter d’un point en particulier, souvent mal perçu par les débutants et parfois même par des gens plus expérimentés : il s’agit de la directive inline.

Piqûre de rappel

Avant d’avancer sur le chemin de “l’inlining”, rappelons quelques principes élémentaires du C++.

Remarque : En tant que programmeur expérimenté, vous connaissez probablement déjà tout ce qui suit. Vous devriez tout de même prendre le temps de lire cette partie pour deux raisons : la première, ça ne fait jamais de mal. Et la deuxième : si jamais j’écrivais une bêtise, vous pourriez gentiment me le faire remarquer ! :D

Le C++ est un langage compilé (par opposition à langage interprété), ce qui signifie qu’il induit la génération d’un “binaire” lors d’une phase appelée compilation. Ce binaire peut être un fichier exécutable (.exe sous Windows), une bibliothèque (.so/.a sous Unix, .lib/.dll sous Windows) ou un fichier objet intermédiaire (.o sous Unix, .obj sous Windows).

La phase que l’on nomme “compilation” est en fait séparée en trois étapes successives :

  1. Le prétraitement (ou “preprocessing”), qui va se charger de remplacer les différentes macros présentes dans le code par leur véritable valeur. Le résultat de ce prétraitement est passé au “compilateur”.
  2. La compilation, qui transforme le code pré-traité en langage machine au sein de fichiers objets. En pratique, il y a un fichier objet généré par unité de traduction (ou “translation unit”).
  3. L’édition des liens, qui rassemble les fichiers objets générés au sein d’une seule entité (une bibliothèque dynamique ou un exécutable). Si on a déclaré et utilisé une fonction mais que son implémentation est absente, cette étape ne passe pas.

Remarque : Habituellement, dans le cas d’une bibliothèque statique, l’édition des liens n’est pas effectuée : il s’agit d’une simple concaténation des fichiers objets.

Les bonnes pratiques du C++ dictent ensuite que lorsque l’on écrit le code d’une classe, on place sa définition (et donc sa déclaration) dans un fichier dit “header“, et son implémentation dans un fichier “source“.

Il existe une règle nommée “règle de la définition unique” (ou ODR : “One Definition Rule“) qui dit que l’on peut déclarer autant de fois que l’on veut une classe, une fonction, etc. mais qu’on ne peut la définir qu’une seule fois. Nous verrons plus tard en quoi inline influe à ce niveau.

Un exemple simple

Prenons un exemple tout simple avec une classe “Person” qui représente une personne. :)

Voici le fichier header :

Dans ce header, nous avons déclaré et défini le type Person.

Son implémentation, elle, va dans le fichier source :

Si nous reprenons les trois étapes de la compilation, voici ce que se passe pour chacun des fichiers :

Le processus commence par le choix de l’unité de traduction à compiler : ici, il s’agit du fichier “person.cpp”.

  • Le préprocesseur analyse chaque ligne, procède à l’inclusion du fichier “person.hpp” (directive #include) tel qu’on le ferait avec un copier-coller. Au passage, tous les commentaires dans les fichiers sont supprimés, et les éventuelles macros sont remplacées.

On se retrouve avec un fichier qui se rapproche théoriquement de ça :

  • Le compilateur vérifie la syntaxe de l’ensemble du fichier et compile chaque implémentation de fonction (ou méthode) qu’il rencontre. Ici, les codes du constructeur Person::Person() et du “getter” name() sont effectivement transformés en langage machine au sein d’un fichier objet.
  • Enfin, si le programme fait référence à Person::Person() ou Person::name(), l’édition des liens associera l’appel de fonction à son adresse effective.

Les idées fausses sur la directive “inline”

S’en suit ici un florilège des idées reçues que j’ai déjà entendu (ou prononcé :D) ça et là sur inline :

  • “Ça sert à ordonner au compilateur de ne jamais compiler le code de la fonction.”
  • “C’est quand on écrit directement du code dans la définition d’une classe.”
  • “C’est pour accélérer les appels à une fonction.”
  • “Ça sert à déclarer des macros intelligentes.”
  • “Ça indique que la fonction a une liaison interne.”

En réalité, voici la raison d’être du mot clé inline, telle que définie par Bjarne Stroustrup :

The inline specifier is a hint to the compiler that it should attempt to generate code for a call of the inline function rather than laying down the code for the function once and then calling through the usual function call mechanism.

Pour ceux que l’anglais rebute :

La directive inline est une information donnée au compilateur lui indiquant qu’il devrait essayer de générer du code pour chaque appel de la fonction plutôt que de générer une seule fois le code de façon générique et d’utiliser le mécanisme habituel d’appel de fonction.

En gros, on apprend que la directive inline n’est pas un ordre, mais une simple indication, que le compilateur est d’ailleurs libre de refuser. Souvenez-vous que c’est son travail d’optimiser le code généré, pas le vôtre.

En pratique, on utilisera donc pas inline pour des raisons d’optimisation, mais simplement pour modifier la “One Definition Rule”. En effet, là où une fonction ne doit habituellement avoir qu’une seule définition parmi toutes les unités de traductions, le fait de la rendre inline change la règle et indique que la fonction doit avoir la même définition dans chacune des unités de traduction qui l’utilise.

Un exemple

Prenons pour exemple le célèbre cas de la fonction factorielle.

Remarque : Le choix de cet exemple n’est pas innocent. Bjarne Stroustrup utilise lui-même cet exemple lorsqu’il parle de la directive inline.

Une implémentation naïve de factorielle est la suivante :

Note : Cette fonction n’est pas optimale (on répète inutilement le test “(n <= 1)" à chaque itération. Mais elle convient très bien pour notre exemple.

Supposons que cette fonction est déclarée dans un header de notre bibliothèque et qu’un utilisateur de cette bibliothèque utilise quelque-part dans son code la fonction, par exemple :

Lors de la compilation de ce code, il peut se passer plusieurs choses :

  1. Le compilateur peut décider de compiler la fonction factorial comme n’importe qu’elle autre fonction. Elle aura en pratique un passage de paramètre, une pile d’appel etc.
  2. Ou il peut décider de remplacer factorial(6) par 6 * factorial(5) directement.
  3. Enfin, un compilateur très intelligent peut carrément décider “d’inliner” complètement l’appel et de remplacer factorial(6) par 720, optimisant de ce fait drastiquement le programme.

On notera que l’appel d’une fonction inline est sémantiquement identique à celle d’une fonction “classique”. Il est possible d’en hériter, de la surcharger, etc.

Utilisation au quotidien

Voici quelques usages corrects de fonctions “inline” :

  • Dans le premier cas, la méthode value() est directement définie au sein de la définition de la classe. Elle est implicitement déclarée inline. L’ajout du mot clé inline serait redondant et donc inutile.
  • Dans le second cas, la méthode add() est simplement déclarée (sans mot clé particulier) au sein de la classe. Sa définition est écrite dans le fichier header, en dehors de celle de la classe, mais toujours dans le même namespace, tel qu’on le ferait si on implémentait cette fonction dans le fichier source. Dans ce cas, la définition de la fonction étant écrite au sein même du fichier header (et donc potentiellement présente dans plusieurs unités de traduction), on doit cependant ajouter le mot clé inline pour s’affranchir de la “One Definition Rule“.
  • Enfin, dans le dernier cas, la méthode sub n’est pas une vraie méthode mais un template. L’ajout de la directive inline n’est pas obligatoire, car encore une fois, la définition de la méthode se situe dans la définition de la classe. Elle est donc implicitement inline.

N’utilisez inline que sur de très petites fonctions (notion subjective mais en gros : si votre fonction fait plus qu’une simple opération arithmétique ou un retour de valeur, elle n’a surement pas d’intérêt à être inline) et si possible, uniquement sur celles qui ont vocation à être appelées souvent. Les meilleurs candidats pour inline sont généralement bien sûr les getters, les setters, ou encore les destructeurs virtuels vides.

Conclusion

La première fois que l’on m’a parlé du mot clef inline, on me l’a présenté comme un moyen d’optimiser les appels de fonction. Pendant très longtemps, j’ai d’ailleurs soutenu cette version aveuglement. Mais nous avons vu aujourd’hui que les compilateurs sont suffisamment capables pour déterminer d’eux-même quand, quoi et comment optimiser.

En pratique, on retiendra que de bonnes connaissances concernant la “One Definition Rule” et la directive inline sont indispensables à l’écriture d’un code réutilisable et maintenable.

J’espère que cet article vous aura appris quelque-chose (ou à défaut intéressé). N’hésitez pas à me signaler dans les commentaires les éventuelles erreurs que j’aurais pu commettre.

Bon code !