Cosa dovrebbe essere contenuto in un file .h?


93

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



7
Questo è un problema di puro stile, ma credo che le dichiarazioni C ++ vadano in un .hppfile mentre le dichiarazioni C vadano in un .hfile. Questo è molto utile quando si mescolano codice C e C ++ (ad esempio moduli legacy in C).
Thomas Matthews,

@ThomasMatthews ha senso. Questa pratica è usata spesso?
ty

@lightningleaf: Sì, la pratica viene spesso utilizzata soprattutto quando si mescolano i linguaggi C ++ e C.
Thomas Matthews

Risposte:


113

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

I file di codice ( .cpp) sono progettati per fornire le informazioni di 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 .cppfile. In una parola, "implementazioni".

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


4
Tranne che i dati della classe privata devono andare nell'intestazione. I modelli devono essere completamente definiti dall'intestazione (a meno che non si utilizzi uno dei pochi compilatori che supportano export). L'unico modo per aggirare il numero 1 è PIMPL. # 2 sarebbe possibile se exportfosse supportato e potrebbe essere possibile utilizzando c ++ 0x e externmodelli. IMO, i file di intestazione in c ++ perdono gran parte della loro utilità.
KitsuneYMG

23
Tutto bene, ma con una terminologia imprecisa. In una parola, "dichiarazioni" - il termine "definizione" è sinonimo di "implementazione". Solo il codice dichiarativo, il codice inline, le definizioni delle macro e il codice del modello devono essere presenti in un'intestazione; cioè niente che istanzia codice o dati.
Clifford,

8
Sono d'accordo con Clifford. Usi i termini dichiarazione e definizione in modo piuttosto sciolto e in qualche modo intercambiabile. Ma hanno significati precisi in C ++. Esempi: una dichiarazione di classe introduce il nome di una classe ma non dice cosa contiene. Una definizione di classe elenca tutti i membri e le funzioni di amicizia. Entrambi possono essere inseriti nei file di intestazione senza problemi. Quello che chiamate "prototipo di funzione" è una dichiarazione di funzione . Ma una definizione di funzione è quella cosa che contiene il codice della funzione e dovrebbe essere inserita in un file cpp, a meno che non sia in linea o (parte di) un modello.
sellibitze

5
Hanno significati precisi in C ++, non hanno significati precisi in inglese. La mia risposta era scritta in quest'ultima.
Amber

54

Il fatto è che, in C ++, questo è un po 'più complicato dell'organizzazione dell'intestazione / sorgente C.

Cosa vede il compilatore?

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

Allora, perché sono necessarie le intestazioni?

Perché un'unità di compilazione potrebbe richiedere 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 sorgente che necessita di utilizzarla.

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

La soluzione è condividere alcuni dettagli. Mentre l'implementazione dovrebbe rimanere nel Source, potrebbe essere necessario condividere la dichiarazione di simboli condivisi, come funzioni, o la definizione di strutture, classi, enumerazioni, ecc.

Le intestazioni vengono utilizzate per inserire quei dettagli condivisi.

Spostare nell'intestazione le dichiarazioni di ciò che deve essere condiviso tra più fonti

Niente di più?

