La #pragma est-elle une fois un garde sûr?

311

J'ai lu qu'il y a une certaine optimisation du compilateur lors de l'utilisation, #pragma oncece qui peut entraîner une compilation plus rapide. Je reconnais que ce n'est pas standard et pourrait donc poser un problème de compatibilité entre plates-formes.

Est-ce quelque chose qui est pris en charge par la plupart des compilateurs modernes sur les plates-formes non Windows (GCC)?

Je veux éviter les problèmes de compilation de la plate-forme, mais je veux aussi éviter le travail supplémentaire des gardes de secours:

#pragma once
#ifndef HEADER_H
#define HEADER_H

...

#endif // HEADER_H

Dois-je m'inquiéter? Dois-je y consacrer plus d'énergie mentale?

Ryan Emerle
la source
3
Après avoir posé une question similaire , j'ai découvert que cela #pragma oncesemblait éviter certains problèmes d'affichage de classe dans VS 2008. Je suis en train de me débarrasser des gardes d'inclusion et de les remplacer tous #pragma oncepour cette raison.
SmacL

Réponses:

189

L'utilisation #pragma oncedevrait fonctionner sur n'importe quel compilateur moderne, mais je ne vois aucune raison de ne pas utiliser un standard #ifndefinclude guard. Cela fonctionne très bien. La seule mise en garde est que GCC ne prenait pas en charge #pragma onceavant la version 3.4 .

J'ai également constaté qu'au moins sur GCC, il reconnaît la #ifndefprotection standard incluse et l'optimise , donc il ne devrait pas être beaucoup plus lent que #pragma once.

Zifre
la source
12
Cela ne devrait pas être plus lent du tout (avec GCC de toute façon).
Jason Coco
54
Il n'est pas mis en œuvre de cette façon. Au lieu de cela, si le fichier commence par un #ifndef la première fois et se termine par un #endif, gcc s'en souvient et ignore toujours les inclusions à l'avenir sans même prendre la peine d'ouvrir le fichier.
Jason Coco
10
#pragma onceest généralement plus rapide car le fichier n'est pas prétraité. ifndef/define/endifnécessite un prétraitement de toute façon, car après ce bloc, vous pouvez avoir quelque chose de compilable (théoriquement)
Andrey
13
Documents GCC sur l'optimisation des macros de garde: gcc.gnu.org/onlinedocs/cppinternals/Guard-Macros.html
Adrian
38
Pour utiliser les protections d'inclusion, il existe une exigence supplémentaire que vous devez définir un nouveau symbole tel que #ifndef FOO_BAR_H, normalement pour un fichier tel que "foo_bar.h". Si vous renommez ce fichier ultérieurement, devez-vous ajuster les protections d'inclusion en conséquence pour être cohérent avec cette convention? De plus, si vous avez deux foo_bar.h distincts à deux endroits différents dans votre arborescence de code, vous devez penser à deux symboles différents pour chacun. La réponse courte est d'utiliser #pragma onceet si vous avez vraiment besoin de compiler dans un environnement qui ne le prend pas en charge, alors allez-y et ajoutez des gardes d'inclusion pour cet environnement.
Brandin
329

#pragma once a un inconvénient (autre que d'être non standard) et c'est si vous avez le même fichier à différents emplacements (nous l'avons parce que notre système de construction copie les fichiers), alors le compilateur pensera que ce sont des fichiers différents.

Motti
la source
36
Mais vous pouvez également avoir deux fichiers avec le même nom dans des emplacements différents sans avoir à vous soucier de créer différents #define NAMES, ce qui est souvent sous la forme de HEADERFILENAME_H
Vargas
69
Vous pouvez également avoir deux ou plusieurs fichiers avec le même #define WHATEVER, ce qui ne provoque pas de plaisir, c'est pourquoi je préférerais utiliser pragma une fois.
Chris Huang-Leaver,
107
Pas convaincant ... Remplacez le système de construction par un système qui ne copie pas les fichiers mais utilise des liens symboliques à la place, ou n'incluez le même fichier qu'à partir d'un seul emplacement dans chaque unité de traduction. Cela ressemble plus à votre infrastructure est un gâchis qui doit être réorganisé.
Yakov Galka
3
Et si vous avez différents fichiers portant le même nom dans différents répertoires, l'approche #ifdef pensera qu'il s'agit du même fichier. Il y a donc un inconvénient pour l'un et un inconvénient pour l'autre.
rxantos
3
@rxantos, si les fichiers diffèrent, la #ifdefvaleur de la macro peut également différer.
Motti
63

