#pragma une fois que vs inclure des gardes?

je travaille sur une base de code qui n'est connue que pour fonctionner sous windows et être compilée sous Visual Studio (elle s'intègre étroitement avec excel de sorte qu'elle ne va nulle part). Je me demande si je devrais utiliser le traditionnel include guards ou utiliser #pragma once pour notre code. Je pense que laisser le compilateur traiter avec #pragma once donnera des compilations plus rapides et est moins susceptible d'erreur lors de l'adaptation et le collage. Il est aussi légèrement moins laid ;)

Note: pour obtenir des temps de compilation plus rapides, nous pourrions utiliser redondant Include Guards mais cela ajoute un couplage étroit entre le fichier inclus et le fichier inclus. Habituellement c'est ok parce que le gardien devrait être basé sur le nom du fichier et ne changerait que si vous aviez besoin de changer le nom d'include de toute façon.

278
demandé sur Matt Price 2009-07-17 19:18:03

13 réponses

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 réellement partie de la norme. Le préprocesseur peut être un peu plus rapide avec lui car il est plus simple de comprendre votre intention exacte.

#pragma once est moins susceptible de faire des erreurs et il est moins de code à taper.

pour accélérer le temps de compilation, il suffit de faire suivre declare au lieu de l'inclure .fichiers h quand vous le pouvez.

je préfère utiliser #pragma once .

voir ce article de wikipedia sur la possibilité d'utiliser à la fois .

244
répondu Brian R. Bondy 2009-07-17 15:34:02

je voulais juste ajouter à cette discussion que je compilais juste sur VS et GCC, et utilisé pour utiliser include guards. 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 prennent en charge, et c'est que:

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

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

138
répondu Cookie 2014-11-30 09:57:05

#pragma once a irrécupérable "1519170920 des" bugs". Il ne doit jamais être utilisé.

si votre chemin de recherche #include est suffisamment compliqué, le compilateur peut être incapable de faire la différence entre deux en-têtes avec le même nom de base (par exemple a/foo.h et b/foo.h ), de sorte qu'un #pragma once dans l'un d'eux supprimera les deux . Il se peut également qu'il ne soit pas possible de dire que deux membres de la famille différents comprennent (par exemple #include "foo.h" et #include "../a/foo.h" se réfère au même fichier, donc #pragma once ne supprimera pas une inclusion redondante quand elle aurait dû.

cela affecte également la capacité du compilateur à éviter la relecture de fichiers avec des gardes #ifndef , mais c'est juste une optimisation. Avec #ifndef les gardes, le compilateur peut lire en toute sécurité n'importe quel dossier il n'est pas sûr il a déjà vu; si c'est faux, il doit juste faire un peu de travail supplémentaire. Tant que deux en-têtes ne définissent pas même macro de garde, le code compilera comme prévu. Et si deux en-têtes do définissent la même macro de garde, le programmeur peut entrer et changer l'un d'eux.

#pragma once n'a pas de tel filet de sécurité -- si le compilateur se trompe sur l'identité d'un fichier d'en-tête, dans les deux cas , le programme ne compilera pas. Si vous touchez ce bug, vos seules options sont d'arrêter d'utiliser #pragma once , ou de renommer l'un des en-têtes. Le les noms des en-têtes font partie de votre contrat API, donc renommer n'est probablement pas une option.

(la version courte de la raison pour laquelle il s'agit de unfixable est que ni L'API du système de fichiers Unix ni L'API Windows n'offrent de mécanisme que garantit pour vous dire si deux noms de chemin absolus se réfèrent au même fichier. Si vous êtes sous l'impression que les numéros d'inode peuvent être utilisés pour cela, désolé, vous avez tort.)

(note Historique: La seule raison pour laquelle je n'ai pas rip #pragma once et #import de GCC quand j'ai eu l'autorité pour le faire, ~il y a 12 ans, était d'Apple-têtes système en s'appuyant sur eux. Rétrospectivement, cela ne devrait pas m'en empêcher.)

