Dans quel ordre inclure des fichiers d'en-tête?

Pour plus de détails sur ce processus, voir cet article (un peu vieux, mais bon, c'est mieux que rien).

En C++, les fichiers sources (portant typiquement l'extension .cpp) sont compilés séparément, puis les fruits de cette étape de compilation sont liés les uns aux autres lors d'une phase ultérieure d'édition des liens. Pour inclure des types, des prototypes de fonctions et d'autres noms et signatures, un fichier source inclura typiquement un certain nombre de fichiers d'en-tête (sans extensions pour ceux de la bibliothèque standard, et habituellement avec extension .h ou .hpp pour les autres).

Un chic et éveillé étudiant de la cohorte 06 du Diplôme de développement de jeu vidéo, à l'Université de Sherbrooke, nommé François Ostiguy, m'a posé cette question (légèrement formatée pour les fins de l'article) :

« Vous nous avez parlé en début de session, d'inclure nos en-têtes (donc les fichiers d'en-tête de notre cru) d'abord, puis les en-têtes standards. Je me demandais toutefois pourquoi, lorsque je consulte des fichiers sources ailleurs comme sur le Web ou dans la bibliothèque PhysX, ces fichiers incluent d'abord les en-têtes standards, ensuite les autres... »

J'ai pensé que la réponse à sa question pourrait être d'intérêt public, alors la voici. Selon moi, les pratiques des uns et des autres sont surtout une question d'habitude, mais il demeure sage d'inclure d'abord vos en-têtes et ensuite les en-têtes standards.

Je prends cette suggestion du livre Large Scale C++ Software Design de John Lakos. La motivation derrière sa recommandation est que cette pratique mène à des programmes plus robustes. Voici pourquoi, à l'aide d'un exemple (connexe aux intérêts des gens dans le monde du jeu vidéo, vous le comprendrez).

Imaginez que vous ayez déclaré la classe Monstre suivante :


#ifndef MONSTRE_H
#define MONSTRE_H
class NomPasCool {};
class PasAssezDegueu 
{
   float seuil_, valeur_;
public:
   PasAssezDegueu(float seuil, float valeur) noexcept 
     : seuil_{seuil}, valeur_{valeur}
   {
   }
   float seuil() const noexcept
      { return seuil_; }
   float valeur() const noexcept
      { return valeur_; }
};
class Monstre
{
   string nom_;
   float degueulassitude_;
   static const string & valider_nom(const string &candidat)
   {
      if (candidat == "Patrice") throw NomPasCool{};
      return candidat; 
   }
   static const float SEUIL;
   static float valider_degueu(float candidat)
   {
      if (candidat < SEUIL) throw PasAssezDegueu{ SEUIL, candidat };
      return candidat; 
   }
public:
   Monstre(const string &nom, float degueu)
      : nom_{valider_nom(nom)}, degueulassitude_{valider_degueu(degueu)}
   {
   }
   string nom() const
      { return nom_; }
   float degueulassitude() const noexcept
      { return degueulassitude_; }
};
#endif

Ce fichier d'en-tête cache un bogue pas gentil : il utilise string (supposons std::string) sans avoir inclus le fichier d'en-tête correspondant (nommé <string>); il est donc incomplet et dépendant du code client pour combler ce manque.

Ainsi, si un code client comme Monstre.cpp fait :

#include <string>
using namespace std;
#include "Monstre.h" // compile par accident, du fait que Monstre.cpp a inclus
                     // string... alors un aura un bogue, mais il risque de
                     // nous échapper!
const float Monstre::SEUIL = 100.0f;

...cela pourrait cacher aux développeurs qu'ils ont un bogue, et ce bogue pourrait « péter au visage » de leurs pairs. En effet, puisqu'on compile les fichiers sources individuellement, ce Monstre.cpp compilerait dù à l'ordre des inclusions, masquant un problème ailleurs.

En retour, si on écrit Monstre.cpp comme ceci :

#include "Monstre.h" // ne compile pas car il utilise le type string, qui n'est
                     // pas encore déclaré. C'est très bien comme ça: on veut
                     // que Monstre.h soit cohérent en soi et indépendant de
                     // l'ordre des #include dans les .cpp qui l'utilisent
#include <string>
using namespace std;
const float Monstre::SEUIL = 100.0f;

... alors le bogue apparaîtra tout de suite et on pourra plus facilement le régler au moment opportun et, surtout, au bon endroit.

On suppose généralement que les en-têtes standards sont bien écrits (certains en-têtes standards de plateformes spécifiques, comme l'ignoble <windows.h>, sont des cas patents d'exceptions confirmant la règle, mais de manière générale les en-têtes standards de C++ sont bien faits et respectueux des bons usages).

On peut par ailleurs supposer que les en-têtes standards ne dépendent pas de nos propres en-têtes; évidemment, il est probable que nos en-têtes, eux, dépendent au moins un peu des en-têtes standards (ce qui est naturel). Placer nos propres en-têtes d'abord réduit les risques d'un oubli ou d'une erreur dans notre code, sans les éliminer évidemment (l'un de nos propres en-têtes peut, bien entendu, dépendre d'un autre de nos en-têtes). C'est une saine pratique, pas une panacée.


Valid XHTML 1.0 Transitional

CSS Valide !