C ++ # include semantica

Questa è una domanda multipla per le stesse istruzioni di pre-elaborazione.

1 – o “”?

Oltre alle informazioni trovate nel MSDN:

#include Directive (C-C ++)

1.a: quali sono le differenze tra le due notazioni?
1.b: Tutti i compilatori li implementano allo stesso modo?
1.c: Quando useresti il ​​, e quando useresti il ​​”” (cioè quali sono i criteri che useresti per usare uno o l’altro per l’inclusione di un header)?

2 – #include {TheProject / TheHeader.hpp} o {TheHeader.hpp}?

Ho visto almeno due modi di scrivere che includono le intestazioni di un progetto. Considerando che hai almeno 4 tipi di intestazioni, ovvero:

  • intestazioni private del tuo progetto?
  • intestazioni del tuo progetto, ma che stanno esportando simboli (e quindi “pubblici”)
  • intestazioni di un altro progetto con cui il modulo si collega
  • intestazioni di un compilatore o di una libreria standard

Per ogni tipo di intestazioni:

2.a: useresti o “”?
2.b: Includeresti con {TheProject / TheHeader.hpp} o solo con {TheHeader.hpp}?

3 – Bonus

3.a: lavori a progetto con fonti e / o intestazioni all’interno di un’organizzazione ad albero (ad esempio, le directory all’interno delle directory, al contrario di “ogni file in una directory”) e quali sono i pro / contro?

Dopo aver letto tutte le risposte, oltre alla documentazione del compilatore, ho deciso di seguire lo standard seguente.

Per tutti i file, siano essi intestazioni di progetto o intestazioni esterne, utilizzare sempre il modello:

#include  

Lo spazio dei nomi ha almeno una directory in profondità, per evitare la collisione.

Ovviamente, questo significa che la directory del progetto in cui sono presenti le intestazioni del progetto deve essere aggiunta come “default include header” anche al makefile.

Il motivo di questa scelta è che ho trovato le seguenti informazioni:

1. Il modello “” include è dipendente dal compilatore

Darò le risposte di seguito

1.a Lo standard

Fonte:

Nella sezione 16.2 Inclusione del file di origine, possiamo leggere che:

Una direttiva di pre-elaborazione del modulo

  #include  new-line 

ricerca una sequenza di luoghi definiti dall’implementazione per un’intestazione identificata univocamente dalla sequenza specificata tra i delimitatori e causa la sostituzione di tale direttiva con l’intero contenuto dell’intestazione. Come vengono specificati i posti o l’intestazione identificata è definita dall’implementazione.

Ciò significa che #include <...> cercherà un file in un modo definito dall’implementazione.

Quindi, il prossimo paragrafo:

Una direttiva di pre-elaborazione del modulo

  #include "q-char-sequence" new-line 

causa la sostituzione di tale direttiva con l’intero contenuto del file di origine identificato dalla sequenza specificata tra i “delimitatori” Il file di origine denominato viene cercato in un modo definito dall’implementazione. Se questa ricerca non è supportata o se la ricerca non riesce , la direttiva viene rielaborata come se leggesse

  #include  new-line 

con la sequenza identica contenuta (compresi i caratteri, se ce ne sono) dalla direttiva originale.

Ciò significa che #include “…” cercherà un file in un modo definito di implementazione e quindi, se il file non viene trovato, farà un’altra ricerca come se fosse stato un #include <...>

La conclusione è che dobbiamo leggere la documentazione dei compilatori.

Si noti che, per qualche ragione, da nessuna parte negli standard viene fatta la differenza tra intestazioni “sistema” o “libreria” o altre intestazioni. L’unica differenza sembra che #include <...> sembra colpire le intestazioni, mentre #include “…” sembra colpire la fonte (almeno, nella dicitura inglese).

1.b Visual C ++:

Fonte:

#include “MyFile.hpp”

Il preprocessore cerca i file include nel seguente ordine:

  1. Nella stessa directory del file che contiene l’istruzione #include.
  2. Nelle directory di tutti i file di inclusione precedentemente aperti nell’ordine inverso in cui sono stati aperti. La ricerca inizia dalla directory del file di inclusione che è stata aperta per ultima e continua attraverso la directory del file di inclusione che è stato aperto per primo.
  3. Lungo il percorso specificato dall’opzione del compilatore / I.
  4. (*) Lungo i percorsi specificati dalla variabile di ambiente INCLUDE o include l’ambiente di sviluppo predefinito.