(puisque cela est maintenant apparu deux fois dans le fil de commentaire: les développeurs GCC ont mis tout un effort pour rendre #pragma once aussi fiable que possible; voir rapport de bogue GCC 11569 . Cependant, la mise en œuvre dans les versions actuelles de GCC peut encore échouer dans des conditions plausibles, comme la construction d'exploitations agricoles souffrant de décalage horaire. Je ne sais pas à quoi ressemble l'implémentation d'un autre compilateur, mais je ne m'attendais pas à ce que quelqu'un ait fait mieux .)

87
répondu zwol 2017-04-08 01:57:05

Jusqu'au jour où #pragma once devient standard (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 savent pas, au moins il sera ignoré.
  • Comme il n'y a pas de comportement standard pour #pragma once , vous ne devriez pas supposer que le comportement sera le même sur tous les compilateurs. Les gardiens veilleront au moins à ce que l'hypothèse de base soit la même pour tous les compilateurs qui mettent au moins en œuvre les instructions de préprocesseur nécessaires pour les gardiens.
  • sur la plupart des compilateurs, #pragma once va accélérer la compilation (d'un cpp) parce que le compilateur ne va pas rouvrir le fichier contenant cette instruction. Afin de l'avoir dans un fichier peut aider, ou non, tout dépend du compilateur. J'ai entendu dire que g++ peut faire la même optimisation quand les gardes sont détectés mais il faut le confirmer.

en utilisant les deux ensembles 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 pourrait être plus pratique d'utiliser #pragma once . Juste savoir ce que cela signifie pour le code portable. (J'utilise VAssistX pour générer les gardes et pragma une fois rapidement)

vous devriez presque toujours penser votre code d'une manière portable (parce que vous ne savez pas de quoi l'avenir est fait) mais si vous pensez vraiment qu'il n'est pas destiné à être compilé avec un autre compilateur (code pour un matériel intégré très spécifique par exemple) alors vous devriez juste vérifier votre documentation de compilateur sur #pragma once pour savoir ce que vous faites vraiment.

33
répondu Klaim 2016-09-19 17:43:08

à Partir d'un logiciel de testeur de point de vue

#pragma once est plus court qu'un garde include, moins sujet aux erreurs, supporté par la plupart des compilateurs, et certains disent qu'il compile plus rapidement (ce qui n'est pas vrai [plus longtemps]).

mais je vous suggère quand même d'utiliser le standard #ifndef incluant les gardes.

pourquoi #ifndef ?

considérez une hiérarchie de classe artificielle comme celle-ci où chacune des classes A , B , et C vit dans son propre fichier:

A. h

#ifndef A_H
#define A_H

class A {
public:
  // some virtual functions
};

#endif

B. h

#ifndef B_H
#define B_H

#include "a.h"

class B : public A {
public:
  // some functions
};

#endif

C. h

#ifndef C_H
#define C_H

#include "b.h"

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 vraiment complexe B . Une façon de le faire serait d'écrire un classe simulée en utilisant par exemple Google mock et le mettre dans un répertoire mocks/b.h . Notez que le nom de la classe n'a pas changé mais qu'il est seulement stocké dans un répertoire différent. Mais ce qui est le plus important est que le garde include est nommé exactement le même que dans le fichier original b.h .

Mock/B. h

#ifndef B_H
#define B_H

#include "a.h"
#include "gmock/gmock.h"

class B : public A {
public:
  // some mocks functions
  MOCK_METHOD0(SomeMethod, void());
};

#endif

Quel est le bénéfice?

avec cette approche, vous pouvez vous moquer du comportement de la classe B sans toucher la classe d'origine ou dire C à ce sujet. Tout ce que vous avez à faire est de mettre le répertoire mocks/ dans le chemin de votre compilateur.

pourquoi ne pas le faire avec #pragma once ?

si vous aviez utilisé #pragma once , vous obtiendriez un nom clash car il ne peut pas vous empêcher de définir la classe B deux fois, une fois l'original et une fois la version moquée.

29
répondu Konrad Kleine 2014-11-13 11:38:26

si vous êtes certain que vous n'utiliserez jamais ce code dans un compilateur qui ne le supporte pas (Windows/VS, GCC, et Clang sont des exemples de compilateurs que do supporte), alors 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 la vitesse de compilation sur les systèmes compatibles

#pragma once
#ifndef _HEADER_H_
#define _HEADER_H_

...