Je souhaite #pragma once(ou quelque chose comme ça) avait été dans la norme. Inclure les gardes n'est pas vraiment un gros problème (mais ils semblent être un peu difficiles à expliquer aux personnes qui apprennent la langue), mais cela semble être une gêne mineure qui aurait pu être évitée.

En fait, depuis 99,98% du temps, le #pragma oncecomportement est le comportement souhaité, cela aurait été bien si empêcher l'inclusion multiple d'un en-tête était automatiquement géré par le compilateur, avec un #pragmaou quelque chose pour permettre la double inclusion.

Mais nous avons ce que nous avons (sauf que vous pourriez ne pas en avoir #pragma once).

Michael Burr
la source
48
Ce que je veux vraiment, c'est une #importdirective standard .
John
10
Une directive d'importation standard arrive: isocpp.org/blog/2012/11/… Mais pas encore ici. J'y suis fortement favorable.
AHelps
7
@AHelps Vaporware. Cela fait-il presque cinq ans maintenant? Peut-être qu'en 2023 vous reviendrez sur ce commentaire et vous direz "je vous l'ai dit".
doug65536
Ce n'est pas du vaporware, mais seulement au stade des spécifications techniques. Les modules sont implémentés dans Visual Studio 2015 ( blogs.msdn.microsoft.com/vcblog/2015/12/03/… ) et dans clang ( clang.llvm.org/docs/Modules.html ). Et c'est import, pas #import.
AHelps
Devrait le faire en C ++ 20.
Ionoclast Brigham
36

Je ne connais aucun avantage en termes de performances, mais cela fonctionne certainement. Je l'utilise dans tous mes projets C ++ (à condition que j'utilise le compilateur MS). Je trouve que c'est plus efficace que d'utiliser

#ifndef HEADERNAME_H
#define HEADERNAME_H
...
#endif

Il fait le même travail et ne remplit pas le préprocesseur avec des macros supplémentaires.

GCC prend #pragma onceofficiellement en charge à partir de la version 3.4 .

JaredPar
la source
25

GCC prend en charge #pragma oncedepuis la 3.4, voir http://en.wikipedia.org/wiki/Pragma_once pour plus de prise en charge du compilateur.

Le gros avantage que je vois sur l'utilisation #pragma onceplutôt que d'inclure des gardes est d'éviter les erreurs de copier / coller.

Avouons-le: la plupart d'entre nous commencent à peine un nouveau fichier d'en-tête à partir de zéro, mais plutôt copions simplement un fichier existant et modifiez-le selon nos besoins. Il est beaucoup plus facile de créer un modèle de travail en utilisant #pragma onceau lieu d'inclure des gardes. Moins je dois modifier le modèle, moins je risque de rencontrer des erreurs. Avoir le même garde d'inclusion dans différents fichiers conduit à d'étranges erreurs de compilation et il faut un certain temps pour comprendre ce qui n'a pas fonctionné.

TL; DR: #pragma onceest plus facile à utiliser.

uceumern
la source
11

Je l'utilise et j'en suis content, car je dois taper beaucoup moins pour faire un nouvel en-tête. Cela a bien fonctionné pour moi sur trois plates-formes: Windows, Mac et Linux.

Je n'ai pas d'informations sur les performances mais je pense que la différence entre #pragma et include guard ne sera rien comparée à la lenteur de l'analyse de la grammaire C ++. Voilà le vrai problème. Essayez de compiler le même nombre de fichiers et de lignes avec un compilateur C # par exemple, pour voir la différence.

En fin de compte, utiliser la garde ou le pragma n'aura aucune importance.

Edwin Jarvis
la source
Je n'aime pas #pragma une fois, mais je vous remercie de souligner les avantages relatifs ... L'analyse C ++ est beaucoup plus chère qu'autre chose, dans un environnement d'exploitation "normal". Personne ne compile à partir d'un système de fichiers distant si les temps de compilation sont un problème.
Tom
1
Re C ++ analyse la lenteur par rapport à C #. En C #, vous n'avez pas à analyser (littéralement) des milliers de fichiers LOC (iostream, n'importe qui?) Pour chaque petit fichier C ++. Utilisez des en-têtes précompilés pour réduire ce problème, cependant
Eli Bendersky
11

