2014-12-02 10 views
7

Io uso #pragma once (o si utilizzano le protezioni includi alla #ifndef...) fondamentalmente in ogni file di intestazione nei miei progetti C++. È una coincidenza o è ciò che trovi nella maggior parte dei progetti open source, ad esempio (per evitare risposte che si basano su solo sull'esperienza personale del progetto). Se è così, perché non è il contrario: se voglio che un file di intestazione venga incluso più volte, io uso lo stesso comando speciale per il pre-processore e, in caso contrario, lascio il file così com'è.Perché non sono incluse le protezioni in C++ come predefinite?

+8

'#pragma once' non è standard; '# ifndef/# define/# endif' è. –

+3

@ T.C. Ok. Ma la domanda rimane fondamentalmente la stessa. – NOhs

+0

Si potrebbe dare un'occhiata a '' (e ''). Due intestazioni che verrebbero azzoppate se "#pragma once" o simile fosse automatico. (Anche se sarebbe comunque contrario a rendere la cosa normale facile e breve senza rendere il modo non comune impossibile. Voglio dire, si potrebbe includere '#pragma redo' o simili.) – Deduplicator

risposta

7

Il comportamento di un compilatore C++ è specificato in termini di come gestisce ciascuna unità di traduzione. Un'unità di traduzione è un singolo file dopo aver eseguito il preprocessore su di esso. Il fatto che abbiamo una convenzione per raccogliere dichiarazioni in determinati file e chiamarli file "header" non significa nulla per un compilatore o lo standard C++.

In poche parole, lo standard non fornisce "file di intestazione" in modo che non possa fornire automaticamente la protezione dei file di intestazione. Lo standard fornisce solo la direttiva preprocessore #include e il resto è solo convenzione. Nulla ti impedisce di inoltrare tutto e non usare i file di intestazione (eccetto la pietà per chiunque debba mantenere quel codice ...).

Quindi i file di intestazione non sono speciali e non c'è modo di dire "quello è un file di intestazione, guardarlo", ma perché non possiamo proteggere tutto ciò che ottiene #include 'd? Perché #include è più e meno potente di un sistema di moduli come gli altri linguaggi. #include fa sì che il preprocessore si incolli in altri file, non necessariamente nei file di intestazione. A volte questo può essere utile se si hanno le stesse dichiarazioni di uso e typedef in un gruppo di namespace diversi in file diversi. Potresti raccoglierli in un file e #include in pochi posti. Non vorrai che le protezioni automatiche includano le protezioni che ti impediscono di farlo.

L'utilizzo di #ifndef e #define per includere condizionalmente le intestazioni è puramente convenzione. Lo standard non ha alcun concetto di "include guard". (Compilatori moderni ma in realtà sono a conoscenza di includere guardie. Riconoscendo includere guardie possono consentire la compilazione più veloce ma non ha nulla a che fare con la corretta attuazione della norma.)

Ottenere pedante

Lo standard non liberalmente usa la parola "header", in particolare in riferimento alle librerie standard C e C++. Tuttavia, il comportamento di #include è definito in § 16.2 *Source file* inclusion (emph. Mine) e non concede poteri speciali ai file di intestazione.

Ci sono sforzi per ottenere un adeguato sistema di moduli nello standard C++.

+1

C'è "header" e c'è "source file". "header" non ha bisogno di essere file reali. –

+0

Davvero? Dillo. È perché lo standard consente che le cose in <> vengano fornite da qualsiasi mezzo definito dall'implementazione? – Praxeolitic

+4

Destra (o non specificata, piuttosto che l'implementazione definita). Tutto ciò che è richiesto è che scrivendo "#include " faccia vedere le dichiarazioni e le definizioni appropriate. Il compilatore potrebbe avere una gestione speciale per riconoscere i nomi di intestazione standard e avere il loro contenuto codificato in esso, oppure potrebbe recuperare una porzione di dati AST da un database di oggetti ...oppure potrebbe semplicemente ottenere il preprocessore per includere testualmente un file (che è ciò che tutte le implementazioni comuni fanno in pratica). –

4

Perché uvetta isterica.

Il preprocessore C++ è quasi identico al preprocessore C, progettato oltre 40 anni fa. I compilatori allora erano molto più semplici. Il preprocessore era ancora più semplice, solo un microprocessore non era nemmeno un compilatore. Sebbene lo standard C++ non specifichi come funziona per le intestazioni standard, concettualmente lo #include è sempre lo stesso di oltre 40 anni fa: fa in modo che il preprocessore inserisca il contenuto del file indicato nel file incluso.

In un semplice codebase degli anni '70 senza molte interdipendenze e sottomoduli, potrebbe non esserci alcuna necessità di includere protezioni. La precoce pre-standard C non utilizzava i prototipi di funzione, che è ciò che la maggior parte dei file include vengono utilizzati per questi giorni. Se l'inclusione di un'intestazione due volte ha causato un errore, probabilmente potresti riordinare il codice per evitare che venga incluso due volte.

Man mano che la base di codice cresceva e diventava più complessa, presumo che il problema di includere accidentalmente un'intestazione due volte (probabilmente indirettamente, tramite altre intestazioni) diventasse più comune. Una soluzione sarebbe stata quella di alterare il preprocessore per renderlo più intelligente, ma questo avrebbe richiesto a tutti di usare il nuovo preprocessore e lo avrebbe reso più grande e più lento. Quindi, è stata sviluppata una convenzione che risolve il problema utilizzando le funzioni esistenti del preprocessore (#define e #ifndef), non impedendo l'inclusione di un'intestazione due volte, ma semplicemente rendendo innocuo includere un'intestazione due volte, perché non ha alcun effetto dopo il primo ora è inclusa.

Nel corso del tempo la convenzione è diventato più ampiamente usato e ora è quasi universale, fatta eccezione per i rari esempi di intestazioni che sono progettati per essere inclusi due volte e scritti per funzionare correttamente in questo modo (per esempio <assert.h>).

Più tardi ancora, #pragma once è stato introdotto da alcuni compilatori come alternativa, modo non portabile per avere lo stesso effetto di includere le guardie, ma a quel punto c'erano molte migliaia di copie di vari preprocessori C in uso intorno alla parola e comprendono le guardie erano diventate la norma.

Quindi il comportamento corrente è quasi certamente dovuto a ragioni storiche. I moderni linguaggi scritti oggi, per i computer incredibilmente potenti di oggi, non userebbero qualcosa come il preprocessore C se progettato da zero. Ma C non è stato progettato nel 21 ° secolo. Penso che la convenzione sulla protezione delle inclusioni sia stata stabilita lentamente nel tempo e non abbia richiesto modifiche al software esistente per farlo funzionare. Cambiandolo ora si rompono quantità sconosciute di codice C e C++ che si basano sul comportamento corrente e probabilmente non sono un'opzione, poiché la compatibilità con le versioni precedenti è importante sia per C che per C++.

0

Dovrei essere d'accordo sul fatto che il fattore principale è storico, che di tanto in tanto si vede il codice che si basa su di loro non essere lì. MAME è un esempio: costruisce strutture di dati complesse (o almeno l'ultima volta che ho guardato, qualche tempo fa) da un file basato su macro leggibile includendolo più volte con le macro definite in modo diverso. Se le guardie incluse fossero automatiche, ti verrebbe in mente un codice che richiederebbe un modo per disattivarle.

Problemi correlati