#include

Il preprocessore cerca i file include nel seguente ordine:

  1. Lungo il percorso specificato dall’opzione del compilatore / I.
  2. (*) Lungo i percorsi specificati dalla variabile di ambiente INCLUDE o include l’ambiente di sviluppo predefinito.

Nota sull’ultimo passaggio

Il documento non è chiaro riguardo al “Lungo i percorsi specificati dalla variabile di ambiente INCLUDE” per entrambi <...> e "..." include. La seguente citazione fa aderire allo standard:

Per includere i file che sono specificati come #include “path-spec”, la ricerca della directory inizia con la directory del file principale e quindi procede attraverso le directory di qualsiasi file nonno. Cioè, la ricerca inizia rispetto alla directory che contiene il file sorgente che contiene la direttiva #include che viene elaborata. Se non ci sono file nonni e il file non è stato trovato, la ricerca continua come se il nome del file fosse racchiuso tra parentesi angolari.

L’ultimo passaggio (contrassegnato da un asterisco) è quindi un’interpretazione dalla lettura dell’intero documento.

1.c g ++

Fonte:

La seguente citazione riassume il processo:

GCC […] cercherà le intestazioni richieste con #include in [directory di sistema] […] Tutte le directory nominate da -I vengono cercate, in ordine da sinistra a destra, prima delle directory predefinite

GCC cerca le intestazioni richieste con # include il “file” prima nella directory contenente il file corrente, quindi nelle directory come specificato dalle opzioni -iquote, quindi negli stessi punti dovrebbe cercare un’intestazione richiesta con parentesi angolari.

#include “MyFile.hpp”

Questa variante viene utilizzata per i file di intestazione del proprio programma. Il preprocessore cerca i file include nel seguente ordine:

  1. Nella stessa directory del file che contiene l’istruzione #include.
  2. Lungo il percorso specificato da ciascuna opzione del compilatore -iquote.
  3. Per quanto riguarda #include

#include

Questa variante viene utilizzata per i file di intestazione di sistema. Il preprocessore cerca i file include nel seguente ordine:

  1. Lungo il percorso specificato da ciascuna opzione -I del compilatore.
  2. All’interno delle directory di sistema.

1.d Oracle / Sun Studio CC

Fonte:

Nota che il testo si contraddice in qualche modo (vedi l’esempio per capire). La frase chiave è la seguente: ” La differenza è che la directory corrente viene cercata solo per i file di intestazione i cui nomi sono racchiusi tra virgolette.

#include “MyFile.hpp”

Questa variante viene utilizzata per i file di intestazione del proprio programma. Il preprocessore cerca i file include nel seguente ordine:

  1. La directory corrente (cioè la directory contenente il file “incluso”)
  2. Le directory nominate con opzioni -I, se presenti
  3. La directory di sistema (es. La directory / usr / include)

#include

Questa variante viene utilizzata per i file di intestazione di sistema. Il preprocessore cerca i file include nel seguente ordine:

  1. Le directory nominate con opzioni -I, se presenti
  2. La directory di sistema (es. La directory / usr / include)

1.e Riferimento del compilatore C / C ++ XL – IBM / AIX

Fonte:

Entrambi i documenti sono intitolati “XL C / C ++ Compiler Reference”. Il primo documento è più vecchio (8.0), ma è più facile da capire. Il secondo è più recente (12.1), ma è un po ‘più difficile da decifrare.

#include “MyFile.hpp”

Questa variante viene utilizzata per i file di intestazione del proprio programma. Il preprocessore cerca i file include nel seguente ordine:

  1. La directory corrente (cioè la directory contenente il file “incluso”)
  2. Le directory nominate con opzioni -I, se presenti
  3. La directory di sistema (ad es. / / Usr / vac [cpp] / include o / usr / include le directory)

#include

Questa variante viene utilizzata per i file di intestazione di sistema. Il preprocessore cerca i file include nel seguente ordine:

  1. Le directory nominate con opzioni -I, se presenti
  2. La directory di sistema (es. La directory / usr / vac [cpp] / include o / usr / include)

1.e Conclusione

Il pattern “” potrebbe portare a un errore di compilazione sottile tra i compilatori e, poiché attualmente lavoro sia su Windows Visual C ++, Linux g ++, Oracle / Solaris CC e AIX XL, questo non è accettabile.