L'utilisation de ' #pragma once' pourrait ne pas avoir d'effet (elle n'est pas prise en charge partout - bien qu'elle soit de plus en plus largement prise en charge), vous devez donc utiliser le code de compilation conditionnelle de toute façon, dans ce cas, pourquoi s'embêter avec ' #pragma once'? Le compilateur l'optimise probablement de toute façon. Cela dépend cependant de vos plateformes cibles. Si toutes vos cibles le prennent en charge, alors allez-y et utilisez-le - mais cela devrait être une décision consciente car tout l'enfer se déchaînera si vous utilisez uniquement le pragma, puis portez sur un compilateur qui ne le prend pas en charge.

Jonathan Leffler
la source
1
Je ne suis pas d'accord pour dire que vous devez de toute façon soutenir les gardes. Si vous utilisez #pragma une fois (ou des gardes) c'est parce que cela soulève certains conflits sans eux. Donc, s'il n'est pas pris en charge par votre outil de chaîne, le projet ne se compilera tout simplement pas et vous êtes exactement dans le même genre de situation que lorsque vous souhaitez compiler des ansi C sur un ancien compilateur K&R. Il vous suffit d'obtenir un outil de chaint plus récent ou de changer le code pour ajouter des gardes. Tout l'enfer serait si le programme se compilait mais ne fonctionnait pas.
kriss
5

L'avantage en termes de performances est de ne pas avoir à rouvrir le fichier une fois la #pragma lue. Avec les gardes, le compilateur doit ouvrir le fichier (qui peut être coûteux en temps) pour obtenir les informations qu'il ne devrait plus inclure son contenu.

C'est de la théorie uniquement parce que certains compilateurs n'ouvriront pas automatiquement les fichiers sans code de lecture pour chaque unité de compilation.

Quoi qu'il en soit, ce n'est pas le cas pour tous les compilateurs, donc idéalement, #pragma doit être évité une fois pour le code multiplateforme s'il n'est pas standard du tout / n'a pas de définition et d'effet normalisés. Cependant, pratiquement, c'est vraiment mieux que les gardes.

En fin de compte, la meilleure suggestion que vous pouvez obtenir pour être sûr d'avoir la meilleure vitesse de votre compilateur sans avoir à vérifier le comportement de chaque compilateur dans ce cas, est d'utiliser à la fois pragma et gardes.

#ifndef NR_TEST_H
#define NR_TEST_H
#pragma once

#include "Thing.h"

namespace MyApp
{
 // ...
}

#endif

