Mentre puoi includere .cppfile come hai già detto, questa è una cattiva idea.
Come hai detto, le dichiarazioni appartengono ai file di intestazione. Questi non causano problemi se inclusi in più unità di compilazione perché non includono implementazioni. Includere una definizione di una funzione o di un membro della classe più volte normalmente causerà un problema (ma non sempre) perché il linker verrà confuso e genererà un errore.
Ciò che dovrebbe accadere è che ogni .cppfile includa definizioni per un sottoinsieme del programma, come una classe, un gruppo di funzioni logicamente organizzato, variabili statiche globali (usare con parsimonia se non del tutto), ecc.
Ogni unità di compilazione ( .cppfile) include quindi tutte le dichiarazioni necessarie per compilare le definizioni in essa contenute. Tiene traccia delle funzioni e delle classi a cui fa riferimento ma che non contiene, quindi il linker può risolverli in un secondo momento quando combina il codice oggetto in un eseguibile o in una libreria.
Esempio
Foo.h -> contiene la dichiarazione (interfaccia) per la classe Foo.
Foo.cpp -> contiene la definizione (implementazione) per la classe Foo.
Main.cpp-> contiene il metodo principale, punto di ingresso del programma. Questo codice crea un'istanza di Foo e la utilizza.
Entrambi Foo.cppe Main.cppdevono essere inclusi Foo.h. Foo.cppne ha bisogno perché sta definendo il codice che supporta l'interfaccia di classe, quindi ha bisogno di sapere quale sia l'interfaccia. Main.cppne ha bisogno perché sta creando un Foo e invocando il suo comportamento, quindi deve sapere qual è quel comportamento, la dimensione di un Foo in memoria e come trovare le sue funzioni, ecc. ma non ha ancora bisogno dell'attuazione effettiva.
Il compilatore genererà Foo.oda Foo.cppcui contiene tutto il codice della classe Foo in forma compilata. Genera anche Main.oche include il metodo principale e riferimenti irrisolti alla classe Foo.
Ora arriva il linker, che combina i due file oggetto Foo.oe Main.oin un file eseguibile. Vede i riferimenti irrisolti di Foo Main.oma vede che Foo.ocontiene i simboli necessari, quindi "collega i punti" per così dire. Una chiamata di funzione Main.oè ora connessa alla posizione effettiva del codice compilato, quindi in fase di esecuzione il programma può passare alla posizione corretta.
Se avessi incluso il Foo.cppfile Main.cpp, ci sarebbero due definizioni della classe Foo. Il linker lo vedrebbe e dire "Non so quale scegliere, quindi questo è un errore". La fase di compilazione avrebbe esito positivo, ma il collegamento no. (A meno che non si compili Foo.cppma perché è in un .cppfile separato ?)
Infine, l'idea di diversi tipi di file è irrilevante per un compilatore C / C ++. Compila "file di testo" che si spera contengano un codice valido per la lingua desiderata. A volte potrebbe essere in grado di dire la lingua in base all'estensione del file. Ad esempio, compila un .cfile senza opzioni di compilatore e assumerà C, mentre un'estensione .cco .cppgli direbbe di assumere C ++. Tuttavia, posso facilmente dire a un compilatore di compilare un file .ho anche .docxcome C ++ ed emetterà un .ofile object ( ) se contiene un codice C ++ valido in formato testo normale. Queste estensioni sono più a beneficio del programmatore. Se vedo Foo.he Foo.cpp, presumo immediatamente che il primo contenga la dichiarazione della classe e il secondo contenga la definizione.