#pragma une fois vs inclure des gardes?

Je travaille sur une base de code connue pour ne fonctionner que sur Windows et être compilée sous Visual Studio (elle s’intègre parfaitement avec Excel, donc elle ne va nulle part). Je me demande si je devrais aller avec les gardes traditionnels d’inclusion ou utiliser #pragma once pour notre code. Je pense que laisser le compilateur traiter avec #pragma once donnera des compilations plus rapides et sera moins sujet aux erreurs lors de la copie et du collage. C’est aussi légèrement moins moche ;)

Remarque: pour obtenir des temps de compilation plus rapides, nous pourrions utiliser Redundant Include Guards, mais cela ajoute un couplage étroit entre le fichier inclus et le fichier inclus. Habituellement, ça va parce que le garde doit être basé sur le nom du fichier et ne changera que si vous avez besoin de changer le nom de l’inclusion de toute façon.

Je ne pense pas que cela fera une différence significative dans le temps de compilation, mais #pragma once est très bien supporté par les compilateurs mais ne fait pas vraiment partie du standard. Le préprocesseur peut être un peu plus rapide car il est plus simple de comprendre votre intention exacte.

#pragma once est moins enclin à faire des erreurs et c’est moins de code à taper.

Pour accélérer le temps de compilation, il vous suffit d’avancer votre déclaration plutôt que de l’inclure dans les fichiers .h quand vous le pouvez.

Je préfère utiliser #pragma once .

Voir cet article de Wikipedia sur la possibilité d’utiliser les deux .

Je voulais juste append à cette discussion que je ne fais que comstackr sur VS et GCC, et que je l’utilise pour inclure des gardes. Je suis maintenant passé à #pragma once , et la seule raison pour moi n’est pas la performance ou la portabilité ou la norme car je ne me soucie pas vraiment de ce qui est standard tant que VS et GCC le supportent.

#pragma once réduit les possibilités de bogues.

Il est trop facile de copier et coller un fichier d’en-tête dans un autre fichier d’en-tête, de le modifier en fonction de vos besoins et d’oublier de changer le nom de la garde d’inclusion. Une fois les deux inclus, il vous faut un certain temps pour retrouver l’erreur, car les messages d’erreur ne sont pas nécessairement clairs.

#pragma once bogues non résolus . Il ne devrait jamais être utilisé.

Si votre chemin de recherche #include est suffisamment compliqué, le compilateur peut ne pas être en mesure de faire la différence entre deux en-têtes ayant le même nom de base (par exemple, a/foo.h et b/foo.h ). va supprimer les deux . Il peut également être impossible de dire que deux #include "foo.h" relatifs différents (par exemple, #include "foo.h" et #include "../a/foo.h" font référence au même fichier, donc #pragma once , #pragma once ne pourra pas supprimer un fichier redondant). inclure quand il devrait avoir.

Cela affecte également la capacité du compilateur à éviter de relire les fichiers avec les gardes #ifndef , mais il ne s’agit que d’une optimisation. Avec #ifndef gardes, le compilateur peut lire en toute sécurité n’importe quel fichier dont il n’est pas sûr qu’il a déjà vu; si c’est faux, il suffit de faire du travail supplémentaire. Tant que deux en-têtes ne définissent pas la même macro de garde, le code sera compilé comme prévu. Et si deux en têtes définissent la même macro de garde, le programmeur peut y entrer et en changer un.

#pragma once n’a pas un tel filet de sécurité – si le compilateur est faux sur l’identité d’un fichier d’en-tête, de toute façon , le programme ne parviendra pas à comstackr. Si vous rencontrez ce bogue, vos seules options sont d’arrêter d’utiliser #pragma once ou de renommer l’un des en-têtes. Les noms des en-têtes font partie de votre contrat API, donc le renommage n’est probablement pas une option.

(La version courte de la raison pour laquelle cela est impossible à corriger est que ni l’API Unix ni le système de fichiers Windows ne proposent un mécanisme garantissant que deux noms de chemins absolus se réfèrent au même fichier. Si vous ça, désolé, tu as tort.)

