#pragma, una volta, includi le guardie?

Sto lavorando su un codebase che è noto per funzionare solo su Windows ed essere compilato in Visual Studio (si integra strettamente con Excel, quindi non sta andando da nessuna parte). Mi chiedo se dovrei andare con le tradizionali guardie incluse o usare #pragma once per il nostro codice. Penserei che lasciare che il compilatore abbia a che fare con #pragma once produrrà compilazioni più veloci ed è meno sobject a errori quando si copiano e incollano. È anche leggermente meno brutto ;)

Nota: per ottenere tempi di compilazione più rapidi, è ansible utilizzare le protezioni Incluse ridondanti, ma ciò aggiunge un accoppiamento stretto tra il file incluso e il file incluso. Di solito è ok perché la guardia dovrebbe essere basata sul nome del file e cambierebbe solo se fosse necessario cambiare comunque il nome di include.

Non penso che farà una differenza significativa in fase di compilazione, ma #pragma once è molto ben supportato tra i compilatori ma non in realtà parte dello standard. Il preprocessore può essere un po ‘più veloce con esso in quanto è più semplice capire il tuo intento preciso.

#pragma once è meno incline a fare errori ed è meno codice da digitare.

Per accelerare il tempo di compilazione, è sufficiente inoltrare la dichiarazione anziché includere i file .h quando ansible.

Preferisco usare #pragma once .

Vedi questo articolo su Wikipedia su la possibilità di utilizzare entrambi .

Volevo solo aggiungere a questa discussione che sto solo compilando su VS e GCC, e usavo includere le guardie. Ora sono passato a #pragma once , e l’unica ragione per me non sono le prestazioni o la portabilità o lo standard in quanto non mi interessa quello che è standard finché VS e GCC lo supportano, e cioè che:

#pragma once riduce le possibilità di errori.

È fin troppo facile copiare e incollare un file di intestazione in un altro file di intestazione, modificarlo per soddisfare le proprie esigenze e dimenticare di cambiare il nome della guardia di inclusione. Una volta che entrambi sono inclusi, ci vuole un po ‘per rintracciare l’errore, in quanto i messaggi di errore non sono necessariamente chiari.

#pragma once ha bug non risolvibili . Non dovrebbe mai essere usato.

Se il tuo #include percorso di ricerca è sufficientemente complicato, il compilatore potrebbe non essere in grado di distinguere tra due intestazioni con lo stesso nome di base (ad esempio a/foo.h e b/foo.h ), quindi un #pragma once in uno di essi sopprimerà entrambi . Potrebbe anche non essere in grado di dire che due diversi parenti includono (ad esempio #include "foo.h" e #include "../a/foo.h" riferiscono allo stesso file, quindi #pragma once non riuscirà a sopprimere un ridondante includere quando dovrebbe.

Ciò influisce anche sulla capacità del compilatore di evitare di rileggere i file con le guardie #ifndef , ma questa è solo un’ottimizzazione. Con le guardie #ifndef , il compilatore può tranquillamente leggere qualsiasi file che non è sicuro di aver già visto; se è sbagliato, deve solo fare un lavoro extra. Finché non ci sono due intestazioni che definiscono la stessa macro di guardia, il codice verrà compilato come previsto. E se due intestazioni definiscono la stessa macro di guardia, il programmatore può entrare e cambiarne una.

#pragma once non ha una tale rete di sicurezza – se il compilatore ha torto sull’id quadro di un file di intestazione, in entrambi i casi , il programma non riuscirà a compilare. Se riscontri questo errore, le tue uniche opzioni sono di smettere di usare #pragma once o di rinominare una delle intestazioni. I nomi delle intestazioni fanno parte del tuo contratto API, quindi la rinomina non è probabilmente un’opzione.

(La versione breve del motivo per cui ciò non è risolvibile è che né l’API Unix né l’API del filesystem Windows offrono alcun meccanismo che garantisca di dire se due percorsi assoluti si riferiscono allo stesso file. Se hai l’impressione che i numeri inode possano essere usati per quello, scusa, ti sbagli.)