Ad ogni modo, il vantaggio delle funzionalità “” descritte è tutt’altro che interessante, quindi …

2. Utilizzare il modello {namespace} /header.hpp

Ho visto al lavoro ( cioè questa non è teoria, questa è un’esperienza professionale dolorosa nella vita reale ) due intestazioni con lo stesso nome, una nella directory del progetto locale e l’altra nella sezione globale.

Dato che stavamo usando il pattern “” e quel file era incluso sia nelle intestazioni locali che nelle intestazioni globali, non c’era modo di capire cosa stesse realmente accadendo, quando apparivano strani errori.

L’utilizzo della directory nell’inclusione ci avrebbe permesso di risparmiare tempo perché l’utente avrebbe dovuto scrivere:

 #include  

o

 #include  

Noterai che mentre

 #include "Header.hpp" 

avrebbe compilato con successo, quindi, nascondendo ancora il problema, mentre

 #include  

non avrei compilato in circostanze normali.

Pertanto, attenersi alla notazione <> avrebbe reso obbligatorio per lo sviluppatore il prefisso dell’inserimento con la directory giusta, un’altra ragione per preferire <> a “”.

3. Conclusione

Usando insieme la notazione <> e la notazione con namespace rimuove dal pre-compilatore la possibilità di indovinare i file, cercando invece solo le directory di inclusione predefinite.

Naturalmente, le librerie standard sono ancora incluse come al solito, cioè:

 #include  #include  

Io di solito uso <> per le intestazioni di sistema e “” per le intestazioni di progetto. Per quanto riguarda i percorsi, è necessario solo se il file desiderato si trova in una sottodirectory di un percorso di inclusione.

per esempio, se hai bisogno di un file in / usr / include / SDL /, ma solo / usr / include / è nel tuo percorso di inclusione, allora potresti semplicemente usare:

 #include  

Inoltre, tieni presente che, a meno che il percorso che hai messo inizi con un /, sia relativo alla directory di lavoro corrente.

MODIFICA PER RISPONDERE AL COMMENTO: Dipende, se ci sono solo alcune include per una libreria, vorrei solo includere la sua sottodirectory nel percorso di inclusione, ma se la libreria ha molte intestazioni (come dozzine) allora preferirei avere solo in una sottodir che specifichi. Un buon esempio di ciò sono le intestazioni di sistema di Linux. Li usi come:

 #include  #include  

eccetera.

MODIFICA PER INCLUDERE UN’ALTRA BUONA RISPOSTA: inoltre, se è ipotizzabile che due o più librerie forniscano intestazioni con lo stesso nome, la soluzione della sottodirectory fornisce sostanzialmente a ogni intestazione uno spazio dei nomi.

Per citare lo standard C99 (a prima vista la formulazione sembra essere identica nello standard C90, ma non posso tagliare-n-paste da quello):

Una direttiva di pre-elaborazione del modulo

# include "q-char-sequence" new-line

causa la sostituzione di tale direttiva con l’intero contenuto del file di origine identificato dalla sequenza specificata tra i “delimitatori” Il file di origine denominato viene cercato in un modo definito dall’implementazione. Se questa ricerca non è supportata o se la ricerca non riesce , la direttiva viene rielaborata come se leggesse

# include new-line

con la sequenza identica contenuta (compresi i caratteri, se ce ne sono) dalla direttiva originale.

Quindi le posizioni cercate da #include "whatever" è un super-set delle posizioni cercate da #include . L’intento è quello di utilizzare il primo stile per le intestazioni che in generale “appartengono” a te e il secondo metodo sarebbe utilizzato per le intestazioni che “appartengono” al compilatore / all’ambiente. Certo, ci sono spesso alcune aree grigie, che dovresti usare per le intestazioni Boost, ad esempio? Userei #include <> , ma non discuterei troppo se qualcun altro nella mia squadra voleva #include "" .

In pratica, non penso che nessuno presta molta attenzione a quale forma viene usata fino a quando la costruzione non si rompe. Certamente non ricordo che sia mai stato menzionato in una recensione del codice (o altrimenti, anche).

Tratterò la seconda parte della tua domanda:

Normalmente utilizzo quando intestazioni di terze parti. E "myHeader.h" quando "myHeader.h" intestazioni all’interno del progetto.