(Note historique: la seule raison pour laquelle je n’ai pas extrait #pragma once et #import de GCC lorsque j’avais l’autorisation de le faire, il ya environ 12 ans, c’était les en-têtes système d’Apple qui les utilisaient. m’a arrêté.)

(Comme cela a maintenant été mentionné deux fois dans le fil de commentaires: les développeurs de GCC ont déployé beaucoup d’efforts pour rendre #pragma once aussi fiable que possible; voir le rapport de bogue 11569 de GCC. échouer dans des conditions plausibles, telles que construire des fermes avec un décalage d’horloge. Je ne sais pas quelle implémentation d’autres compilateurs est comme, mais je ne m’attendrais pas à ce que quelqu’un ait fait mieux .)

Jusqu’au jour où #pragma once deviendra #pragma once norme (ce n’est pas actuellement une priorité pour les futures normes), je vous suggère de l’utiliser ET d’utiliser des gardes, de cette façon:

 #ifndef BLAH_H #define BLAH_H #pragma once // ... #endif 

Les raisons sont les suivantes:

  • #pragma once n’est pas standard, il est donc possible que certains compilateurs ne fournissent pas la fonctionnalité. Cela dit, tous les principaux compilateurs le supportent. Si un compilateur ne le sait pas, il sera au moins ignoré.
  • Comme il n’y a pas de comportement standard pour #pragma once , vous ne devez pas supposer que le comportement sera le même sur tous les compilateurs. Les gardes veilleront au moins à ce que l’hypothèse de base soit la même pour tous les compilateurs qui implémentent au moins les instructions de préprocesseur nécessaires pour les gardes.
  • Sur la plupart des compilateurs, #pragma once accélérera la compilation (d’une cpp) car le compilateur ne rouvrira pas le fichier contenant cette instruction. Donc, l’avoir dans un fichier peut aider ou non, selon le compilateur. J’ai entendu que g ++ peut faire la même optimisation lorsque des gardes sont détectés mais il doit être confirmé.

En utilisant les deux ensemble, vous obtenez le meilleur de chaque compilateur pour cela.

Maintenant, si vous n’avez pas de script automatique pour générer les gardes, il serait peut-être plus simple d’utiliser #pragma once . Juste savoir ce que cela signifie pour le code portable. (J’utilise VAssistX pour générer les gardes et le pragma une fois rapidement)

Vous devriez presque toujours penser votre code de manière portable (parce que vous ne savez pas de quoi le futur est fait) mais si vous pensez vraiment qu’il ne doit pas être compilé avec un autre compilateur (code pour du matériel embarqué très spécifique par exemple) alors vous devriez juste vérifier votre documentation du compilateur concernant #pragma once pour savoir ce que vous faites vraiment.

Du sharepoint vue d’un testeur de logiciels

#pragma once est plus court qu’un garde d’inclusion, moins sujet aux erreurs, supporté par la plupart des compilateurs, et certains disent qu’il se comstack plus rapidement (ce qui n’est plus vrai).

Mais je vous suggère toujours d’aller avec #ifndef standard inclut des gardes.

Pourquoi #ifndef ?

Considérons une hiérarchie de classes comme celle-ci où chacune des classes A , B et C réside dans son propre fichier:

ah

 #ifndef A_H #define A_H class A { public: // some virtual functions }; #endif 

bh

 #ifndef B_H #define B_H #include "ah" class B : public A { public: // some functions }; #endif 

ch

 #ifndef C_H #define C_H #include "bh" class C : public B { public: // some functions }; #endif 

Supposons maintenant que vous écrivez des tests pour vos classes et que vous devez simuler le comportement de la classe B vraiment complexe. Une façon de le faire serait d’écrire un simulacre de classe en utilisant par exemple google mock et le mettre dans un répertoire mocks/bh . Notez que le nom de la classe n’a pas changé, mais seulement dans un répertoire différent. Mais le plus important est que le garde d’inclusion soit nommé exactement comme dans le fichier d’origine bh .

mocks / bh

 #ifndef B_H #define B_H #include "ah" #include "gmock/gmock.h" class B : public A { public: // some mocks functions MOCK_METHOD0(SomeMethod, void()); }; #endif 

Quel est l’avantage?

Avec cette approche, vous pouvez vous moquer du comportement de la classe B sans toucher à la classe d’origine ou en parler à C Tout ce que vous avez à faire est de placer le répertoire mocks/ dans le chemin d’inclusion de votre complicateur.

Pourquoi cela ne peut-il pas être fait avec #pragma once ?

Si vous aviez utilisé #pragma once , vous auriez un conflit de noms car il ne peut pas vous empêcher de définir la classe B deux fois, une fois la version originale et la version simulée.

Si vous êtes certain de ne jamais utiliser ce code dans un compilateur qui ne le supporte pas (Windows / VS, GCC et Clang sont des exemples de compilateurs qui le supportent), vous pouvez certainement utiliser #pragma une fois sans soucis .

Vous pouvez également utiliser les deux (voir exemple ci-dessous), de sorte que vous obtenez la portabilité et l’accélération de la compilation sur les systèmes compatibles

 #pragma once #ifndef _HEADER_H_ #define _HEADER_H_ ... #endif 

Je ne #pragma once généralement pas #pragma once avec #pragma once car mon code doit parfois être compilé avec autre chose que MSVC ou GCC (les compilateurs pour les systèmes embarqués n’ont pas toujours le #pragma).

Donc, je dois utiliser des gardes #include de toute façon. Je pourrais aussi utiliser #pragma once comme le suggèrent certaines réponses, mais il ne semble pas y avoir beaucoup de raisons et cela entraînera souvent des avertissements inutiles sur les compilateurs qui ne le supportent pas.

Je ne suis pas sûr des économies de temps que le pragma pourrait apporter. J’ai entendu dire que les compilateurs reconnaissent généralement quand un en-tête n’a rien d’autre que des commentaires en dehors des macros de garde et font le #pragma once équivalent dans ce cas (ie, ne plus jamais traiter le fichier). Mais je ne suis pas sûr que ce soit vrai ou qu’un simple cas de compilateurs puisse effectuer cette optimisation.

Dans les deux cas, il est plus facile pour moi d’utiliser des gardes #include qui fonctionneront partout sans se soucier de cela.

Après avoir engagé une longue discussion sur le compromis supposé entre les performances de #pragma once et #ifndef gardes contre l’argument de la correction ou non (je prenais le côté de #pragma once basé sur un endocsortingnement relativement récent), j’ai décidé pour enfin tester la théorie selon laquelle #pragma once est #pragma once plus rapide car le compilateur n’a pas besoin d’essayer d’ #include un fichier déjà inclus.

Pour le test, j’ai généré automatiquement 500 fichiers d’en-tête avec des interdépendances complexes et un fichier .c qui les #include tous. J’ai couru le test de trois manières, une fois avec juste #ifndef , une fois avec juste #pragma once , et une fois avec les deux. J’ai effectué le test sur un système assez moderne (un MacBook Pro 2014 exécutant OSX, utilisant le Clang fourni avec XCode, avec le SSD interne).

Tout d’abord, le code de test:

 #include  //#define IFNDEF_GUARD //#define PRAGMA_ONCE int main(void) { int i, j; FILE* fp; for (i = 0; i < 500; i++) { char fname[100]; snprintf(fname, 100, "include%dh", i); fp = fopen(fname, "w"); #ifdef IFNDEF_GUARD fprintf(fp, "#ifndef _INCLUDE%d_H\n#define _INCLUDE%d_H\n", i, i); #endif #ifdef PRAGMA_ONCE fprintf(fp, "#pragma once\n"); #endif for (j = 0; j < i; j++) { fprintf(fp, "#include \"include%dh\"\n", j); } fprintf(fp, "int foo%d(void) { return %d; }\n", i, i); #ifdef IFNDEF_GUARD fprintf(fp, "#endif\n"); #endif fclose(fp); } fp = fopen("main.c", "w"); for (int i = 0; i < 100; i++) { fprintf(fp, "#include \"include%dh\"\n", i); } fprintf(fp, "int main(void){int n;"); for (int i = 0; i < 100; i++) { fprintf(fp, "n += foo%d();\n", i); } fprintf(fp, "return n;}"); fclose(fp); return 0; } 

Et maintenant, mes différents tests s'exécutent:

 folio[~/Desktop/pragma] fluffy$ gcc pragma.c -DIFNDEF_GUARD folio[~/Desktop/pragma] fluffy$ ./a.out folio[~/Desktop/pragma] fluffy$ time gcc -E main.c > /dev/null real 0m0.164s user 0m0.105s sys 0m0.041s folio[~/Desktop/pragma] fluffy$ time gcc -E main.c > /dev/null real 0m0.140s user 0m0.097s sys 0m0.018s folio[~/Desktop/pragma] fluffy$ time gcc -E main.c > /dev/null real 0m0.193s user 0m0.143s sys 0m0.024s folio[~/Desktop/pragma] fluffy$ gcc pragma.c -DPRAGMA_ONCE folio[~/Desktop/pragma] fluffy$ ./a.out folio[~/Desktop/pragma] fluffy$ time gcc -E main.c > /dev/null real 0m0.153s user 0m0.101s sys 0m0.031s folio[~/Desktop/pragma] fluffy$ time gcc -E main.c > /dev/null real 0m0.170s user 0m0.109s sys 0m0.033s folio[~/Desktop/pragma] fluffy$ time gcc -E main.c > /dev/null real 0m0.155s user 0m0.105s sys 0m0.027s folio[~/Desktop/pragma] fluffy$ gcc pragma.c -DPRAGMA_ONCE -DIFNDEF_GUARD folio[~/Desktop/pragma] fluffy$ ./a.out folio[~/Desktop/pragma] fluffy$ time gcc -E main.c > /dev/null real 0m0.153s user 0m0.101s sys 0m0.027s folio[~/Desktop/pragma] fluffy$ time gcc -E main.c > /dev/null real 0m0.181s user 0m0.133s sys 0m0.020s folio[~/Desktop/pragma] fluffy$ time gcc -E main.c > /dev/null real 0m0.167s user 0m0.119s sys 0m0.021s folio[~/Desktop/pragma] fluffy$ gcc --version Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx-include-dir=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.12.sdk/usr/include/c++/4.2.1 Apple LLVM version 8.1.0 (clang-802.0.42) Target: x86_64-apple-darwin17.0.0 Thread model: posix InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin 

Comme vous pouvez le voir, les versions avec #pragma once étaient en effet légèrement plus rapides à pré-traiter que #ifndef -uniquement, mais la différence était plutôt négligeable et serait largement éclipsée par la quantité de temps nécessaire à la construction et à la liaison du code. prendre. Peut-être qu'avec une base de code suffisamment importante, cela pourrait entraîner une différence de temps de construction de quelques secondes, mais entre les compilateurs modernes capables d'optimiser les gardes #ifndef , le fait que les systèmes d'exploitation possèdent de bons caches disque et les vitesses croissantes de la technologie de stockage. il semble que l'argument de performance est discutable, du moins sur un système de développement typique de nos jours. Les environnements de construction plus anciens et plus exotiques (par exemple les en-têtes hébergés sur un partage réseau, la création de bandes, etc.) peuvent modifier quelque peu l'équation, mais dans ces circonstances, il semble plus simple de créer un environnement de construction moins fragile.

Le fait est que #ifndef est normalisé avec un comportement standard alors que #pragma once ne l’est pas #pragma once et que #ifndef gère également les cas bizarres de système de fichiers et de chemin de recherche alors que #pragma once peut être très perturbé par certaines choses. le programmeur n'a aucun contrôle sur Le principal problème avec #ifndef est que les programmeurs choisissent les mauvais noms pour leurs gardes (avec des collisions de noms, etc.) et même alors, il est tout à fait possible que les utilisateurs d’API remplacent ces mauvais noms par #undef . mais c'est possible , alors que #pragma once n'a aucun recours si le compilateur élimine à tort un #include .

Ainsi, même si #pragma once est manifestement (légèrement) plus rapide, je ne suis pas d'accord pour dire que cela en soi est une raison de l'utiliser sur les gardes #ifndef .

EDIT : Grâce aux commentaires de @LightnessRacesInOrbit, j'ai augmenté le nombre de fichiers d’en-tête et modifié le test pour n’exécuter que l’étape de préprocesseur, éliminant le peu de temps ajouté par le processus de compilation et de liaison (ce qui était sortingvial avant et après). inexistant maintenant). Comme prévu, le différentiel est à peu près le même.

Je pense que la première chose à faire est de vérifier si cela va vraiment changer les choses, c.-à-d. vous devriez d’abord tester la performance. Une des recherches dans Google a lancé ceci .

Dans la page de résultats, les colonnes sont légèrement dépouillées pour moi, mais il est clair qu’au moins jusqu’à VC6, Microsoft n’implémentait pas les optimisations de gardes d’inclusion utilisées par les autres outils. Lorsque la garde d’inclusion était interne, cela prenait 50 fois plus de temps que lorsque la garde d’inclusion était externe (les gardes d’inclusion externe étaient au moins aussi bonnes que #pragma). Mais considérons l’effet possible de ceci:

Selon les tableaux présentés, le temps nécessaire pour ouvrir l’inclusion et la vérification est 50 fois celui d’un équivalent #pragma. Mais le temps réel pour le faire a été mesuré à 1 microseconde par fichier en 1999!

Alors, combien de doubles en-têtes une seule UT aura-t-elle? Cela dépend de votre style, mais si nous disons qu’un TU moyen a 100 doublons, en 1999, nous paierons potentiellement 100 microsecondes par UT. Avec les améliorations apscopes au disque dur, cela est probablement nettement inférieur à présent, mais même avec des en-têtes précompilés et un suivi correct des dépendances, le coût total cumulé pour un projet est certainement une partie insignifiante de votre temps de construction.

D’un autre côté, aussi improbable que cela puisse être, si vous passez à un compilateur qui ne supporte pas #pragma once considérez combien de temps il faudra pour mettre à jour votre firebase database entière afin d’inclure des gardes plutôt que # pragma?

Il n’y a aucune raison pour que Microsoft ne puisse pas implémenter une optimisation de garde d’inclusion de la même manière que GCC et tous les autres compilateurs (en fait, quelqu’un peut-il confirmer si leurs versions plus récentes implémentent cela?). IMHO, #pragma once fait très peu autre chose que de limiter votre choix de compilateur alternatif.

Il y a une question connexe à laquelle j’ai répondu :

#pragma once a un inconvénient (autre que d’être non standard) et c’est si vous avez le même fichier à des endroits différents (nous avons ceci parce que notre système de génération copie les fichiers) alors le compilateur pensera qu’il s’agit de fichiers différents.

J’ajoute également la réponse au cas où quelqu’un trébucherait sur cette question et pas sur l’autre.

#pragma once permet au compilateur d’ignorer complètement le fichier lorsqu’il se reproduit – au lieu d’parsingr le fichier jusqu’à ce qu’il atteigne les gardes #include.

En tant que telles, la sémantique est un peu différente, mais elles sont identiques si elles sont utilisées de la manière dont elles sont destinées à être utilisées.

La combinaison des deux est probablement la voie la plus sûre à suivre, car dans le pire des cas (un compilateur signalant des pragmas inconnus comme des erreurs réelles, pas seulement des avertissements), il suffirait de supprimer les # pragma eux-mêmes.

Lorsque vous limitez vos plates-formes à des “compilateurs grand public sur le bureau”, vous pouvez omettre en toute sécurité les gardes #include, mais cela me gêne également.

OT: Si vous avez d’autres conseils / expériences à partager sur l’accélération des builds, je serais curieux.

Pour ceux qui voudraient utiliser #pragma une fois et inclure des gardes ensemble: Si vous n’utilisez pas MSVC, alors vous n’obtiendrez pas beaucoup d’optimisation de la part de #pragma.

Et vous ne devriez pas mettre “#pragma une fois” dans un en-tête censé être inclus plusieurs fois, chaque inclusion pouvant avoir un effet différent.

Voici une discussion détaillée avec des exemples sur l’utilisation de #pragma une fois.

Atop explication par Konrad Kleine ci-dessus.

Un bref résumé:

  • lorsque nous utilisons # pragma once c’est # pragma once grande partie de la responsabilité du compilateur, ne pas autoriser son inclusion plus d’une fois. Ce qui signifie que, après avoir mentionné l’extrait de code dans le fichier, ce n’est plus votre responsabilité.

Maintenant, le compilateur regarde cet extrait de code au début du fichier et l’ignore pour l’inclure (s’il est déjà inclus une fois). This definitely will reduce the compilation-time (on an average and in huge-system). However, in case of mocks/test environment, will make the test-cases implementation difficult, due to circular etc dependencies.

  • Now, when we use the #ifndef XYZ_H for the headers, it is more of the developers responsibility to maintain the dependency of headers. Which means, whenever due to some new header file, there is possibility of the circular dependency, comstackr will just flag some ” undefined .. ” error messages at comstack time, and it is user to check the logical connection/flow of the entities and rectify the improper includes.

This definitely will add to the compilation time (as needs to rectified and re-run). Also, as it works on the basis of including the file, based on the “XYZ_H” defined-state, and still complains, if not able to get all the definitions.

Therefore, to avoid situations like this, we should use, as;

 #pragma once #ifndef XYZ_H #define XYZ_H ... #endif 

ie the combination of both.