In C ++, ci sono altre cose che potrebbero essere inserite nell'intestazione perché, anch'esse, devono essere condivise:

  • codice inline
  • modelli
  • costanti (di solito quelle che vuoi usare all'interno degli interruttori ...)

Passa all'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 sorgenti).

  • Dichiarazioni future
  • dichiarazioni / definizione di funzioni / strutture / classi / modelli
  • implementazione di codice inline e basato su modelli

Diventa complicato e in alcuni casi (dipendenze circolari tra simboli), impossibile tenerlo in un'intestazione.

Le intestazioni possono essere suddivise in tre parti

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

  • un'intestazione di dichiarazione anticipata
  • 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<typename T>
class MyObject ;

.

// - - - - MyObject_declaration.hpp - - - - 
// This header is included by the code which need to know how
// MyObject is defined, but nothing more.
#include <MyObject_forward.hpp>

template<typename T>
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 <MyObject_declaration.hpp>

template<typename T>
MyObject<T>::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 <MyObject_implementation.hpp>

void doSomething()
{
   // etc.
} ;

// etc.

Wow!

Nella "vita reale", di solito è meno complicato. La maggior parte del codice avrà solo una semplice organizzazione di intestazione / sorgente, con un po 'di codice inline nel sorgente.

Ma in altri casi (oggetti modellati che si conoscono a vicenda), dovevo avere per ogni oggetto dichiarazioni separate e intestazioni di implementazione, 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 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 in avanti quando l'implementazione di un metodo inline cambia.

Conclusione

È necessario rendere l'organizzazione del codice il più semplice possibile e il più modulare possibile. Metti quanto più possibile nel file sorgente. Esponi nelle intestazioni solo ciò che deve essere condiviso.

Ma il giorno in cui avrai dipendenze circolari tra oggetti modellati, non sorprenderti se la tua organizzazione del codice diventa un po 'più "interessante" della semplice organizzazione di intestazione / origine ...

^ _ ^


17

oltre a tutte le altre risposte, ti dirò cosa NON metti in un file di intestazione: la
usingdichiarazione (l'essere più comune using namespace std;) non dovrebbe apparire in un file di intestazione perché inquina lo spazio dei nomi del file sorgente in cui è inclusa .


+1 con un avvertimento che puoi fare utilizzando fintanto che è in uno spazio dei nomi di dettaglio (o uno spazio dei nomi anonimo). Ma sì, non usare mai usingper portare cose nello spazio dei nomi globale in un'intestazione.
KitsuneYMG

+1 È molto più facile rispondere a questo. :) Inoltre, i file di intestazione non devono contenere spazi dei nomi anonimi .
sellibitze

Va bene che i file di intestazione contengano spazi dei nomi anonimi, purché tu capisca cosa significa, cioè che ogni unità di traduzione avrà una copia diversa delle cose che definisci spazio dei nomi. Le funzioni inline negli spazi dei nomi anonimi sono consigliate in C ++ per i casi in cui static inlineuseresti in C99, a causa di qualcosa che ha a che fare con ciò che accade quando combini il collegamento interno con i modelli. Gli spazi dei nomi Anon consentono di "nascondere" le funzioni, preservando il collegamento esterno.
Steve Jessop,

Steve, quello che hai scritto non mi ha convinto. Scegli un esempio concreto in cui pensi che uno spazio dei nomi anon abbia perfettamente senso in un file di intestazione.
sellibitze

6

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

Le variabili non si compilano in niente, ma le dichiarazioni di tipo sì (perché descrivono solo come si comportano le variabili).

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

i modelli non sono codice, sono solo una ricetta per creare codice. quindi vanno anche nei file h.


1
"le funzioni inline ... producono codice solo dove vengono chiamate". Non è vero. le funzioni inline possono o non possono essere inline nei siti di chiamata, ma anche se sono inline, il corpo della funzione reale esiste ancora proprio come fa per una funzione non inline. Il motivo per cui va bene avere funzioni inline nelle intestazioni non ha nulla a che fare con se generano codice, è perché le funzioni inline non attivano l'unica regola di definizione, quindi a differenza delle funzioni non inline non ci sono problemi a collegare insieme due diverse unità di traduzione che hanno entrambi incluso l'intestazione.
Steve Jessop

3

In generale, inserisci le dichiarazioni nel file di intestazione e le definizioni nel file di implementazione (.cpp). L'eccezione a questo sono i modelli, in cui la definizione deve essere inserita anche nell'intestazione.

Questa domanda e altre simili sono state poste frequentemente in SO - vedi Perché avere file di intestazione e file .cpp in C ++? e file di intestazione C ++, separazione del codice per esempio.


naturalmente, puoi anche inserire definizioni di classe nei file di intestazione. Non devono nemmeno essere modelli.
sellibitze

1

Le vostre dichiarazioni di classi e funzioni più la documentazione e le definizioni per funzioni / metodi inline (sebbene alcuni preferiscano metterli in file .inl separati).


1

Principalmente il file di intestazione contiene lo scheletro della classe o la dichiarazione (non cambia frequentemente)

e il file cpp contiene l' implementazione della classe (cambia frequentemente).


5
Si prega di astenersi dall'utilizzare una terminologia non standard. Cos'è "scheletro di classe", cos'è "implementazione di classe"? Inoltre, ciò che chiamate dichiarazione nel contesto delle classi probabilmente include definizioni di classe.
sellibitze

0

il file header (.h) dovrebbe essere per le dichiarazioni di classi, strutture e suoi metodi, prototipi, ecc. L'implementazione di questi oggetti è fatta in cpp.

in .h

    class Foo {
    int j;

    Foo();
    Foo(int)
    void DoSomething();
}

0

Mi aspetto di vedere:

  • dichiarazioni
  • Commenti
  • definizioni contrassegnate in linea
  • modelli

la vera risposta però è cosa non inserire:

  • definizioni (può portare a definire le cose in modo moltiplicato)
  • usare dichiarazioni / direttive (le impone a chiunque, inclusa la tua intestazione, può causare conflitti di nome)

1
Puoi certamente inserire anche definizioni di classe nei file di intestazione. Una dichiarazione di classe non dice nulla sui suoi membri.
sellibitze

0

L'intestazione Definisce qualcosa ma non dice nulla sull'implementazione. (Esclusi i modelli in questo "metafore".

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

  • Tu definisci il "layout" della tua struttura, dicendo solo quanto è necessario ai gruppi di utilizzo circostanti.
  • Le definizioni di una variabile, una funzione e una classe.

Ora sto ovviamente parlando del primo sottogruppo.

L'intestazione serve per definire il layout della struttura in modo da aiutare il resto del software a utilizzare l'implementazione. Potresti vederlo come una "astrazione" della tua implementazione, il che è detto in modo vanitoso, ma penso che in questo caso si adatti abbastanza bene.

Come hanno detto e mostrato i poster precedenti, dichiari le aree di utilizzo pubbliche e private e le loro intestazioni, questo include anche le 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.


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

Questa è una pratica. Per alcuni progetti più piccoli, potrebbe essere la strada da percorrere. Ma potresti provare a deprecare le funzioni ei loro prototipi (nei file di intestazione), piuttosto che cambiare la loro firma o rimuoverli. Almeno fino a quando non si cambia il numero maggiore. Come quando 1.9.2 viene portato a 2.0.0 beta.
TamusJRoyce

0

Intestazione (.h)

  • Macro e include necessari per le interfacce (il minor numero possibile)
  • La dichiarazione delle funzioni e delle classi
  • Documentazione dell'interfaccia
  • Dichiarazione di funzioni / metodi inline, se presenti
  • esterno alle variabili globali (se presenti)

Corpo (.cpp)

  • Resto delle macro e include
  • Includere l'intestazione del modulo
  • Definizione di funzioni e metodi
  • Variabili globali (se presenti)

Come regola pratica, metti la parte "condivisa" del modulo su .h (la parte che gli altri moduli devono essere in grado di vedere) e la parte "non condivisa" su .cpp

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

EDIT: Modificato dopo il commento di David


Come regola pratica, nel file .h dovrebbe essere presente il minor numero possibile di inclusioni e il file .cpp dovrebbe includere tutte le intestazioni di cui ha bisogno. Ciò riduce i tempi di compilazione e non inquina gli spazi dei nomi.
David Thornley,
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.