Il motivo per cui utilizzo invece di è perché è ansible che più di una libreria abbia un file “libHeader.h”. Per includerli entrambi è necessario il nome della libreria come parte del nome file incluso.

1.a: quali sono le differenze tra le due notazioni?

“” avvia la ricerca nella directory in cui si trova il file C / C ++. <> avvia la ricerca nelle directory -I e nei percorsi predefiniti (come / usr / include). Entrambi alla fine cercano lo stesso insieme di posizioni, solo l’ordine è diverso.

1.b: Tutti i compilatori li implementano allo stesso modo?

Lo spero, ma non ne sono sicuro.

1.c: Quando useresti il ​​<>, e quando useresti il ​​”” (cioè quali sono i criteri che useresti per usare uno o l’altro per l’inclusione di un header)?

Io uso “” quando si suppone che il file di inclusione sia vicino al file C, <> in tutti gli altri casi. In particolare, nel nostro progetto tutti i file “pubblici” includono nella directory project / include, quindi uso <> per loro.

2 – #include {TheProject / TheHeader.hpp} o {TheHeader.hpp}?

Come già sottolineato, xxx / filename.h consente di fare cose come diskio / ErrorCodes.h e netio / ErrorCodes.h

* intestazioni private del tuo progetto?

Intestazione privata del mio sottosistema nel progetto. Utilizzare “filename.h” Intestazione pubblica del mio sottosistema nel progetto (non visibile all’esterno del progetto, ma accessibile ad altri sottosistemi). Utilizzare o, a seconda della convenzione adattata per il progetto. Preferirei usare

* le intestazioni del tuo progetto, ma che esportano simboli (e quindi “pubblici”)

includi esattamente come gli utenti della tua libreria li includerebbe. Probabilmente

* Intestazioni di un altro progetto con cui il modulo si collega

Determinato dal progetto, ma certamente usando <> * intestazioni di un compilatore o di una libreria standard Definitivamente <>, secondo lo standard.

3.a: lavori a progetto con fonti e / o intestazioni all’interno di un’organizzazione ad albero (ad esempio, le directory all’interno delle directory, al contrario di “ogni file in una directory”) e quali sono i pro / contro?

Lavoro su un progetto strutturato. Non appena avrai più di una ventina di file, alcune divisioni saranno evidenti. Dovresti fare come ti sta prendendo il codice.

Se ricordo bene.

Usa il diamante per tutte le librerie che si possono trovare nel tuo “percorso”. Quindi qualsiasi libreria che si trova nell’STL o che hai installato. In Linux il tuo percorso è solitamente “/ usr / include”, in Windows non sono sicuro, ma suppongo che sia sotto “C: \ windows”.

Usa “” quindi per specificare tutto il resto. “my_bla.cpp” senza informazioni sulla directory iniziale si risolverà nella directory in cui risiede / compila il codice. In alternativa puoi anche specificare la posizione esatta della tua inclusione con essa. Come questo “c: \ myproj \ some_code.cpp”

Il tipo di intestazione non ha importanza, solo la posizione.

Ri <> vs “”. Nel mio negozio, sono molto disponibile per quanto riguarda lo “stile”. Una delle poche aree in cui ho bisogno è l’uso delle parentesi angolari nelle dichiarazioni #include – la regola è questa: se si include #include un sistema operativo o un file del compilatore, è possibile utilizzare parentesi angolari se appropriato. In tutti gli altri casi, sono vietati. Se si include # un file scritto da qualcuno qui o da una libreria di terze parti, <> è vietato.

Il motivo è questo: #include “xh” e #include non cercare gli stessi percorsi. #include cercherà solo il percorso del sistema e qualsiasi cosa tu abbia inserito. Importante, non cercherà il percorso dove si trova il file xh, se quella directory non è inclusa nel percorso di ricerca in qualche altro modo.

Ad esempio, supponiamo di avere i seguenti file:

c: \ dev \ angoli \ main.cpp

 #include "c:\utils\mylib\mylibrary.h" int main() { return 0; } 

c: \ utils \ mylib \ mylibrary.h

 #ifndef MYLIB_H #define MYLIB_H #include  namespace mylib { void Speak(SpeechType speechType); }; #endif 

c: \ utils \ mhlib \ speech.h

 #ifndef SPEECH_H #define SPEECH_H namespace mylib { enum SpeechType {Bark, Growl}; }; #endif 

