Cosa dovrebbe e cosa non dovrebbe essere in un file di intestazione? [chiuso]


71

Quali cose non dovrebbero mai essere incluse in un file di intestazione?

Se per esempio sto lavorando con un formato standard del settore documentato che ha molte costanti, è una buona pratica definirle in un file di intestazione (se sto scrivendo un parser per quel formato)?

Quali funzioni dovrebbero andare nel file di intestazione?
Quali funzioni non dovrebbero?


1
Breve e indolore: definizioni e dichiarazioni necessarie in più di un modulo.
ott--

21
Contrassegnare questa domanda come "troppo ampia" e chiudere è una vergognosa assoluta vergogna di moderazione. Questa domanda chiede esattamente quello che sto cercando: la domanda è ben formata e fa una domanda molto chiara: quali sono le migliori pratiche? Se questo è "troppo ampio" per la progettazione del software ... potremmo anche chiudere l'intero forum.
Gewure,

TL; DR. Per il C ++, nella quarta edizione di "Il linguaggio di programmazione C ++" scritto da Bjarne Stroustrup (il suo creatore), nella Sezione 15.2.2 viene descritto ciò che un'intestazione dovrebbe e non dovrebbe contenere. So che hai taggato la domanda a C, ma alcuni dei consigli sono applicabili. Penso che questa sia una buona domanda ...
horro

Risposte:


57

Cosa mettere nelle intestazioni:

  • L'insieme minimo di #includedirettive necessarie per rendere compilabile l'intestazione quando l'intestazione è inclusa in alcuni file di origine.
  • Definizioni dei simboli del preprocessore di cose che devono essere condivise e che possono essere realizzate solo tramite il preprocessore. Anche in C, i simboli del preprocessore sono tenuti al minimo.
  • Dichiarazioni in avanti delle strutture necessarie per rendere compilabili le definizioni delle strutture, i prototipi di funzione e le dichiarazioni delle variabili globali nel corpo dell'intestazione.
  • Definizioni di strutture dati ed enumerazioni condivise tra più file di origine.
  • Dichiarazioni per funzioni e variabili le cui definizioni saranno visibili al linker.
  • Definizioni delle funzioni incorporate, ma fai attenzione qui.

Cosa non appartiene a un'intestazione:

  • #includeDirettive gratuite . Quelle gratuite includono la ricompilazione delle cose che non hanno bisogno di essere ricompilate, e a volte possono farlo in modo che un sistema non possa essere compilato. Non creare #includeun file in un'intestazione se l'intestazione stessa non necessita dell'altro file di intestazione.
  • Simboli del preprocessore il cui intento potrebbe essere realizzato da qualche meccanismo, qualsiasi meccanismo, diverso dal preprocessore.
  • Molte definizioni di strutture. Dividili in intestazioni separate.
  • Definizioni incorporate di funzioni che richiedono un ulteriore #include, che sono soggette a modifiche o che sono troppo grandi. Queste funzioni in linea dovrebbero avere poca o nessuna ventola, e se hanno una ventola, dovrebbero essere localizzate in elementi definiti nell'intestazione.

Cosa costituisce l'insieme minimo di #includedichiarazioni?

Questa risulta essere una domanda non banale. Una definizione TL; DR: un file di intestazione deve includere i file di intestazione che definiscono direttamente ciascuno dei tipi utilizzati direttamente o che dichiarano direttamente ciascuna delle funzioni utilizzate nel file di intestazione in questione, ma non devono includere nient'altro. Un puntatore o un tipo di riferimento C ++ non si qualifica come uso diretto; i riferimenti in avanti sono preferiti.

C'è un posto per una #includedirettiva gratuita , e questo è in un test automatizzato. Per ogni file di intestazione in un pacchetto software, generi automaticamente e quindi compilo quanto segue:

#include "path/to/random/header_under_test"
int main () { return 0; }

La compilation dovrebbe essere pulita (ovvero priva di avvisi o errori). Avvertenze o errori relativi a tipi incompleti o sconosciuti indicano che il file di intestazione sotto test ha alcune #includedirettive mancanti e / o dichiarazioni in avanti mancanti. Nota bene: solo perché il test ha esito positivo non significa che l'insieme delle #includedirettive sia sufficiente, per non parlare del minimo.


