Domanda

Perché C ++ ha file header e file .cpp?

È stato utile?

Soluzione

Bene, la ragione principale sarebbe quella di separare l'interfaccia dall'implementazione. L'intestazione dichiara "cosa" una classe (o qualunque cosa venga implementata) farà, mentre il file cpp definisce " come " eseguirà tali funzioni.

Ciò riduce le dipendenze in modo che il codice che utilizza l'intestazione non debba necessariamente conoscere tutti i dettagli dell'implementazione e qualsiasi altra classe / intestazione necessaria solo per quello. Ciò ridurrà i tempi di compilazione e anche la quantità di ricompilazione necessaria quando qualcosa nell'implementazione cambia.

Non è perfetto e di solito ricorrere a tecniche come Pimpl Idiom per separare correttamente l'interfaccia e l'implementazione, ma è un buon inizio.

Altri suggerimenti

Compilazione C ++

Una compilazione in C ++ viene eseguita in 2 fasi principali:

  1. La prima è la raccolta di " source " file di testo in binario "oggetto" file: il file CPP è il file compilato e viene compilato senza alcuna conoscenza degli altri file CPP (o persino delle librerie), a meno che non venga fornito tramite una dichiarazione non elaborata o inclusione dell'intestazione. Il file CPP viene solitamente compilato in un .OBJ o .O "oggetto". file.

  2. Il secondo è il collegamento di tutti gli oggetti " oggetto " file, e quindi, la creazione del file binario finale (una libreria o un eseguibile).

Dove si colloca l'HPP in tutto questo processo?

Un povero file CPP solitario ...

La compilazione di ciascun file CPP è indipendente da tutti gli altri file CPP, il che significa che se A.CPP necessita di un simbolo definito in B.CPP, come:

// A.CPP
void doSomething()
{
   doSomethingElse(); // Defined in B.CPP
}

// B.CPP
void doSomethingElse()
{
   // Etc.
}

Non verrà compilato perché A.CPP non ha modo di conoscere " doSomethingElse " esiste ... A meno che non ci sia una dichiarazione in A.CPP, come:

// A.CPP
void doSomethingElse() ; // From B.CPP

void doSomething()
{
   doSomethingElse() ; // Defined in B.CPP
}

Quindi, se si dispone di C.CPP che utilizza lo stesso simbolo, quindi copiare / incollare la dichiarazione ...

AVVISO COPIA / PASTA!

Sì, c'è un problema. Le copie / paste sono pericolose e difficili da mantenere. Ciò significa che sarebbe bello se non avessimo il modo di NON copiare / incollare e dichiarare ancora il simbolo ... Come possiamo farlo? Con l'inclusione di alcuni file di testo, comunemente suffissi da .h, .hxx, .h ++ o, il mio preferito per i file C ++, .hpp:

// B.HPP (here, we decided to declare every symbol defined in B.CPP)
void doSomethingElse() ;

// A.CPP
#include "B.HPP"

void doSomething()
{
   doSomethingElse() ; // Defined in B.CPP
}

// B.CPP
#include "B.HPP"

void doSomethingElse()
{
   // Etc.
}

// C.CPP
#include "B.HPP"

void doSomethingAgain()
{
   doSomethingElse() ; // Defined in B.CPP
}

Come funziona include ?

L'inclusione di un file, in sostanza, analizza e quindi copia e incolla il suo contenuto nel file CPP.

Ad esempio, nel codice seguente, con l'intestazione A.HPP:

// A.HPP
void someFunction();
void someOtherFunction();

... l'origine B.CPP:

// B.CPP
#include "A.HPP"

void doSomething()
{
   // Etc.
}

... diventerà dopo l'inclusione:

// B.CPP
void someFunction();
void someOtherFunction();

void doSomething()
{
   // Etc.
}

Una piccola cosa: perché includere B.HPP in B.CPP?

Nel caso attuale, ciò non è necessario e B.HPP ha la dichiarazione della funzione doSomethingElse e B.CPP ha la definizione della funzione doSomethingElse (che è, di per sé una dichiarazione). Ma in un caso più generale, in cui B.HPP viene utilizzato per le dichiarazioni (e il codice inline), potrebbe non esserci una definizione corrispondente (ad esempio enumerazioni, strutture semplici, ecc.), Quindi l'inclusione potrebbe essere necessaria se B.CPP utilizza tali dichiarazioni di B.HPP. Tutto sommato, è "buon gusto" affinché una fonte includa per impostazione predefinita la sua intestazione.