#endif
24
répondu Donnie DeBoer 2015-08-02 20:05:36

Je ne me soucie généralement pas de #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 #include guards 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 raison et cela provoquera souvent des avertissements inutiles sur les compilateurs qui ne le supportent pas.

Je ne sais pas ce que gain de temps que le pragma pourrait apporter. J'ai entendu dire que les compilateurs reconnaissent généralement déjà quand un en-tête n'a rien d'autre que des commentaires en dehors des macros de guard et vont faire l'équivalent #pragma once dans ce cas (i.e.., jamais de traitement de nouveau le fichier). Mais je ne suis pas sûr si c'est vrai ou juste un cas de compilateurs pourrait faire cette optimisation.

dans tous les cas, il est juste plus facile pour moi d'utiliser #include des gardes qui vont travailler partout et ne pas s'inquiéter à ce sujet plus loin.

15
répondu Michael Burr 2009-07-17 18:42:22

après une longue discussion sur le présumé compromis de performance entre #pragma once et #ifndef guards vs. l'argument de l'exactitude ou non (je prenais le parti de #pragma once basé sur une certaine endoctrinement relativement récente à cette fin), j'ai décidé de tester enfin la théorie que #pragma once est plus rapide parce que le compilateur n'a pas à essayer de re - #include un fichier qui avait déjà été inclus.

pour le test, I généré automatiquement 500 fichiers d'en-tête avec des interdépendances complexes, et avait un .c fichier qui #include s eux tous. J'ai fait le test de trois façons, 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 tournant sous OSX, en utilisant le clang empaqueté de XCode, avec le SSD interne).

premièrement, le code d'essai:

#include <stdio.h>

//#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%d.h", 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%d.h\"\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%d.h\"\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 essais:

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 le #ifndef - un seul, mais la différence était tout à fait négligeable, et serait de loin éclipsée par la quantité de temps qui réellement construire et relier le code prendrait. Peut-être avec un codebase assez grand, il pourrait en fait conduire à une différence de temps de construction de quelques secondes, mais entre les compilateurs modernes être en mesure de optimiser #ifndef garde, le fait que les os ont de bonnes caches de disque, et les vitesses croissantes de la technologie de stockage, il semble que l'argument de performance est sans objet, au moins sur un système de développeur typique de nos jours. Des environnements de construction plus anciens et plus exotiques (par exemple des en-têtes hébergés sur un partage de réseau, des constructions à partir de bandes, etc.) peut modifier quelque peu l'équation, mais dans ces circonstances, il semble plus utile de simplement créer un environnement bâti moins fragile.

le fait de la question Est, #ifndef est standardisé avec le comportement standard tandis que #pragma once n'est pas, et #ifndef gère également bizarre système de fichiers et les cas de corner chemin de recherche tandis que #pragma once peut être très confus par certaines choses, conduisant à un comportement incorrect qui le programmeur n'a aucun contrôle sur. Le problème principal avec #ifndef est que les programmeurs choisissent de mauvais noms pour leurs gardes (avec les collisions de noms et ainsi de suite) et même alors c'est assez possible pour le consommateur D'une API de passer outre ces noms pauvres en utilisant #undef - pas une solution parfaite, peut-être, mais il est possible , tandis que #pragma once n'a aucun recours si le compilateur est par erreur culling un #include .

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

EDIT : grâce au feedback de @LightnessRacesInOrbit j'ai augmenté le nombre de fichiers d'en-tête et changé le test pour ne Lancer que l'étape du préprocesseur, éliminant n'importe quelle petite quantité de temps a été ajoutée par le processus de compilation et de lien (qui était trivial avant et inexistant maintenant). Comme prévu, le différentiel est à peu près le même.

11
répondu fluffy 2017-07-15 20:34:15

je pense que la première chose que vous devriez faire est de vérifier si cela va vraiment faire une différence, c'est à dire. vous devez d'abord tester les performances. Une des recherches dans google a vomi ce .

