Pragma: une once de problèmes

Article original : Pragma: once or twice? | Belay the C++ (belaycpp.com)
Traductrice : Chloé Lourseyre

Contexte

Les header guards

Les header guards (littéralement : protections de header) sont une méthode très répandue pour protéger les fichiers header des inclusions multiples, et ainsi de ne pas avoir plusieurs fois la même définition de variable, fonction ou classe.

Normalement, tous les développeurs C++ se sont vus enseigner cette méthode.

En voici un exemple :

#ifndef HEADER_FOOBAR
#define HEADER_FOOBAR

class FooBar
{
    // ...
};

#endif // HEADER_FOOBAR

Pour ceux qui ne sont pas familiers avec son fonctionnement, voici ce qui se passe : la première fois que le fichier est inclus, la macro HEADER_FOOBAR n est pas définie. Nous entrons donc dans la directive de contrôle #ifndef. Dedans, on définit la macro HEADER_FOOBAR et la classe FooBar. Plus tard, si on inclut de nouveau ce fichier, puisque la macro HEADER_FOOBAR est désormais définie, on ne rentre pas dans le #ifndef, du coup la classe FooBar n’est pas redéfinie.

#pragma once

#pragma est une directive de précompilation qui prodigue des informations additionnelles au compilateur, au-delà de ce que fournit déjà le langage lui-même.

Tout compilateur est libre d’interpréter les directives #pragma comme il l’entend. Cependant, au fil des années, certaines directives ont acquis plus de popularité que les autres et sont maintenant presque des standards (comme #pragma once, qui est le sujet de cet article, ou encore #pragma pack).

#pragma once est une directive qui indique au compilateur d’inclure le fichier où elle est présente une seule fois. C’est au compilateur de gérer comment il fait ça.

Du coup, instinctivement, on pourrait se dire que #pragma once fait le même travail qu’un header guard, mais en mieux puisqu’il fait ça en une ligne au lieu de trois, et sans avoir à réfléchir à un nom de macro.

Aujourd’hui ?

Autrefois, #pragma once n’était pas implémentée sur tous les compilateurs, elle était donc moins portable que les header guards.

Mais aujourd’hui, en C++, il n’y a (à ma connaissance) aucun compilateur qui ne l’implémente pas.

Donc pourquoi continuer à utiliser les header guards ? Réponse : à cause du problème que je vais détailler dans la section suivante.

Un problème bizarre avec #pragma once

La problématique que je m’apprête à décrire ne peut pas arriver avec des header guards, ce qui rend #pragma once particulièrement problématique.

Disons, par exemple, que votre fichier header est dupliqué pour une raison qui vous échappe. Cela peut être parce que:

  • Vous avez raté un merge, et votre gestionnaire de version a gardé une version de chaque fichier.
  • Votre gestionnaire de version a mal géré le déplacement d’un fichier.
  • Votre système de fichier possède plusieurs points de montage sur le même disque, ce qui fait que chaque fichier peut-être accéder par deux chemins différents, ce que le compilateur comprend comme étant deux fichiers différents.
  • Quelqu’un a copié-collé un des fichiers du projet pour son usage personnel à un autre endroit du projet, sans renommer quoique ce soit (c’est très malpoli, mais ça peut arriver).

(notez que j’ai déjà rencontré chacun de ces problèmes sur des projets réels.)

Quand ce genre de cas de figure survient, les #pragma once et les header guards ne se comportent pas de manière identique:

  • Comme les macros qui protègent les headers dupliqués ont le même nom, les header guards fonctionnent parfaitement bien et un seul des fichiers dupliqués est inclus.
  • Comme le FS indique qu’il s’agit de fichiers différents, le #pragma once protège chaque duplicata indépendamment, ce qui mène fatalement à une collision de nom.

Des problèmes avec les header guards ?

Les header guards on des problèmes qui leur sont spécifiques également. Par exemple, s’il y a une typographie dans la macro de protection, alors le header guard ne fonctionnera pas. De plus, si les conventions de nom sont mal formulées, certains header guards peuvent avoir le même nom (alors qu’ils protègent des fichiers différents).

Cependant, éviter ces deux problèmes est trivial (les typos sont faciles à voir et si vous avez une convention de nommage correcte, tout ira bien).

Avec un gros avantage !

Il existe aussi un avantage non négligeable aux header guards dans le cadre d une stratégie de test.

Disons que vous voulez tester la classe Foo (dans Foo.h) qui utilise la classe Bar (dans Bar.h). Mais, pour des raisons de test, vous voulez bouchonner Bar.

Une possibilité que vous permet les header guards est de créer votre propre bouchon de Bar (dans le fichier BarMock.h). Si le bouchon utilise les mêmes header guards que l’original, alors vous pouvez inclure BarMock.h puis Foo.h sans que le header Bar.h ne soit inclus (puisque les protections ont déjà été levée dans BarMock.h).

Du coup, dois-je utiliser #pragma once ou des header guards?

Cette question dont il est un peu complexe de répondre. Voici les possibilités qui s’offrent à vous :

  • #pragma once est non standard et cause des problèmes majeurs quand vous tombez dans un environnement dégradé.
  • Les header guards peuvent causer des problèmes si elles ne sont pas utilisées correctement.

D’après moi, les directives #pragma sont à éviter dès que possible. Si, en pratique, elles fonctionnent, elles ne sont pas formellement standards.

Cher C++20, quid des Modules ?

Les Modules, une des « big four » fonctionnalités du C++20, change notre approche du processus de build des projets. À la place d’avoir des fichiers source et header, on peut maintenant avoir des fichiers module. Ils dépassent complètement les restrictions des headers, augmentent la vitesse de compilation, réduisent les violations de la règle de One-Time-Definition et, surtout, permettent de se dispenser de directives de préprocesseur.

Grâce aux modules, on peut dire que les dilemmes de #pragma once et des header guards sont de l’histoire ancienne.

Pour en apprendre plus sur les module, allez-voir les articles suivants :

En conclusion

Cet article, parlant surtout des directives #pragma et des header guards concerne les projets qui sont sur une version antérieure au C++20. Si vous hésitez encore entre les #pragma once et les header guards, peut-être devriez-vous passer au C++20 ?

Si vous ne pouvez pas migrer aussi facilement que ça (ce qui est le cas de la plupart des projets industriels), alors choisissez méticuleusement entre #pragma once et les header guards,

Merci de votre attention et à la prochaine!

Article original : Pragma: once or twice? | Belay the C++ (belaycpp.com)
Traductrice : Chloé Lourseyre

Addendum

Sources

Laisser un commentaire