Quindi, se ho una libreria che definisce una struttura, chiamata A, e questa libreria, chiamata B, usa quella struttura e la libreria B viene utilizzata dal programma C, dovrei includere il file di intestazione della libreria A nell'intestazione principale della libreria B, o dovrei Ho appena dichiarato in avanti? la libreria A viene compilata e collegata alla libreria B durante la sua compilazione.
MarcusJ,

@MarcusJ - La prima cosa che ho elencato in Ciò che non appartiene a un'intestazione sono state le dichiarazioni #include gratuite. Se il file di intestazione B non dipende dalle definizioni nel file di intestazione A, non #includere il file di intestazione A nel file di intestazione B. Un file di intestazione non è il luogo in cui specificare dipendenze di terze parti o istruzioni di compilazione. Quelli vanno da qualche altra parte come un file readme di livello superiore.
David Hammen,

1
@MarcusJ - Ho aggiornato la mia risposta nel tentativo di rispondere alla tua domanda. Nota che non esiste una risposta alla tua domanda. Illustrerò con un paio di estremi. Caso 1: l'unico posto in cui la libreria B utilizza direttamente la funzionalità della libreria A è nei file sorgente della libreria B. Caso 2: la libreria B è una sottile estensione della funzionalità nella libreria A, con i file di intestazione della libreria B che utilizzano direttamente i tipi e / o le funzioni definiti nella libreria A. Nel caso 1, non c'è motivo di esporre la libreria A in le intestazioni per la libreria B. Nel caso 2, questa esposizione è praticamente obbligatoria.
David Hammen,

Sì, è il caso 2, mi dispiace che il mio commento abbia ignorato il fatto che sta usando i tipi dichiarati nella libreria A nelle intestazioni della libreria B, stavo pensando che potrei essere in grado di dichiararlo in avanti, ma non penso che funzionerà. Grazie per l'aggiornamento.
MarcusJ,

L'aggiunta di costanti a un file di intestazione è un grande no-no?
mding5692,

15

Oltre a quanto è già stato detto.

I file H devono sempre contenere:

  • Documentazione sul codice sorgente !!! Come minimo, qual è lo scopo dei vari parametri e restituisce i valori delle funzioni.
  • Protezioni intestazione, #ifndef MYHEADER_H #define MYHEADER_H ... #endif

I file H non devono mai contenere:

  • Qualsiasi forma di allocazione dei dati.
  • Definizioni delle funzioni. Le funzioni integrate possono essere un'eccezione rara in alcuni casi.
  • Qualsiasi cosa etichettata static.
  • Typedefs, #define o costanti che non hanno rilevanza per il resto dell'applicazione.