Conclusione

Il file di intestazione è quindi necessario, poiché il compilatore C ++ non è in grado di cercare da soli le dichiarazioni di simboli e, pertanto, è necessario aiutarlo includendo tali dichiarazioni.

Un'ultima parola: dovresti mettere delle protezioni di intestazione attorno al contenuto dei tuoi file HPP, per essere sicuro che inclusioni multiple non rompano nulla, ma tutto sommato, credo che il motivo principale dell'esistenza dei file HPP sia spiegato sopra.

#ifndef B_HPP_
#define B_HPP_

// The declarations in the B.hpp file

#endif // B_HPP_

Poiché C, dove il concetto ha avuto origine, ha 30 anni, e all'epoca era l'unico modo possibile per collegare codice da più file.

Oggi è un terribile hack che distrugge totalmente i tempi di compilazione in C ++, provoca innumerevoli dipendenze inutili (perché le definizioni di classe in un file di intestazione espongono troppe informazioni sull'implementazione) e così via.

Poiché in C ++, il codice eseguibile finale non contiene alcuna informazione sul simbolo, è più o meno un codice macchina puro.

Pertanto, è necessario un modo per descrivere l'interfaccia di un pezzo di codice, che è separato dal codice stesso. Questa descrizione si trova nel file di intestazione.

Perché il C ++ li ha ereditati da C. Sfortunatamente.

Perché le persone che hanno progettato il formato della libreria non volevano " sprecare " spazio per informazioni utilizzate raramente come le macro del preprocessore C e le dichiarazioni di funzione.

Dato che hai bisogno di quelle informazioni per dire al tuo compilatore "questa funzione è disponibile in seguito quando il linker sta facendo il suo lavoro", hanno dovuto trovare un secondo file dove queste informazioni condivise potevano essere memorizzate.

La maggior parte delle lingue dopo C / C ++ memorizza queste informazioni nell'output (codice byte Java, ad esempio) o non usano affatto un formato precompilato, vengono sempre distribuite in formato sorgente e compilate al volo (Python, Perl) .

È il modo preprocessore di dichiarare le interfacce. Inserisci l'interfaccia (dichiarazioni del metodo) nel file di intestazione e l'implementazione nel cpp. Le applicazioni che usano la tua libreria devono solo conoscere l'interfaccia, a cui possono accedere tramite #include.

Spesso vorrai avere una definizione di un'interfaccia senza dover spedire l'intero codice. Ad esempio, se si dispone di una libreria condivisa, si spedirebbe con sé un file di intestazione che definisce tutte le funzioni e i simboli utilizzati nella libreria condivisa. Senza i file di intestazione, dovresti spedire la fonte.

All'interno di un singolo progetto, vengono utilizzati i file di intestazione, IMHO, per almeno due scopi:

  • Chiarezza, ovvero mantenendo le interfacce separate dall'implementazione, è più facile leggere il codice
  • Tempo di compilazione. Utilizzando solo l'interfaccia ove possibile, anziché la completa implementazione, il tempo di compilazione può essere ridotto perché il compilatore può semplicemente fare un riferimento all'interfaccia invece di dover analizzare il codice effettivo (che, in teoria, dovrebbe solo essere fatto una sola volta).

Risposta a Risposta di MadKeithV ,

  

Questo riduce le dipendenze, così come il codice che utilizza l'intestazione no   necessariamente bisogno di conoscere tutti i dettagli dell'implementazione e qualsiasi   altre classi / intestazioni servivano solo per quello. Questo ridurrà   tempi di compilazione e anche la quantità di ricompilazione necessaria quando   qualcosa nell'implementazione cambia.

Un altro motivo è che un'intestazione fornisce un ID univoco per ogni classe.

Quindi se abbiamo qualcosa come

class A {..};
class B : public A {...};

class C {
    include A.cpp;
    include B.cpp;
    .....
};

Avremo errori, quando proveremo a costruire il progetto, poiché A fa parte di B, con le intestazioni eviteremmo questo tipo di mal di testa ...

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top