Perché C ++ ha file header e file .cpp?
Perché C ++ ha file header e file .cpp?
Risposte:
Bene, la ragione principale sarebbe quella di separare l'interfaccia dall'implementazione. L'intestazione dichiara "cosa" farà una classe (o qualunque cosa venga implementata), mentre il file cpp definisce "come" eseguirà tali funzionalità.
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 cambia qualcosa nell'implementazione.
Non è perfetto e di solito ricorrere a tecniche come Idiom Pimpl per separare correttamente interfaccia e implementazione, ma è un buon inizio.
Una compilazione in C ++ viene eseguita in 2 fasi principali:
La prima è la compilazione di file di testo "sorgente" in file "oggetto" binari: 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 generalmente compilato in un file "oggetto" .OBJ o .O.
Il secondo è il collegamento di tutti i file "oggetto" e, quindi, la creazione del file binario finale (una libreria o un eseguibile).
Dove si colloca l'HPP in tutto questo processo?
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 sapere che esiste "doSomethingElse" ... 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 hai C.CPP che utilizza lo stesso simbolo, copia / incolla la dichiarazione ...
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
}
include
funziona?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();
... la fonte B.CPP:
// B.CPP
#include "A.HPP"
void doSomething()
{
// Etc.
}
... diventerà dopo l'inclusione:
// B.CPP
void someFunction();
void someOtherFunction();
void doSomething()
{
// Etc.
}
Nel caso attuale, ciò non è necessario e B.HPP ha la doSomethingElse
dichiarazione di funzione e B.CPP ha la doSomethingElse
definizione di funzione (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" per una fonte includere di default la sua intestazione.
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 le protezioni delle intestazioni 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_
o anche più semplice
#pragma once
// The declarations in the B.hpp file
You still have to copy paste the signature from header file to cpp file, don't you?
nessuna necessità. Finché il CPP "include" l'HPP, il precompilatore eseguirà automaticamente il copia-incolla del contenuto del file HPP nel file CPP. Ho aggiornato la risposta per chiarirlo.
While compiling A.cpp, compiler knows the types of arguments and return value of doSomethingElse from the call itself
. No, non lo fa. Conosce solo i tipi forniti dall'utente, che per metà del tempo non si preoccuperanno nemmeno di leggere il valore restituito. Quindi, si verificano conversioni implicite. E poi, quando hai il codice foo(bar)
:, non puoi nemmeno essere sicuro che foo
sia una funzione. Quindi il compilatore deve avere accesso alle informazioni nelle intestazioni per decidere se l'origine viene compilata correttamente o no ... Quindi, una volta compilato il codice, il linker collegherà semplicemente le chiamate delle funzioni.
Seems, they're just a pretty ugly arbitrary design.
: Se C ++ fosse stato creato nel 2012, anzi. Ma ricorda che il C ++ fu costruito su C negli anni '80 e, a quel tempo, i vincoli erano piuttosto diversi a quel tempo (IIRC, fu deciso ai fini dell'adozione di mantenere gli stessi linker di quelli di C).
foo(bar)
è una funzione - se viene ottenuta come puntatore? In effetti, parlando di cattivo design, biasimo C, non C ++. Non mi piacciono davvero alcuni vincoli della C pura, come avere file header o avere funzioni che restituiscono un solo valore, pur prendendo più argomenti sull'input (non sembra naturale che input e output si comportino in modo simile ; perché argomenti multipli, ma uscita singola?) :)
Why can't I be sure, that foo(bar) is a function
foo potrebbe essere un tipo, quindi si chiamerebbe un costruttore di classi. In fact, speaking of bad design, I blame C, not C++
: Posso dare la colpa a C per molte cose, ma essere stato progettato negli anni '70 non sarà uno di questi. Ancora una volta, i vincoli di quel tempo ... such as having header files or having functions return one and only one value
: le tuple possono aiutare a mitigarlo, oltre a passare argomenti per riferimento. Ora, quale sarebbe la sintassi per recuperare più valori restituiti e varrebbe la pena cambiare la lingua?
Perché C, dove ha avuto origine il concetto, 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.
Perché in C ++, il codice eseguibile finale non contiene alcuna informazione sul simbolo, è un codice macchina più o meno 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é 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 in cui queste informazioni condivise potevano essere archiviate.
La maggior parte delle lingue dopo C / C ++ memorizza queste informazioni nell'output (bytecode 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:
Rispondendo alla risposta di MadKeithV ,
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 cambia qualcosa nell'implementazione.
Un altro motivo è che un'intestazione fornisce un ID univoco per ogni classe.
Quindi se abbiamo qualcosa del genere
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 ...