#Pragma è una volta sicuro includere la guardia?


311

Ho letto che c'è qualche ottimizzazione del compilatore durante l'utilizzo #pragma onceche può portare a una compilazione più veloce. Riconosco che non è standard e quindi potrebbe rappresentare un problema di compatibilità multipiattaforma.

È qualcosa che è supportato dalla maggior parte dei compilatori moderni su piattaforme non Windows (gcc)?

Voglio evitare problemi di compilazione della piattaforma, ma voglio anche evitare il lavoro extra delle protezioni di fallback:

#pragma once
#ifndef HEADER_H
#define HEADER_H

...

#endif // HEADER_H

Dovrei essere preoccupato? Dovrei spendere ulteriore energia mentale su questo?


3
Dopo aver posto una domanda simile , ho scoperto che #pragma oncesembra evitare alcuni problemi di vista di classe in VS 2008. Sono in procinto di sbarazzarmi delle guardie di inclusione e sostituirle tutte con #pragma oncequesto motivo.
SmacL

Risposte:


189

L'utilizzo #pragma oncedovrebbe funzionare su qualsiasi compilatore moderno, ma non vedo alcun motivo per non utilizzare una #ifndefprotezione include standard . Funziona benissimo. L'unica avvertenza è che GCC non supportava #pragma onceprima della versione 3.4 .

Ho anche scoperto che, almeno su GCC, riconosce lo standard #ifndefinclude guard e lo ottimizza , quindi non dovrebbe essere molto più lento di #pragma once.


12
Non dovrebbe essere affatto più lento (con GCC comunque).
Jason Coco,

54
Non è implementato in questo modo. Invece, se il file inizia con un #ifndef la prima volta e termina con un #endif, gcc lo ricorda e salta sempre quello che include in futuro senza nemmeno preoccuparsi di aprire il file.
Jason Coco,

10
#pragma onceè generalmente più veloce perché il file non viene preelaborato. ifndef/define/endifrichiede comunque la preelaborazione, perché dopo questo blocco puoi avere qualcosa di compilabile (teoricamente)
Andrey

13
Documenti GCC sull'ottimizzazione delle macro di guardia: gcc.gnu.org/onlinedocs/cppinternals/Guard-Macros.html
Adrian

38
Per utilizzare include guards, vi è il requisito aggiuntivo che è necessario definire un nuovo simbolo come #ifndef FOO_BAR_H, normalmente, per un file come "foo_bar.h". Se in seguito rinominerai questo file, dovresti adattare le protezioni di inclusione di conseguenza per essere coerenti con questa convenzione? Inoltre, se hai due distinti foo_bar.h in due posti diversi nella tua struttura di codice, devi pensare a due simboli diversi per ognuno. La risposta breve è usare #pragma oncee se hai davvero bisogno di compilare in un ambiente che non lo supporta, vai avanti e aggiungi protezioni incluse per quell'ambiente.
Brandin,

329

#pragma once ha uno svantaggio (oltre ad essere non standard) e cioè se hai lo stesso file in posizioni diverse (lo abbiamo perché il nostro sistema di build copia i file) quindi il compilatore penserà che si tratti di file diversi.


36
Ma puoi anche avere due file con lo stesso nome in posizioni diverse senza doversi preoccupare di creare diversi #define NAMES, che sono formati sotto forma di HEADERFILENAME_H
Vargas,

69
Puoi anche avere due o più file con lo stesso #define WHATEVER che non ha fine al divertimento, motivo per cui preferirei usare pragma una volta.
Chris Huang-Leaver,

107
Non persuasivo ... Cambia il sistema di generazione in uno che non copi i file ma usi invece i collegamenti simbolici o includa lo stesso file solo da una posizione in ogni unità di traduzione. Sembra più che la tua infrastruttura sia un casino che deve essere riorganizzato.
Yakov Galka,

3
E se hai file diversi con lo stesso nome in directory diverse, l'approccio #ifdef penserà che siano lo stesso file. Quindi c'è uno svantaggio per uno, e c'è uno svantaggio per l'altro.
rxantos,