De cette façon, vous obtenez le meilleur des deux (multiplateforme et vitesse de compilation de l'aide).

Comme il est plus long à taper, j'utilise personnellement un outil pour aider à générer tout cela de manière très mèche (Visual Assist X).

Klaim
la source
Visual Studio n'optimise-t-il pas # les gardes en l'état? D'autres compilateurs (mieux?) Le font, et j'imagine que c'est assez facile.
Tom
1
Pourquoi mettez-vous l' pragmaafter the ifndef? Y a-t-il un avantage?
user1095108
1
@ user1095108 Certains compilateurs utiliseront les gardes d'en-tête comme délimiteur pour savoir si le fichier contient uniquement du code qui doit être instancié une fois. Si du code est en dehors des gardes d'en-tête, alors le fichier entier peut être considéré comme instanciable plus d'une fois. Si ce même compilateur ne prend pas en charge le pragma une fois, il ignorera cette instruction. Par conséquent, placer le pragma une fois à l'intérieur des protections d'en-tête est le moyen le plus générique de s'assurer qu'au moins les protections d'en-tête peuvent être «optimisées».
Klaim
4

Pas toujours.

http://gcc.gnu.org/bugzilla/show_bug.cgi?id=52566 a un bel exemple de deux fichiers destinés à être inclus, mais pensés à tort comme étant identiques en raison d'horodatages et de contenu identiques (nom de fichier différent) .

Omer
la source
10
Ce serait un bogue dans le compilateur. (essayer de prendre un raccourci ne devrait pas prendre).
rxantos
4
#pragma oncen'est pas standard, donc tout ce qu'un compilateur décide de faire est "correct". Bien sûr, nous pouvons alors commencer à parler de ce qui est "attendu" et de ce qui est "utile".
user7610
2

En utilisant gcc 3.4 et 4.1 sur de très grands arbres (en utilisant parfois distcc ), je n'ai pas encore vu d'accélération lors de l'utilisation de #pragma une fois à la place ou en combinaison avec les gardes d'inclusion standard.

Je ne vois vraiment pas comment cela vaut la peine de confondre les anciennes versions de gcc, ou même d'autres compilateurs, car il n'y a pas de réelles économies. Je n'ai pas essayé tous les différents dé-linters, mais je suis prêt à parier que cela en confondra beaucoup.

Je souhaite moi aussi qu'il ait été adopté dès le début, mais je peux voir l'argument "Pourquoi en avons-nous besoin si ifndef fonctionne parfaitement bien?". Compte tenu des nombreux coins sombres et de la complexité de C, les gardes d'inclusion sont l'une des choses les plus faciles à expliquer. Si vous avez même une petite connaissance du fonctionnement du préprocesseur, ils devraient être explicites.

Cependant, si vous observez une accélération importante, veuillez mettre à jour votre question.

Tim Post
la source
2

Aujourd'hui, les gardes à l'ancienne sont aussi rapides qu'une #pragma une fois. Même si le compilateur ne les traite pas spécialement, il s'arrêtera quand il verra #ifndef WHATEVER et WHATEVER est défini. L'ouverture d'un fichier est très bon marché aujourd'hui. Même s'il devait y avoir une amélioration, ce serait de l'ordre des millisecondes.

Je n'utilise tout simplement pas #pragma une fois, car cela n'a aucun avantage. Pour éviter les conflits avec d'autres gardes d'inclusion, j'utilise quelque chose comme: CI_APP_MODULE_FILE_H -> CI = Company Initials; APP = nom de l'application; Le reste est explicite.

CMircea
la source
19
L'avantage n'est-il pas beaucoup moins de taper?
Andrey
1
Notez cependant que quelques millisecondes cent mille fois sont quelques minutes. Les grands projets se composent de dix milliers de fichiers comprenant des dizaines d'en-têtes chacun. Étant donné les processeurs multicœurs actuels, les entrées / sorties, en particulier l'ouverture de nombreux petits fichiers , constituent l'un des principaux goulots d'étranglement.
Damon
1
"Aujourd'hui, les gardes de la vieille école sont aussi rapides qu'une #pragma une fois." Aujourd'hui et aussi il y a de nombreuses années. Les documents les plus anciens sur le site GCC datent de 2,95 depuis 2001 et l'optimisation des gardes d'inclusion n'était pas nouvelle à l'époque. Ce n'est pas une optimisation récente.
Jonathan Wakely
4
Le principal avantage est que les protections incluent sont sujettes aux erreurs et verbeuses. Il est trop facile d'avoir deux fichiers différents avec des noms identiques dans des répertoires différents (et les gardes d'inclusion peuvent être le même symbole), ou de faire des erreurs de copier-coller lors de la copie des gardes d'inclusion. Pragma est une fois moins sujet aux erreurs et fonctionne sur toutes les principales plates-formes PC. Si vous pouvez l'utiliser, c'est un meilleur style.
AHelps
2

La principale différence est que le compilateur a dû ouvrir le fichier d'en-tête pour lire le garde d'inclusion. En comparaison, pragma oblige le compilateur à garder une trace du fichier et à ne faire aucun IO de fichier lorsqu'il rencontre une autre inclusion pour le même fichier. Bien que cela puisse sembler négligeable, il peut facilement évoluer avec d'énormes projets, en particulier ceux sans bon en-tête.

Cela dit, de nos jours, les compilateurs (y compris GCC) sont assez intelligents pour traiter une fois les gardes comme Pragma. c'est-à-dire qu'ils n'ouvrent pas le fichier et évitent la pénalité d'E / S du fichier.

Dans les compilateurs qui ne prennent pas en charge pragma, j'ai vu des implémentations manuelles un peu lourdes.

#ifdef FOO_H
#include "foo.h"
#endif

Personnellement, j'aime l'approche #pragma une fois car elle évite les tracas de nommer les collisions et les erreurs de frappe potentielles. C'est aussi un code plus élégant en comparaison. Cela dit, pour le code portable, cela ne devrait pas faire de mal d'avoir les deux à moins que le compilateur ne s'en plaint.