(Direi anche che non c'è mai motivo di usare variabili globali / esterne non costanti, ovunque, ma questa è una discussione per un altro post.)


1
Sono d'accordo con tutto, tranne quello che hai evidenziato. Se stai creando una libreria, sì, dovresti documentare o gli utenti della tua libreria. Per un progetto interno non dovresti avere bisogno di ingombrare le intestazioni con la documentazione, se usi nomi variabili e funzioni autoesplicativi.
martiert,

5
@martiert Sono anche della scuola "lascia che il codice parli da solo". Tuttavia dovresti almeno documentare sempre le tue funzioni, anche se solo te stesso le utilizzerà. Le cose di particolare interesse sono: nel caso in cui la funzione abbia una gestione degli errori, quali codici di errore restituisce e in quali condizioni fallisce? Cosa succede ai parametri (buffer, puntatori ecc.) Se la funzione fallisce? Un'altra cosa molto rilevante è: i parametri del puntatore restituiscono qualcosa al chiamante, ovvero si aspettano memoria allocata? ->

1
Dovrebbe essere ovvio al chiamante cosa viene fatta la gestione degli errori all'interno della funzione e cosa non viene fatto. Se la funzione prevede un buffer allocato, molto probabilmente lascerà anche i controlli fuori limite al chiamante. Se la funzione si basa su un'altra funzione da eseguire, questa deve essere documentata (ovvero eseguire link_list_init () prima di link_list_add ()). E infine, se la funzione ha un "effetto collaterale" come la creazione di file, thread, timer o altro, dovrebbe essere dichiarata nella documentazione. ->

1
Forse la "documentazione del codice sorgente" è troppo ampia qui, questo appartiene davvero al codice sorgente. La "documentazione d'uso" con input e output, pre e postcondizioni ed effetti collaterali dovrebbe sicuramente andare lì, non in modo epico ma in una forma breve .
Sicuro l'

2
Un po 'in ritardo, ma +1 per la documentazione. Perché esiste questa classe? Il codice non parla da solo. Cosa fa questa funzione? RTFC (leggi il file .cpp fine) è un acronimo acre di quattro lettere. Non si dovrebbe mai dovere RTFC per capire. Il prototipo nell'intestazione dovrebbe riassumere, in alcuni commenti estraibili (ad esempio doxygen), quali sono gli argomenti e cosa fa la funzione. Perché esiste questo membro di dati, cosa contiene ed è il valore in metri, piedi o lunghezze? Anche questo è un altro argomento per i commenti (estraibili).
David Hammen,

4

Probabilmente non direi mai mai, ma le dichiarazioni che generano dati e codice quando vengono analizzate non dovrebbero essere in un file .h.

Le macro, le funzioni in linea e i modelli possono apparire come dati o codice, ma non generano codice quando vengono analizzati, ma invece quando vengono utilizzati. Questi elementi devono spesso essere utilizzati in più di un .c o .cpp, quindi appartengono al .h.

A mio avviso, un file di intestazione dovrebbe avere l'interfaccia pratica minima per un corrispondente .c o .cpp. L'interfaccia può includere #define, classe, typedef, definizioni di struttura, prototipi di funzione e definizioni esterne meno preferite per variabili globali. Tuttavia, se una dichiarazione viene utilizzata in un solo file di origine, probabilmente dovrebbe essere esclusa da .h ed essere invece contenuta nel file di origine.

Alcuni potrebbero non essere d'accordo, ma i miei criteri personali per i file .h sono che # includono tutti gli altri file .h che devono essere in grado di compilare. In alcuni casi, questo può essere un sacco di file, quindi abbiamo alcuni metodi efficaci per ridurre le dipendenze esterne come dichiarazioni forward alle classi che ci permettono di usare i puntatori agli oggetti di una classe senza includere quello che potrebbe essere un grande albero di file include.


3

Il file di intestazione dovrebbe avere la seguente organizzazione:

  • tipo e definizioni costanti
  • dichiarazioni di oggetti esterni
  • dichiarazioni di funzioni esterne

I file di intestazione non devono mai contenere definizioni di oggetti, solo definizioni di tipi e dichiarazioni di oggetti.


Che dire delle definizioni delle funzioni inline?
Kos,

Se la funzione inline è una funzione "helper" utilizzata solo all'interno di un modulo C, inserirla solo in quel file .c. Se la funzione inline deve essere visibile a due o più moduli, inserirla nel file di intestazione.
thed

Inoltre, se la funzione deve essere visibile oltre un limite della libreria, non renderla in linea poiché ciò costringe tutti coloro che usano la libreria a ricompilare ogni volta che si modificano le cose.
Donal Fellows,

@DonalFellows: è una soluzione rovesciata. Una regola migliore: non inserire oggetti nelle intestazioni soggetti a frequenti modifiche. Non c'è niente di sbagliato nell'integrare una breve funzione in un'intestazione se la funzione non ha scolorimento e ha una definizione chiara che cambierà solo se cambia la struttura dei dati sottostanti. Se la definizione della funzione cambia perché la definizione della struttura sottostante è cambiata, sì, devi ricompilare tutto, ma dovrai farlo comunque perché la definizione della struttura è cambiata.
David Hammen,

0

Le istruzioni che generano dati e codice quando vengono analizzate, non devono essere contenute in un .hfile. Per quanto riguarda il mio punto di vista, un file di intestazione dovrebbe avere solo l'interfaccia pratica minima per un corrispondente .co .cpp.

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.