dans la page des résultats, les colonnes sont légèrement off pour moi, mais il est clair qu'au moins JUSQU'à VC6 microsoft ne mettait pas en œuvre les optimisations include guard que les autres outils utilisaient. Là où la garde inclue était interne il a fallu 50 les temps sont aussi longs que ceux où le gardien include était externe (les gardiens include externes sont au moins aussi bons que #pragma). Mais considérons l'effet possible de ceci:

selon les tableaux présentés, le temps d'ouvrir l'include et de le vérifier est 50 fois celui d'un équivalent #pragma. Mais le temps a été mesuré à 1 microseconde par fichier en 1999!

donc, combien d'en-têtes doubles un seul TU aura-t-il? Cela dépend sur votre style, Mais si nous disons qu'un TU moyen a 100 doubles, alors en 1999, nous sommes potentiellement payer 100 microsecondes par TU. Avec des améliorations HDD, cela est probablement beaucoup plus faible maintenant, mais même alors avec des en-têtes précompilés et un suivi de la dépendance correcte, le coût cumulatif total de ceci pour un projet est presque certainement une partie importante de votre temps de construction.

maintenant, de l'autre côté, aussi peu probable que cela puisse être, si jamais vous passez à un compilateur qui ne soutenez #pragma once alors considérez combien de temps cela prendra pour mettre à jour votre base de source entière pour avoir inclure des gardes plutôt que #pragma?

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

9
répondu Richard Corden 2009-07-20 08:45:47

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 que si vous avez le même fichier dans des endroits différents (nous l'avons parce que notre système de compilation copie les fichiers autour) alors le compilateur pensera qu'il s'agit de fichiers différents.

j'ajoute la réponse ici aussi au cas où quelqu'un trébuche sur cette question et pas l'autre.

9
répondu Motti 2017-05-23 11:47:36

#pragma once permet au compilateur de sauter complètement le fichier quand il se produit à nouveau - au lieu de parser le fichier jusqu'à ce qu'il atteigne les gardes #include.

en tant que telle, la sémantique est un peu différente, mais elles sont identiques si elles sont utilisées de la façon dont elles sont destinées à être utilisées.

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

quand vous limitez vos plates-formes à, disons "compilateurs mainstream sur le bureau", vous pouvez en toute sécurité omettre le #include guards, mais je me sens mal à l'aise là aussi.

OT: si vous avez d'autres conseils/expériences à partager sur l'accélération des constructions, je serais curieux.

4
répondu peterchen 2009-07-17 15:38:07

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 #pragma une fois.

et vous ne devriez pas mettre" #pragma once " dans un en-tête qui est censé être inclus plusieurs fois avec chaque inclusion ayant peut-être un effet différent.

ici est une discussion détaillée avec des exemples sur l'usage #pragma once.

1
répondu Deqing 2013-09-17 02:48:48

au Sommet de l'explication par Konrad Kleine ci-dessus.

un bref résumé:

  • lorsque nous utilisons # pragma once c'est une grande partie de la responsabilité du compilateur, de ne pas permettre son inclusion plus d'une fois. Ce qui veut dire qu'après avoir mentionné le code dans le fichier, ce n'est plus votre responsabilité.

maintenant, le compilateur regarde, pour ce code-snippet au début du fichier, et saute d'être inclus (si déjà inclus une fois). Cela va certainement réduire le temps de compilation (en moyenne et en énorme-système). Cependant, en cas de mocks/environnement d'essai, rendra la mise en œuvre de cas d'essai difficile, en raison de la circulaire etc dépendances.

  • Maintenant, lorsque nous utilisons le #ifndef XYZ_H pour les en-têtes, c'est plus pour les développeurs responsabilité de maintenir la dépendance des en-têtes. Ce qui signifie, Chaque fois que dû à un nouveau fichier d'en-tête, là est une possibilité de la dépendance circulaire, compilateur va juste signaler quelques " undefined .. " messages d'erreur au moment de compiler, et il est utilisateur pour vérifier la connexion logique/flux des entités et de rectifier les inclusions inappropriées.

cela va certainement ajouter au temps de compilation (comme les besoins de rectifié et relancé). En outre, comme il fonctionne sur la base de l'inclusion du fichier, basé sur L'état" XYZ_H " défini -, et se plaint toujours, si pas en mesure d'obtenir tous les définition.

par conséquent, pour éviter des situations comme celle-ci, nous devrions utiliser, comme;

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

c'est à dire la combinaison des deux.

1
répondu parasrish 2017-05-23 11:33:24