Shammi
la source
1
"Cela dit, de nos jours, les compilateurs (y compris GCC) sont assez intelligents pour traiter une fois les gardes comme Pragma." Ils le font depuis des décennies, peut-être plus longtemps que ce #pragma oncequi existait!
Jonathan Wakely
Je pense que tu m'as mal compris. Je voulais dire avant le pragma une fois, tous les compilateurs encourraient plusieurs conventions d'E / S pour le même fichier h inclus plusieurs fois pendant la phase de préprocesseur. Les implémentations modernes finissent par utiliser une meilleure mise en cache des fichiers au stade du préprocesseur. Quoi qu'il en soit, sans pragmas, l'étape du préprocesseur finit toujours par inclure tout ce qui se trouve en dehors des gardes d'inclusion. Avec pragma une fois, tout le fichier est laissé de côté. De ce point de vue, le pragma est toujours avantageux.
Shammi
1
Non, c'est faux, des compilateurs décents laissent le fichier entier même sans #pragma une fois, ils n'ouvrent pas le fichier une deuxième fois et ils ne le regardent même pas une deuxième fois, voir gcc.gnu.org/onlinedocs/ cpp / Once-Only-Headers.html (cela n'a rien à voir avec la mise en cache).
Jonathan Wakely
1
À partir de votre lien, il semble que l'optimisation ne se produise qu'en cpp. Quoi qu'il en soit, la mise en cache entre en jeu. Comment le préprocesseur sait-il inclure du code en dehors des gardes. Exemple ... extern int foo; #ifndef INC_GUARD #define INC_GUARD class ClassInHeader {}; #endif Dans ce cas, le préprocesseur devra inclure extern int foo; plusieurs fois si vous incluez le même fichier plusieurs fois. Fin de la journée, pas grand-chose à discuter à ce sujet tant que nous comprenons la différence entre #pragma une fois et que nous incluons des gardes et comment les différents compilateurs se comportent avec les deux :)
Shammi
1
Il n'applique évidemment pas l'optimisation à cela.
Jonathan Wakely
1

Si nous utilisons msvc ou Qt (jusqu'à Qt 4.5), depuis GCC (jusqu'à 3.4), msvc supporte tous les deux #pragma once, je ne vois aucune raison de ne pas l'utiliser #pragma once.

Le nom du fichier source est généralement le même nom de classe égal, et nous savons, parfois, nous avons besoin d'un remaniement , pour renommer le nom de la classe, alors nous avons dû changer le #include XXXXaussi, donc je pense que le maintien manuel #include xxxxxn'est pas un travail intelligent. même avec l'extension Visual Assist X, maintenir le "xxxx" n'est pas un travail nécessaire.

raidsan
la source
1

Note supplémentaire aux personnes qui pensent qu'une inclusion automatique unique des fichiers d'en-tête est toujours souhaitée: je construis des générateurs de code en utilisant une double ou plusieurs inclusion de fichiers d'en-tête depuis des décennies. Surtout pour la génération de stubs de bibliothèque de protocoles, je trouve très confortable d'avoir un générateur de code extrêmement portable et puissant sans outils et langages supplémentaires. Je ne suis pas le seul développeur à utiliser ce schéma comme le montrent ces blogs X-Macros . Cela ne serait pas possible sans le gardiennage automatique manquant.

Marcel
la source
Les modèles C ++ pourraient-ils résoudre le problème? Je trouve rarement un besoin valide de macros en raison de la façon dont les modèles C ++.
Plus clair
1
Notre expérience professionnelle à long terme est que l'utilisation constante d'une infrastructure de langage, de logiciels et d'outils mature nous donne en tant que fournisseurs de services (systèmes intégrés) un énorme avantage en termes de productivité et de flexibilité. Les concurrents qui développent des logiciels et des piles de systèmes embarqués basés sur C ++ pourraient plutôt trouver certains de leurs développeurs plus heureux au travail. Mais nous les surpassons généralement en termes de délais de commercialisation, de fonctionnalité et de flexibilité à plusieurs reprises. Nether sous-estime les gains de productivité liés à l'utilisation répétée d'un seul et même outil. Les Web-Devs souffrent à la place de nombreux moyens de nombreux Frameworks.
Marcel
Une remarque cependant: n'inclut pas guards / # pragma une fois dans chaque fichier d'en-tête par rapport au principe DRY lui-même. Je peux voir votre point dans la X-Macrofonctionnalité, mais ce n'est pas l'utilisation principale d'inclure, ne devrait-il pas être l'inverse comme header unguard / # pragma multi si nous devions rester avec DRY?
caiohamamura le
DRY signifie "Ne répétez pas VOUS-MÊME". Il s'agit de l'humain. Ce que fait la machine n'a rien à voir avec ce paradigme. Les modèles C ++ se répètent beaucoup, les compilateurs C le font aussi (par exemple le déroulement des boucles) et chaque ordinateur répète presque tout ce qui est incroyable souvent et rapidement.
Marcel