Cosa dovrebbe andare in un file .h?

Quando dividi il tuo codice in più file, cosa esattamente dovrebbe andare in un file .h e cosa dovrebbe andare in un file .cpp?

I file di intestazione ( .h ) sono progettati per fornire le informazioni che saranno necessarie in più file. Cose come le dichiarazioni di class, i prototipi di funzione e le enumerazioni tipicamente vanno nei file di intestazione. In una parola, “definizioni”.

I file di codice ( .cpp ) sono progettati per fornire le informazioni sull’implementazione che devono essere conosciute solo in un file. In generale, i corpi delle funzioni e le variabili interne che dovrebbero / non saranno mai accessibili da altri moduli, sono ciò che appartiene ai file .cpp . In una parola, “implementazioni”.

La domanda più semplice da porsi per determinare cosa appartiene dove è “se cambio questo, dovrò cambiare il codice in altri file per fare di nuovo compilare le cose?” Se la risposta è “sì”, probabilmente appartiene al file di intestazione; se la risposta è “no”, probabilmente appartiene al file di codice.

Il fatto è, in C ++, questo è un po ‘più complicato che l’organizzazione di intestazione / sorgente C.

Cosa vede il compilatore?

Il compilatore vede un grande file sorgente (.cpp) con le intestazioni correttamente incluse. Il file sorgente è l’unità di compilazione che verrà compilata in un file object.

Quindi, perché sono necessarie le intestazioni?

Perché una unità di compilazione potrebbe aver bisogno di informazioni su un’implementazione in un’altra unità di compilazione. Quindi si può scrivere ad esempio l’implementazione di una funzione in una sorgente e scrivere la dichiarazione di questa funzione in un’altra fonte che ha bisogno di usarla.

In questo caso, ci sono due copie delle stesse informazioni. Che è cattivo …

La soluzione è condividere alcuni dettagli. Mentre l’implementazione dovrebbe rimanere nella Sorgente, potrebbe essere necessario condividere la dichiarazione di simboli condivisi, funzioni simili o definizione di strutture, classi, enumerazioni, ecc.

Le intestazioni sono usate per mettere quei dettagli condivisi.

Sposta all’intestazione le dichiarazioni di ciò che deve essere condiviso tra più fonti

Niente di più?

In C ++, ci sono altre cose che potrebbero essere messe nell’intestazione perché, anche loro, devono essere condivise:

  • codice inline
  • modelli
  • costanti (di solito quelle che si vogliono usare all’interno degli switch …)

Sposta l’intestazione TUTTO ciò che deve essere condiviso, comprese le implementazioni condivise

Significa quindi che potrebbero esserci fonti all’interno delle intestazioni?

Sì. In effetti, ci sono molte cose diverse che potrebbero essere all’interno di un “header” (cioè condiviso tra le fonti).

  • Dichiarazioni in avanti
  • dichiarazioni / definizione di funzioni / strutture / classi / modelli
  • implementazione di codice in linea e basato su modelli

Diventa complicato e, in alcuni casi (dipendenze circolari tra i simboli), imansible conservarlo in un’unica intestazione.

Le intestazioni possono essere suddivise in tre parti

Ciò significa che, in un caso estremo, potresti avere:

  • un’intestazione di dichiarazione diretta
  • un’intestazione di dichiarazione / definizione
  • un’intestazione di implementazione
  • una fonte di implementazione

Immaginiamo di avere un MyObject basato su modelli. Potremmo avere:

 // - - - - MyObject_forward.hpp - - - - // This header is included by the code which need to know MyObject // does exist, but nothing more. template class MyObject ; 

.

 // - - - - MyObject_declaration.hpp - - - - // This header is included by the code which need to know how // MyObject is defined, but nothing more. #include  template class MyObject { public : MyObject() ; // Etc. } ; void doSomething() ; 

.

 // - - - - MyObject_implementation.hpp - - - - // This header is included by the code which need to see // the implementation of the methods/functions of MyObject, // but nothing more. #include  template MyObject::MyObject() { doSomething() ; } // etc. 

.

 // - - - - MyObject_source.cpp - - - - // This source will have implementation that does not need to // be shared, which, for templated code, usually means nothing... #include  void doSomething() { // etc. } ; // etc. 

Wow!

Nella “vita reale”, di solito è meno complicato. La maggior parte del codice avrà solo una semplice intestazione / organizzazione di origine, con qualche codice inserito nella fonte.

Ma in altri casi (oggetti basati su modelli che si conoscono a vicenda), dovevo avere per ogni object intestazioni di dichiarazione e implementazione separate, con una fonte vuota che includesse quelle intestazioni solo per aiutarmi a vedere alcuni errori di compilazione.

Un altro motivo per suddividere le intestazioni in intestazioni separate potrebbe essere quello di accelerare la compilazione, limitando la quantità di simboli analizzati allo stretto necessario ed evitando la ricompilazione non necessaria di una fonte che si preoccupa solo della dichiarazione forward quando è stata modificata un’implementazione del metodo inline.

