Quelques rappels de cours sur les bonnes pratiques liées à la modularisation des applications C/C++. Je présente en plus quelques astuces pour structurer une application C/C++ et son packaging.
8. Plan de la formation
▧ Objectifs
▧ Structure d’une application C/C++
▧ Exemples d’applications OpenSource
▧ Gestion des headers
▧ Gestion des includes
▧ Outillage
9. Objectifs
Objectifs primaires :
▧ Structurer un projet C/C++
▧ Connaitre la différence entre include système et include
▧ Connaître l’interaction entre le chemin des includes et les instructions
CMake
▧ Savoir écrire un header C/C++ correct
Objectifs secondaires :
▧ Savoir exporter des définitions
▧ Optimiser les dépendances entre les fichiers
▧ Savoir pré-compiler des headers
▧ Définir les bonnes pratiques de modularisation pour une application C/C++
11. Modularisation
La programmation modulaire c’est l’art de concevoir
une application logicielle qui met en oeuvre
la séparation des fonctionnalités d’un programme
en des modules indépendants, interchangeables
et pour chaque module
que celui ne contienne uniquement ce qui est
nécessaire à son exécution et sa fonctionnalité.
14. La recette
1.Prendre les sources de l’application
2.Récupérer les dépendances : Où ???
3.Décomposer en module : Comment ??
4.Construire les binaires
5.Et après ? Intégration continue
18. Bric à brac du codeur
▧ Scripts de compilation
▧ Ressources (images, fichiers de configuration)
▧ Documentation
Readme
ChangeLog
▧ Sources
▧ Headers
▧ Bibliothèques
▧ ...
19. Autopsie d’une application C/C++
Au niveau du source, un programme C/C++ est
une collection de :
● Fichiers en-têtes (headers) (fichier .h ou
.hpp)
● Fichiers sources (fichier .c ou .cpp)
20. Contenu des fichiers en-têtes
Contenu partagé entre plusieurs fichiers
sources :
▧ types de données comme les structs ou les
classes
▧ les protypes de fonction ( déclarations)
▧ les constantes (les déclarations)
▧ les templates
▧ les types énumérés
21. La notion de dépendances entre fichiers
Tout fichier peut utiliser la directive #include
afin insérer le contenu d’un autre fichier
Ex : Un fichier source peut inclure plusieurs
fichiers en-tête
Ex : Un fichier en-tête peut inclure plusieurs
fichiers en-tête
25. Bien gérer ses dépendances
Accéler le temps de compilation
Limiter le périmètre de régression /
modification
Faciliter les futures évolutions de l’application
Améliorer la compréhension de l’architecture
global de l’application
39. “Une archive pour la bibliothèque ?
les exemples sont dedans ou à coté ?
40. Une archive qui contient la bibliothèque et ses exemples
-> Utilisation d’un CMake Global
-> Les exemples réutilisent la bibliothèque via CMake
-> La Compilation globale est réalisée via CMake
Situation A
41. Une archive pour la bibliothèque et une archive par
exemple
-> Utilisation d’un CMake pour la bibliothèque
-> Utilisation d’un CMake par exemple
-> Les exemples réutilisent la bibliothèque via le
gestionnaire de dépendances
-> La Compilation globale est réalisée via Gradle
Situation B
47. ▧ Utilisation de CMake
Pour la compilation multi-plateforme
Pour l’utilisation dans Eclipse
▧ Ajout de Gradle
Pour gérer les dépendances inter-projets
Pour construire le packaging et l’uploader
Solution Pragmatique
52. Utilisez :
● la notion de buildType CMake
● un autre sous-projet (et livrable)
Si vous avez besoin de recompiler un
fichier ou une bibliothèque avec des
flags différents
53. Vérifiez
● Que les projets ne soient pas indépendants
(release) et donc deux livrables différents
Utilisez :
L’option OBJECT de CMake
(http://www.cmake.org/pipermail/cmake-
developers/2012-March/003693.html)
Si vous devez réutilisez des .obj entre
deux sous-projets
54. Outillage pour gérer les dépendances
dump -H
<program>
AIX
hatr <program>
HP-UX
ldd <program> Linux, SunOS5X
$LIB_PATH AIX
SHLIB_PATH
HP-UX
$LD_LIBRARY_P
ATH
Linux, SunOS5X
55. Outillage pour analyser les dépendancs
Visual Studio
PC Lint
Visual Lint
Eclipse Includator
CLang Include what you use
Include File Dependency
JEtBrains IDE CLion
58. Les Headers : mais pourquoi ?
http://webstory.my/
▧ Accélérer le temps de
compilation
▧ Garder son code mieux
organisé
▧ Séparer proprement les
interfaces de
l’implémentation
▧ Prérequis pour la
développement de
bibliothèques
59. ▧ Compilation en deux temps :
Chaque fichier est compilé avec l’ensemble de ses headers
L’ensemble des fichiers est assemblé (link) pour construire
le binaire final.
▧ Les fichiers sources peuvent inclure des fichiers headers
▧ Les fichiers headers peuvent inclure d’autres fichiers headers
Comment cela fonctionne ?
62. // maclasse.h
class MaClasse
{
void faitQuelquechose() {
}
};
Attention aux multiples inclusions
// main.cpp
#include "maclasse.h" // définir MaClasse
#include "maclasse.h" // Erreur de compilation -
MaClasse already defined
63. #ifndef __MACLASSE_H_INCLUDED__ // si maclasse.h n’a pas encore été inclus
#define __MACLASSE_H_INCLUDED__ // #define pour que le préprocesseur sache
que cela a été inclus
// maclasse.h
class MaClasse
{
void faireQuelquechose() { }
};
#endif
Attention aux multiples inclusions (solution)
64. ▧ #include “filename”
Pour les headers maintenus par l’équipe / produit
▧ #include <filename>
Pour les headers fournis par les bibliothèques standard (pré-
définis)
Bibliothèques standard : (STL) + Packages installés sur la machine
▧ Sous GCC, la variable INCLUDE_PATH est exploitée pour retrouver
les fichiers headers (argument -I)
Includes systèmes / includes
65. Les fichiers headers doivent être auto-suffisants
//=================================
// include guard
#ifndef __MYCLASS_H_INCLUDED__
#define __MYCLASS_H_INCLUDED__
//=================================
// forward declared dependencies
class Foo;
class Bar;
//=================================
// included dependencies
#include <vector>
#include "parent.h"
//=================================
// the actual class
class MyClass : public Parent // Parent object, so #include
"parent.h"
{
public:
std::vector<int> avector; // vector object, so #include
<vector>
Foo* foo; // Foo pointer, so forward
declare Foo
void Func(Bar& bar); // Bar reference, so forward
declare Bar
};
#endif // __MYCLASS_H_INCLUDED__
66. N’incluez pas les dépendances transitives
// C.h
#include “b.h”
#include “a.h”
// C.cpp
#include “c.h”
#include “a.h” // USELESS
67. Positionnez l’include du source en premier
// C.cpp
#include “c.hpp”
#include <iostream>
...
Les fichiers
headers doivent
être
autosuffisants.
68. ▧ Incluez uniquement le header du module, jamais ses
dépendances.
▧ Les fichiers headers ne doivent pas avoir d’instruction using::
▧ Le fichier header ne doit contenir que :
Structure et déclarations de classes
Prototypes de fonction
Déclaration de variables globales externes
▧ L’initialisation des variables doit être dans les fichiers
sources
▧ Enlevez les déclarations internes (invisibles de l’utilisateur) et
déplacez les dans les .cpp
Best practices Includes/Headers
69. ▧ Incluez uniquement les fichiers nécessaires dans chaque
header
▧ Chaque header doit avoir des gardes contre l’inclusion
▧ Appliquez la règle : une structure, un fichier .h(pp) et un
fichier .c(pp)
class MyClass => MyClass.hpp, MyClass.cpp
▧ Ne confondez pas #includes systèmes et #includes.
▧ Utilisez les déclarations en avant (forward)
▧ Attention aux fonctions inline
▧ Bien gérer les déclarations externes et exportées
Best practices Includes/Headers
70. ▧ Pas d’inclusion de fichiers .cpp
▧ Déclarez les includes systèmes aussi dans les .cpp
Best practices Includes/Headers
71. // a.h
#ifndef A_H
#define A_H
#include "b.h"
class A { B* b; };
#endif A_H
Dépendances circulaires
// b.h
#ifndef B_H
#define B_H
#include "a.h"
class B { A* a; };
#endif B_H
72. #include "a.h"
// start compiling a.h
#include "b.h"
// start compiling b.h
#include "a.h"
// compilation of a.h skipped because it's guarded
// resume compiling b.h
class B { A* a }; // <--- ERROR, A is undeclared
Dépendances circulaires
73. Pour les longues nuits d’hiver
▧ Modular programming : http://www.informit.com/articles/article.aspx?p=25003&seqNum=5
▧ Detect Unnecessary includes (discussion) : http://stackoverflow.com/questions/74326/how-should-i-
detect-unnecessary-include-files-in-a-large-c-project
▧ Clang Include what you use : https://code.google.com/p/include-what-you-use/
▧ Include File Dependency : http://www.mobile-mir.com/cpp/
▧ JetBrains CLion :
▧ Include et headers : http://www.cplusplus.com/forum/articles/10627/
▧ http://faculty.ycp.edu/~dhovemey/spring2011/cs320/lecture/lecture27.html
74. Vous pouvez me trouver :
https://about.me/sylvain_leroy
Merci
Notas del editor
Améliorer de manière significative le temps de construction des applications
Meilleure séparation entre les interfaces et les implémentations
Trouver les bugs plus rapidement
Maîtriser/Limiter les régressions lors d’évolution sur des composants critiques
Réutiliser les composants /bibliothèque les plus utiles
Responsabiliser les fournisseurs d’API
Améliorer de manière significative le temps de construction des applications
Meilleure séparation entre les interfaces et les implémentations
Trouver les bugs plus rapidement
Maîtriser/Limiter les régressions lors d’évolution sur des composants critiques
Réutiliser les composants /bibliothèque les plus utiles
Responsabiliser les fournisseurs d’API
Améliorer de manière significative le temps de construction des applications
Meilleure séparation entre les interfaces et les implémentations
Trouver les bugs plus rapidement
Maîtriser/Limiter les régressions lors d’évolution sur des composants critiques
Réutiliser les composants /bibliothèque les plus utiles
Responsabiliser les fournisseurs d’API