Questo non verrà compilato senza modificare il percorso impostando la variabile d’ambiente PATH o -i’ing nella directory c: \ utils \ mhlib \. Il compilatore non sarà in grado di resove #include anche se quel file si trova nella stessa directory di mylibrary.h !

Facciamo ampio uso di percorsi relativi e assoluti nelle dichiarazioni #include nel nostro codice, per due ragioni.

1) Mantenendo le librerie e i componenti dall’albero dei sorgenti principale (cioè mettendo le librerie di utilità in una directory speciale), non accoppiamo il ciclo di vita della libreria al ciclo di vita dell’applicazione. Questo è particolarmente importante quando si hanno diversi prodotti distinti che utilizzano le librerie comuni.

2) Utilizziamo le giunzioni per mappare una posizione fisica sul disco rigido in una directory su un’unità logica e quindi utilizzare un percorso completo sull’unità logica in tutti #inclusi. Per esempio:

#include "x:\utils\mylib.h" – buono, x: è un disco sottostimato, e x: \ utils punta a c: \ code \ utils_1.0 sul disco rigido

#include "c:\utils_1.0\mylib.h" – cattivo! l’applicazione che include # mylib.h è ora accoppiata a una versione specifica della libreria MYLIB e tutti gli sviluppatori devono averla nella stessa directory sul disco rigido, c: \ utils_1.0

Infine, un objective ampio ma difficile da raggiungere della mia squadra è quello di essere in grado di supportare le compilazioni con 1 clic. Ciò include la possibilità di compilare l’albero dei sorgenti principale non facendo altro che ottenere il codice dal controllo del codice sorgente e quindi premere ‘compila’. In particolare, aborrirò a dover impostare percorsi e directory complete di macchina per essere in grado di compilare, perché ogni piccolo passo aggiuntivo che aggiungi alla fase di set-up per build una macchina di sviluppo rende solo più difficile, più facile fare casino su, e ci vuole più tempo per ottenere una nuova macchina fino a velocità e la generazione di codice.

Esistono due differenze primarie tra <> e "" . Il primo è che il carattere finirà il nome – non ci sono sequenze di escape nei nomi delle intestazioni, e quindi potresti essere costretto a fare #include o "bla>file.cpp" . L’altra differenza è che gli include di sistema non dovrebbero verificarsi su "" , solo <> . Quindi #include "iostream" non è garantito per funzionare; #include mia preferenza personale è utilizzare "" per i file che fanno parte del progetto e <> per i file che non lo sono.Qualcuno usa solo <> per le intestazioni di libreria standard e "" per tutto il resto.Qualcuno usa addirittura <> solo per Boost e std: dipende dal progetto: come tutti gli aspetti dello stile, la cosa più importante è essere coerenti.

Per quanto riguarda il percorso, una libreria esterna specificherà la convenzione per le intestazioni; ad esempio . In un progetto locale, scriverei tutti i percorsi relativi al srcdir di livello principale (o in un progetto di libreria in cui sono diversi, la directory include).

Quando scrivi una libreria, potresti anche trovare utile usare <> per distinguere tra intestazioni private e pubbliche, o non -I directory sorgente, ma la directory sopra, quindi #include "public_header.hpp" e "src/private_header.hpp" . Dipende davvero da te.

EDIT: Per quanto riguarda i progetti con strutture di directory, li raccomando vivamente. Immagina se tutti gli boost fossero in una directory (e nessun subnamespace)! La struttura delle directory è buona perché consente di trovare i file più facilmente e consente una maggiore flessibilità nella denominazione ( "module\_text\_processor.hpp" anziché "module/text\_processor.hpp" ). Quest’ultimo è più naturale e più facile da usare.

Io uso <...> dal file di intestazione di sistema (stdio, iostreams, string ecc.) E “…” per intestazioni specifiche per quel progetto.

Utilizziamo #include “header.h” per le intestazioni locali al progetto e # include per il sistema include, terze parti include e altri progetti nella soluzione. Usiamo Visual Studio, ed è molto più semplice usare la directory del progetto in un’intestazione, in questo modo ogni volta che creiamo un nuovo progetto, dobbiamo solo specificare il percorso di inclusione per la directory che contiene tutte le directory del progetto, non un percorso separato per ogni progetto.