3
@rxantos, se i file differiscono, anche il #ifdefvalore della macro può differire.
Motti,

63

Vorrei #pragma once(o qualcosa del genere) fosse stato nello standard. Includere le guardie non è un grosso problema (ma sembrano essere un po 'difficili da spiegare alle persone che imparano la lingua), ma sembra un piccolo fastidio che avrebbe potuto essere evitato.

In effetti, dal 99,98% delle volte, il #pragma oncecomportamento è il comportamento desiderato, sarebbe stato bello se la prevenzione dell'inclusione multipla di un'intestazione fosse gestita automaticamente dal compilatore, con un #pragmao qualcosa per consentire il doppio inclusione.

Ma abbiamo quello che abbiamo (tranne che potresti non averlo #pragma once).


48
Quello che voglio davvero è una #importdirettiva standard .
Giovanni,

10
Sta arrivando una direttiva standard sulle importazioni: isocpp.org/blog/2012/11/… Ma non ancora qui. Ne sono fortemente favorevole.
AHelps

7
@AHelps Vaporware. Sono passati quasi cinque anni. Forse nel 2023 tornerai a questo commento e dirai "Te l'ho detto".
Doug65536,

Non è vaporware, ma è solo nella fase delle specifiche tecniche. I moduli sono implementati in Visual Studio 2015 ( blogs.msdn.microsoft.com/vcblog/2015/12/03/… ) e in clang ( clang.llvm.org/docs/Modules.html ). Ed è import, non #import.
AHelps

Dovrebbe farlo in C ++ 20.
Ionoclast Brigham,

36

Non conosco alcun vantaggio in termini di prestazioni, ma sicuramente funziona. Lo uso in tutti i miei progetti C ++ (ammesso che sto usando il compilatore MS). Trovo che sia più efficace dell'uso

#ifndef HEADERNAME_H
#define HEADERNAME_H
...
#endif

Fa lo stesso lavoro e non popola il preprocessore con macro aggiuntive.

GCC supporta #pragma onceufficialmente dalla versione 3.4 .


25

GCC supporta #pragma oncedal 3.4, vedi http://en.wikipedia.org/wiki/Pragma_once per ulteriore supporto del compilatore.

Il grande vantaggio che vedo sull'uso #pragma onceinvece di includere guardie è di evitare errori di copia / incolla.

Ammettiamolo: la maggior parte di noi difficilmente avvia un nuovo file di intestazione da zero, ma piuttosto ne copia uno esistente e lo modifica alle nostre esigenze. È molto più semplice creare un modello di lavoro usando #pragma onceinvece di includere protezioni. Meno devo modificare il modello, meno è probabile che si verifichino errori. Avere la stessa funzione include guard in file diversi porta a strani errori del compilatore e ci vuole del tempo per capire cosa è andato storto.

TL; DR: #pragma onceè più facile da usare.


11

Lo uso e ne sono contento, poiché devo digitare molto meno per creare una nuova intestazione. Ha funzionato bene per me su tre piattaforme: Windows, Mac e Linux.

Non ho alcuna informazione sulle prestazioni ma credo che la differenza tra #pragma e include guard non sarà nulla in confronto alla lentezza dell'analisi della grammatica C ++. Questo è il vero problema. Prova a compilare lo stesso numero di file e linee con un compilatore C # per esempio, per vedere la differenza.

Alla fine, usando la guardia o il pragma, non importa affatto.


Non mi piace #pragma una volta, ma apprezzo che tu sottolinei i relativi vantaggi ... L'analisi in C ++ è molto più costosa di qualsiasi altra cosa, in un ambiente operativo "normale". Nessuno compila da un filesystem remoto se i tempi di compilazione sono un problema.
Tom

1
Re C ++ che analizza la lentezza rispetto a C #. In C # non è necessario analizzare (letteralmente) migliaia di LOC di file di intestazione (iostream, chiunque?) Per ogni piccolo file C ++. Utilizza le intestazioni precompilate per ridurre questo problema, tuttavia
Eli Bendersky,

11

L'uso di " #pragma once" potrebbe non avere alcun effetto (non è supportato ovunque, anche se è sempre più ampiamente supportato), quindi è necessario utilizzare comunque il codice di compilazione condizionale, nel qual caso, perché preoccuparsi di " #pragma once"? Il compilatore probabilmente lo ottimizza comunque. Tuttavia, dipende dalle piattaforme di destinazione. Se tutti i tuoi bersagli lo supportano, vai avanti e usalo, ma dovrebbe essere una decisione consapevole perché si scatenerà l'inferno se usi solo il pragma e quindi esegui il port su un compilatore che non lo supporta.


1
Non sono d'accordo sul fatto che tu debba supportare comunque le guardie. Se usi #pragma una volta (o guardie) questo è perché genera alcuni conflitti senza di loro. Quindi se non è supportato dal tuo strumento a catena il progetto non si compila e tu sei esattamente nello stesso tipo di situazione rispetto a quando vuoi compilare un po 'di C in un vecchio compilatore K&R. Devi solo ottenere un chaintool più aggiornato o modificare il codice per aggiungere alcune protezioni. Tutto l'inferno sarebbe se il programma fosse compilato ma non funzionasse.
Kriss,

5

Il vantaggio in termini di prestazioni è di non dover riaprire il file dopo aver letto #pragma. Con guards, il compilatore deve aprire il file (che può essere costoso in tempo) per ottenere le informazioni che non dovrebbe includere di nuovo il suo contenuto.

Questa è teoria solo perché alcuni compilatori non apriranno automaticamente i file per i quali non è stato letto alcun codice di lettura, per ciascuna unità di compilazione.

Comunque, non è il caso di tutti i compilatori, quindi idealmente #pragma deve essere evitato una volta che il codice multipiattaforma non è affatto standard / non ha definizione ed effetto standardizzati. Tuttavia, praticamente, è davvero meglio delle guardie.

Alla fine, il miglior suggerimento che puoi ottenere per essere sicuro di avere la migliore velocità dal tuo compilatore senza dover controllare il comportamento di ciascun compilatore in questo caso, è usare sia pragma una volta che guardie.

#ifndef NR_TEST_H
#define NR_TEST_H
#pragma once

#include "Thing.h"

namespace MyApp
{
 // ...
}

#endif

In questo modo ottieni il meglio da entrambi (multipiattaforma e aiuto nella velocità di compilazione).

Poiché è più lungo da digitare, uso personalmente uno strumento per aiutare a generare tutto ciò in modo molto malvagio (Visual Assist X).


Visual Studio non ottimizza le protezioni #include com'è? Un altro compilatore (migliore?) Lo fa, e immagino sia abbastanza semplice.
Tom

1
Perché metti il pragmadopo il ifndef? C'è un vantaggio?
user1095108,

1
@ user1095108 Alcuni compilatori useranno le protezioni dell'intestazione come delimitatore per sapere se il file contiene solo codice che deve essere istanziato una volta. Se un po 'di codice è al di fuori delle protezioni delle intestazioni, l'intero file potrebbe essere considerato forse istante più di una volta. Se quello stesso compilatore non supporta il pragma una volta, ignorerà quell'istruzione. Pertanto, inserire il pragma una volta all'interno delle protezioni delle testate è il modo più generico per assicurarsi che almeno le protezioni delle testate possano essere "ottimizzate".
Klaim

4

Non sempre.

http://gcc.gnu.org/bugzilla/show_bug.cgi?id=52566 ha un bell'esempio di due file pensati per essere inclusi entrambi, ma erroneamente ritenuti identici a causa di timestamp e contenuto identici (non identico nome del file) .


10
Sarebbe un bug nel compilatore. (provare a prendere una scorciatoia non dovrebbe richiedere).
rxantos,

4
#pragma oncenon è standard, quindi qualsiasi cosa un compilatore decida di fare è "corretta". Certo, allora possiamo iniziare a parlare di ciò che è "previsto" e di ciò che è "utile".
user7610

2

Usando gcc 3.4 e 4.1 su alberi molto grandi (a volte usando distcc ), devo ancora vedere una maggiore velocità quando uso #pragma una volta al posto di, o in combinazione con le protezioni standard include.

Davvero non vedo quanto valga la pena confondere le versioni precedenti di gcc, o anche altri compilatori poiché non ci sono risparmi reali. Non ho provato tutti i vari de-linters, ma sono disposto a scommettere che confonderà molti di loro.

Anch'io vorrei che fosse stato adottato all'inizio, ma posso vedere l'argomento "Perché ne abbiamo bisogno quando ifndef funziona perfettamente?". Dati i molti angoli oscuri e le complessità di C, le guardie sono una delle cose più semplici e autoesplicative. Se hai anche una piccola conoscenza di come funziona il preprocessore, dovrebbero essere autoesplicativi.

Se si osserva una notevole accelerazione, tuttavia, si prega di aggiornare la domanda.


2

Oggi la vecchia scuola include che le guardie sono veloci quanto una #pragma una volta. Anche se il compilatore non li tratta in modo speciale, si fermerà comunque quando vedrà #ifndef WHATEVER e WHATEVER definiti. L'apertura di un file è poco costoso oggi. Anche se ci fosse un miglioramento, sarebbe nell'ordine dei millisecondi.

Semplicemente non uso #pragma una volta, poiché non ha alcun vantaggio. Per evitare di scontrarmi con altre guardie di inclusione uso qualcosa del tipo: CI_APP_MODULE_FILE_H -> CI = Iniziali dell'azienda; APP = Nome dell'applicazione; il resto si spiega da sé.


19
Il vantaggio non è molto meno di digitare?
Andrey,

1
Si noti che alcuni millisecondi centomila volte sono alcuni minuti, però. I grandi progetti consistono in diecimila file, tra cui decine di intestazioni ciascuno. Date le attuali CPU multi-core, l'input / output, in particolare l'apertura di molti file di piccole dimensioni , è uno dei maggiori colli di bottiglia.
Damon,

1
"Oggi le guardie della vecchia scuola includono una volta la #pragma". Oggi e anche molti anni fa. I documenti più vecchi sul sito GCC sono per 2,95 dal 2001 e l'ottimizzazione delle guardie non era nuova allora. Non è un'ottimizzazione recente.
Jonathan Wakely,

4
Il vantaggio principale è che includere le protezioni sono soggette a errori e prolisso. È troppo facile avere due file diversi con nomi identici in directory diverse (e le guardie di inclusione potrebbero essere lo stesso simbolo) o fare errori di copia-incolla durante la copia delle guardie di inclusione. Pragma una volta è meno soggetto a errori e funziona su tutte le principali piattaforme PC. Se puoi usarlo, è uno stile migliore.
AHelps

2

La differenza principale è che il compilatore ha dovuto aprire il file header per leggere la protezione include. In confronto, una volta pragma fa sì che il compilatore tenga traccia del file e non esegua alcun IO di file quando incontra un altro include per lo stesso file. Anche se può sembrare trascurabile, può facilmente espandersi con grandi progetti, specialmente quelli senza una buona intestazione includono discipline.

Detto questo, in questi giorni i compilatori (incluso GCC) sono abbastanza intelligenti da trattare una volta le guardie come il pragma. cioè non aprono il file ed evitano la penalità IO del file.

Nei compilatori che non supportano il pragma ho visto implementazioni manuali un po 'ingombranti ..

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

Personalmente mi piace l'approccio #pragma una volta che evita il fastidio di nominare collisioni e potenziali errori di battitura. È anche un codice più elegante al confronto. Detto questo, per il codice portatile, non dovrebbe far male avere entrambi a meno che il compilatore non si lamenta.


1
"Detto questo, in questi giorni i compilatori (incluso GCC) sono abbastanza intelligenti da trattare una volta le guardie come il pragma." Lo hanno fatto per decenni, forse più a lungo di quanto #pragma oncesia esistito!
Jonathan Wakely,

Pensi di avermi frainteso. Volevo dire una volta prima del pragma, tutti i compilatori avrebbero dovuto sostenere più penanti IO per lo stesso file h incluso più volte durante la fase del preprocessore. Le implementazioni moderne finiscono per utilizzare una migliore memorizzazione nella cache dei file nella fase del preprocessore. Indipendentemente da ciò, senza pragmi, la fase del preprocessore finisce comunque includendo tutto ciò che è al di fuori delle guardie di inclusione. Con pragma una volta, l'intero file viene lasciato fuori. Da quel punto di vista, il pragma è ancora vantaggioso.
Shammi,

1
No, è sbagliato, i compilatori decenti lasciano l'intero file anche senza #pragma una volta, non aprono il file una seconda volta e non lo guardano nemmeno una seconda volta, vedi gcc.gnu.org/onlinedocs/ cpp / Once-Only-Headers.html (questo non ha nulla a che fare con la memorizzazione nella cache).
Jonathan Wakely, il

1
Dal tuo link, sembra che l'ottimizzazione avvenga solo in cpp. Indipendentemente da ciò, la cache entra in gioco. In che modo il preprocessore sa di includere il codice al di fuori delle protezioni. Esempio ... extern int foo; #ifndef INC_GUARD #define INC_GUARD class ClassInHeader {}; #endif In questo caso il preprocessore dovrà includere extern int foo; più volte se si include più volte lo stesso file. Alla fine della giornata, non c'è molto da discutere su questo fino a quando comprendiamo la differenza tra #pragma una volta e includiamo le guardie e come i vari compilatori si comportano con entrambi :)
Shammi

1
Non applica l'ottimizzazione in questo, ovviamente.
Jonathan Wakely,

1

Se utilizziamo msvc o Qt (fino a Qt 4.5), poiché GCC (fino a 3.4), entrambi supportano msvc #pragma once, non vedo alcun motivo per non utilizzarlo #pragma once.

Il nome del file sorgente di solito ha lo stesso nome di classe uguale e sappiamo, a volte abbiamo bisogno di refactor , per rinominare il nome della classe, quindi abbiamo dovuto cambiare #include XXXXanche il, quindi penso che il mantenimento manuale #include xxxxxnon sia un lavoro intelligente. anche con l'estensione Visual Assist X, mantenere "xxxx" non è un lavoro necessario.


1

Nota aggiuntiva per le persone che pensano che un'inclusione automatica di una sola volta di file di intestazione sia sempre desiderata: costruisco generatori di codice usando l'inclusione doppia o multipla di file di intestazione da decenni. Soprattutto per la generazione di stub di librerie di protocollo trovo molto comodo avere un generatore di codice estremamente portatile e potente senza strumenti e linguaggi aggiuntivi. Non sono l'unico sviluppatore a utilizzare questo schema come mostrano i blog X-Macros . Ciò non sarebbe possibile senza la protezione automatica mancante.


I modelli C ++ potrebbero risolvere il problema? Raramente trovo alcun bisogno valido per le macro a causa del modo in cui i modelli C ++.
Più chiaro il

1
La nostra esperienza professionale a lungo termine è che l'utilizzo di linguaggio maturo, software e infrastruttura di strumenti continuamente ci offre come fornitori di servizi (sistemi integrati) un enorme vantaggio in termini di produttività e flessibilità. I concorrenti che sviluppano software e stack per sistemi embedded basati su C ++ potrebbero invece trovare alcuni dei loro sviluppatori più felici al lavoro. Ma di solito superiamo più volte il time-to-market, la funzionalità e la flessibilità. Nether sottovaluta i guadagni di produttività dall'uso ripetuto dello stesso strumento. Gli sviluppatori Web invece soffrono di modi per molti framework.
Marcel,

Una nota però: non include guards / # pragma una volta in ogni file header contro il principio DRY stesso. Posso vedere il tuo punto nella X-Macrofunzione, ma non è l'uso principale di include, non dovrebbe essere il contrario come header unguard / # pragma multi se dovessimo rimanere con DRY?
Caiohamamura,

DRY significa "Non ripetere te stesso". Riguarda l'umano. Ciò che la macchina sta facendo, non ha nulla a che fare con quel paradigma. I template C ++ si ripetono molto, i compilatori C lo fanno anche (ad es. Srotolamento di loop) e ogni computer ripete quasi tutto incredibilmente spesso e velocemente.
Marcel
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.