Mentre puoi includere .cpp
file 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 .cpp
file 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 ( .cpp
file) 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.cpp
e Main.cpp
devono essere inclusi Foo.h
. Foo.cpp
ne ha bisogno perché sta definendo il codice che supporta l'interfaccia di classe, quindi ha bisogno di sapere quale sia l'interfaccia. Main.cpp
ne 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.o
da Foo.cpp
cui contiene tutto il codice della classe Foo in forma compilata. Genera anche Main.o
che include il metodo principale e riferimenti irrisolti alla classe Foo.
Ora arriva il linker, che combina i due file oggetto Foo.o
e Main.o
in un file eseguibile. Vede i riferimenti irrisolti di Foo Main.o
ma vede che Foo.o
contiene 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.cpp
file 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.cpp
ma perché è in un .cpp
file 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 .c
file senza opzioni di compilatore e assumerà C, mentre un'estensione .cc
o .cpp
gli direbbe di assumere C ++. Tuttavia, posso facilmente dire a un compilatore di compilare un file .h
o anche .docx
come C ++ ed emetterà un .o
file object ( ) se contiene un codice C ++ valido in formato testo normale. Queste estensioni sono più a beneficio del programmatore. Se vedo Foo.h
e Foo.cpp
, presumo immediatamente che il primo contenga la dichiarazione della classe e il secondo contenga la definizione.