(Nota storica: l’unica ragione per cui non ho strappato #pragma once e #import da GCC quando ho avuto l’autorità di farlo, ~ 12 anni fa, le intestazioni di sistema di Apple si basavano su di esse. In retrospettiva, questo non dovrebbe mi hanno fermato.)

(Dal momento che questo è apparso due volte nella discussione dei commenti: gli sviluppatori di GCC hanno fatto un bel po ‘di sforzi per rendere #pragma once più affidabile ansible, vedi il bug report GCC 11569. Tuttavia, l’implementazione nelle attuali versioni di GCC può ancora fallire in condizioni plausibili, come le fattorie di costruzione che soffrono di disallineamento dell’orologio. Non so come sia l’implementazione di qualsiasi altro compilatore, ma non mi aspetto che nessuno abbia fatto di meglio .)

Fino al giorno in cui #pragma once diventa standard (non è attualmente una priorità per gli standard futuri), ti suggerisco di usarlo E usare le guardie, in questo modo:

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

Le ragioni sono:

  • #pragma once non è standard, quindi è ansible che alcuni compilatori non forniscano la funzionalità. Detto questo, tutti i principali compilatori lo supportano. Se un compilatore non lo sa, almeno sarà ignorato.
  • Poiché non esiste un comportamento standard per #pragma once , non si deve assumere che il comportamento sarà lo stesso su tutto il compilatore. Le guardie assicureranno almeno che l’assunto di base sia lo stesso per tutti i compilatori che implementano almeno le istruzioni del preprocessore necessarie per le guardie.
  • Sulla maggior parte dei compilatori, #pragma once velocizzerà la compilazione (di un cpp) perché il compilatore non riaprirà il file contenente questa istruzione. Quindi averlo in un file potrebbe essere di aiuto, o meno, a seconda del compilatore. Ho sentito che g ++ può fare la stessa ottimizzazione quando vengono rilevate protezioni, ma deve essere confermata.

Usando i due insieme ottieni il meglio da ogni compilatore per questo.

Ora, se non hai uno script automatico per generare le guardie, potrebbe essere più comodo usare #pragma once . Basta sapere cosa significa per codice portatile. (Sto usando VAssistX per generare le guardie e il pragma una volta in fretta)

Dovresti quasi sempre pensare il tuo codice in modo portabile (perché non sai di cosa sia fatto il futuro) ma se pensi davvero che non è pensato per essere compilato con un altro compilatore (ad esempio codice per hardware embedded molto specifico) quindi dovresti semplicemente controllare la documentazione del compilatore su #pragma once per sapere cosa stai facendo veramente.

Dal punto di vista del tester del software

#pragma once è più breve di una guardia di inclusione, meno incline agli errori, supportata dalla maggior parte dei compilatori e alcuni dicono che compila più velocemente (il che non è vero [più lungo]).

Ma ti suggerisco ancora di andare con le guardie standard di #ifndef .

Perché #ifndef ?

Considera una gerarchia di classi artificiosa come questa in cui ciascuna delle classi A , B e C vive all’interno del proprio file:

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 

Supponiamo ora che tu stia scrivendo dei test per le tue classi e devi simulare il comportamento della class B veramente complessa. Un modo per farlo sarebbe quello di scrivere una class di simulazione usando ad esempio google mock e metterlo in una directory mocks/bh . Nota che il nome della class non è cambiato ma è solo memorizzato in una directory diversa. Ma la cosa più importante è che la guardia include è chiamata esattamente come nel file originale bh .

schernisce / 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 

Qual è il vantaggio?

Con questo approccio puoi prendere in giro il comportamento della class B senza toccare la class originale o dirlo a C Tutto quello che devi fare è mettere i gestori delle directory mocks/ nel percorso di inclusione del tuo compilatore.

Perché non può essere fatto con #pragma once ?

Se avessi usato #pragma once , avresti ottenuto un conflitto di nomi perché non può proteggerti dal definire la class B due volte, una volta quella originale e una volta la versione derisa.

Se sei sicuro che non userai mai questo codice in un compilatore che non lo supporta (Windows / VS, GCC e Clang sono esempi di compilatori che lo supportano), allora puoi sicuramente usare #pragma una volta senza preoccupazioni .

Puoi anche usare entrambi (vedi esempio sotto), in modo da ottenere portabilità e accelerazione della compilazione su sistemi compatibili

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

Generalmente non mi occupo di #pragma once il mio codice a volte deve compilare qualcosa di diverso da MSVC o GCC (i compilatori per i sistemi incorporati non hanno sempre #pragma).

Quindi devo usare #include guardie comunque. Potrei anche usare #pragma once come suggeriscono alcune risposte, ma non sembra esserci molta ragione e spesso causerà inutili avvertenze sui compilatori che non la supportano.

Non sono sicuro di quanto tempo possa risparmiare il pragma. Ho sentito che i compilatori generalmente riconoscono già quando un’intestazione non ha altro che commenti al di fuori delle macro di guardia e farà il #pragma once equivalente in quel caso (cioè, non elaborerà mai più il file). Ma non sono sicuro se sia vero o solo un caso di compilatori potrebbe fare questa ottimizzazione.

In entrambi i casi, è più facile per me usare #include guardie che funzioneranno ovunque e non ti preoccupare ulteriormente.

Dopo aver intrapreso una discussione estesa sul presunto compromesso di prestazioni tra le guardie di #pragma once e #ifndef contro l’argomento della correttezza o meno (stavo prendendo il lato di #pragma once basato su un indoctrinemento relativamente recente a tale scopo), ho deciso per testare finalmente la teoria che #pragma once più veloce perché il compilatore non deve cercare di #include un file che è già stato incluso.

Per il test, ho generato automaticamente 500 file di intestazione con interdipendenze complesse e disponevo di un file .c che #include tutti. Ho eseguito il test in tre modi, una volta con #ifndef , una volta con #pragma once volta e una volta con entrambi. Ho eseguito il test su un sistema abbastanza moderno (un MacBook Pro 2014 con OSX in esecuzione, utilizzando Clan in dotazione di XCode, con l’SSD interno).

Innanzitutto, il codice di prova:

 #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; } 

E ora, i miei vari test funzionano:

 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 

Come puoi vedere, le versioni con #pragma once erano effettivamente leggermente più veloci rispetto al #ifndef -solo, ma la differenza era del tutto trascurabile e sarebbe stata oscurata dalla quantità di tempo che la creazione e il collegamento del codice avrebbero effettivamente prendere. Forse con una base di codice abbastanza grande potrebbe effettivamente portare a una differenza nei tempi di compilazione di pochi secondi, ma tra i moderni compilatori che sono in grado di ottimizzare le guardie #ifndef , il fatto che i sistemi operativi abbiano una buona cache del disco e le crescenti velocità della tecnologia di archiviazione, sembra che l'argomento delle prestazioni sia discutibile, almeno su un tipico sistema di sviluppo in questo giorno ed età. Ambienti di build più vecchi e più esotici (ad esempio intestazioni ospitate su una condivisione di rete, creazione di nastri, ecc.) Possono modificare l'equazione, ma in tali circostanze sembra più utile semplicemente creare un ambiente di compilazione meno fragile in primo luogo.

#ifndef è standardizzato con un comportamento standard mentre #pragma once non lo è, e #ifndef gestisce anche casi strani di casi di file system e di percorsi di ricerca mentre #pragma once può diventare molto confuso da certe cose, portando a comportamenti scorretti che il programmatore non ha controllo. Il problema principale con #ifndef sono i programmatori che scelgono i brutti nomi per le loro guardie (con collisioni di nomi e così via) e anche allora è abbastanza probabile che l'utente di un'API ignori quei nomi poveri usando #undef - non una soluzione perfetta, forse, ma è ansible , mentre #pragma once non ha alcun ricorso se il compilatore sbaglia erroneamente un #include .

Quindi, anche se #pragma once è dimostrabile (leggermente) più veloce, non sono d'accordo che ciò sia di per sé un motivo per usarlo sulle guardie di #ifndef .

EDIT : Grazie al feedback di @LightnessRacesInOrbit ho aumentato il numero di file di intestazione e modificato il test per eseguire solo la fase del preprocessore, eliminando qualsiasi piccola quantità di tempo veniva aggiunta dal processo di compilazione e collegamento (che era banale prima e inesistente ora). Come previsto, il differenziale è circa lo stesso.

Penso che la prima cosa che dovresti fare sia controllare se questo farà davvero la differenza, ad es. dovresti prima testare la performance. Una delle ricerche in google ha gettato questo .

Nella pagina dei risultati, le colonne sono leggermente al di fuori per me, ma è chiaro che almeno fino a VC6 microsoft non implementava le ottimizzazioni di include guard che gli altri strumenti stavano usando. Dove la guardia inclusa era interna, ci sono voluti 50 volte più a lungo rispetto a dove la guardia include era esterna (le guardie esterne incluse sono almeno buone come #pragma). Ma consideriamo il ansible effetto di questo:

Secondo le tabelle presentate, il tempo di aprire l’inclusione e di controllarlo è 50 volte quello di un equivalente #pragma. Ma il tempo reale per farlo è stato misurato a 1 microsecondo per file nel 1999!

Quindi, quante intestazioni duplicate avrà una TU singola? Questo dipende dal tuo stile, ma se diciamo che una TU media ha 100 duplicati, nel 1999 pagheremo potenzialmente 100 microsecondi per TU. Con i miglioramenti dell’HDD, questo è probabilmente molto più basso ormai, ma anche in questo caso con intestazioni precompilate e tracciamento della dipendenza corretto il costo totale cumulativo di questo per un progetto è quasi certamente una parte insignificante del tempo di costruzione.

Ora, il rovescio della medaglia, per quanto improbabile, se mai si passa a un compilatore che non supporta #pragma once quindi considera quanto tempo ci vorrà per aggiornare l’intera base di origine di avere guardie piuttosto che # pragma?

Non c’è motivo per cui Microsoft non possa implementare l’ottimizzazione di include guard allo stesso modo in cui GCC e ogni altro compilatore (in realtà qualcuno può confermare se le loro versioni più recenti lo implementano?). IMHO, #pragma once fa ben poco oltre a limitare la scelta del compilatore alternativo.

C’è una domanda correlata a cui ho risposto :

#pragma once ha uno svantaggio (oltre ad essere non standard) e cioè se si ha lo stesso file in posizioni diverse (questo perché il nostro sistema di compilazione copia i file in giro), il compilatore penserà che questi siano file diversi.

Sto aggiungendo la risposta anche qui nel caso qualcuno inciampi su questa domanda e non sull’altra.

#pragma once consente al compilatore di saltare completamente il file quando si verifica di nuovo, invece di analizzare il file fino a quando non raggiunge le #include guards.

In quanto tali, la semantica è leggermente diversa, ma sono identiche se sono usate nel modo in cui sono destinate ad essere utilizzate.

Combinare entrambi è probabilmente la strada più sicura da percorrere, come nel caso peggiore (un compilatore che segnala i pragma sconosciuti come errori effettivi, non solo avvertimenti) si dovrebbe semplicemente rimuovere il # pragma stesso.

Quando limiti le tue piattaforms a dire “compilatori mainstream sul desktop”, puoi tranquillamente omettere le guardie #include, ma mi sento a disagio anche su questo.

OT: se hai altri suggerimenti / esperienze da condividere sull’accelerazione delle build, sarei curioso.

Per coloro che vorrebbero utilizzare #pragma una volta e includere le guardie insieme: se non si utilizza MSVC, non si otterrà molto l’ottimizzazione da #pragma una volta.

E non dovresti mettere “#pragma once” in un header che dovrebbe essere incluso più volte con ogni inclusione che potrebbe avere un effetto diverso.

Ecco una discussione dettagliata con esempi su #pragma una volta utilizzato.

In cima alla spiegazione di Konrad Kleine sopra.

Un breve riassunto:

  • quando usiamo # pragma once che è la responsabilità del compilatore, non consentirne l’inclusione più di una volta. Il che significa che, dopo aver menzionato lo snippet di codice nel file, non è più una tua responsabilità.

Ora, il compilatore sembra, per questo frammento di codice all’inizio del file, e lo ignora dall’essere incluso (se già incluso una volta). 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, compiler will just flag some ” undefined .. ” error messages at compile 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.