Conclusione

Dovresti rendere l’organizzazione del codice il più semplice ansible e modulare il più ansible. Metti il ​​più ansible nel file sorgente. Esponi solo nelle intestazioni ciò che deve essere condiviso.

Ma il giorno in cui avrai dipendenze circolari tra gli oggetti basati su modelli, non sorprenderti se la tua organizzazione del codice diventa un po ‘più “interessante” della semplice intestazione / organizzazione di origine …

^ _ ^

oltre a tutte le altre risposte, ti dirò cosa NON inserisci in un file di intestazione:
using dichiarazione (il più comune dei quali sta using namespace std; ) non dovrebbe apparire in un file di intestazione perché inquina lo spazio dei nomi del file sorgente in cui è incluso.

Ciò che compila nel nulla (zero impronta binaria) entra nel file di intestazione.

Le variabili non vengono compilate nel nulla, ma le dichiarazioni di tipo (in quanto descrivono solo come si comportano le variabili).

le funzioni no, ma le funzioni inline fanno (o macro), perché producono codice solo dove sono chiamate.

i modelli non sono codice, sono solo una ricetta per creare codice. così anche loro vanno in h file.

In generale, si mettono le dichiarazioni nel file di intestazione e nelle definizioni nel file di implementazione (.cpp). L’eccezione a questo è modelli, in cui la definizione deve anche andare nell’intestazione.

Questa domanda e altre simili a questa sono state poste frequentemente su SO: vedi Perché i file header e .cpp sono in C ++? e C ++ Header Files, Code Separation per esempio.

Le dichiarazioni di class e funzione più la documentazione e le definizioni per funzioni / metodi inline (anche se alcuni preferiscono inserirli in file .inl separati).

Principalmente il file di intestazione contiene scheletro di class o dichiarazione (non cambia frequentemente)

e il file cpp contiene l’ implementazione di class (cambia frequentemente).

il file di intestazione (.h) dovrebbe essere per dichiarazioni di classi, strutture e relativi metodi, prototipi, ecc. L’implementazione di tali oggetti è fatta in cpp.

in .h

  class Foo { int j; Foo(); Foo(int) void DoSomething(); } 

Mi aspetterei di vedere:

  • dichiarazioni
  • Commenti
  • definizioni contrassegnate in linea
  • modelli

la vera risposta è però cosa non inserire:

  • definitons (può portare a definire molte cose)
  • usando dichiarazioni / direttive (li forza su chiunque, inclusa la tua intestazione, può causare nameclashes)

L’intestazione Definisce qualcosa ma non dice nulla sull’implementazione. (Esclusi i modelli in questa “metafore”.

Detto questo, è necessario dividere le “definizioni” in sottogruppi, in questo caso ci sono due tipi di definizioni.

  • Definisci il “layout” della tua strucutre, dicendo solo quanto è necessario dai gruppi di utilizzo circostanti.
  • Le definizioni di una variabile, una funzione e una class.

Ora, ovviamente sto parlando del primo sottogruppo.

L’intestazione è lì per definire il layout della tua struttura al fine di aiutare il resto del software a utilizzare l’implementazione. Potresti volerlo vedere come una “astrazione” della tua implementazione, che è detto volentieri ma, penso che si adatti abbastanza bene in questo caso.

Come i posters precedenti hanno detto e mostrato di dichiarare aree di utilizzo pubbliche e private e le loro intestazioni, questo include anche variabili private e pubbliche. Ora, non voglio entrare nella progettazione del codice qui, ma potresti prendere in considerazione ciò che inserisci nelle intestazioni, poiché questo è il livello tra l’utente finale e l’implementazione.

Intestazione (.h)

  • Macro e include necessario per le interfacce (il minor numero ansible)
  • La dichiarazione delle funzioni e delle classi
  • Documentazione dell’interfaccia
  • Dichiarazione di funzioni / metodi in linea, se presenti
  • extern a variabili globali (se presenti)

Corpo (.cpp)

  • Resto di macro e include
  • Include l’intestazione del modulo
  • Definizione di funzioni e metodi
  • Variabili globali (se presenti)

Come regola generale, si mette la parte “condivisa” del modulo su .h (la parte che gli altri moduli devono essere in grado di vedere) e la parte “non condivisa” sul file .cpp

PD: Sì, ho incluso le variabili globali. Li ho usati alcune volte ed è importante non definirli nelle intestazioni, o otterrai molti moduli, ognuno dei quali definisce la propria variabile.

EDIT: modificato dopo il commento di David

  • File di intestazione – non dovrebbero cambiare troppo spesso durante lo sviluppo -> dovresti pensare e scriverli contemporaneamente (nel caso ideale)
  • File di origine: modifiche durante l’implementazione