Mentre puoi includere .cpp
file come hai detto, questa è una cattiva idea.
Come hai detto, le dichiarazioni appartengono ai file di intestazione. Questi non causano problemi quando sono inclusi in più unità di compilazione perché non includono le implementazioni. Includere più volte la definizione di una funzione o di un membro della classe causerà un problema (ma non sempre) perché il linker verrà confuso e genererà un errore.
Ciò che dovrebbe accadere è che ogni file .cpp
include 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 qualsiasi dichiarazione di cui ha bisogno per compilare le definizioni che contiene. Tiene traccia delle funzioni e delle classi a cui fa riferimento ma non contiene, quindi il linker può risolverle in seguito quando combina il codice oggetto in un eseguibile o in una libreria.
Esempio
-
Foo.h
- > contiene dichiarazione (interfaccia) per la classe Foo.
-
Foo.cpp
- > contiene la definizione (implementazione) per la classe Foo.
-
Main.cpp
- > contiene il metodo principale, il punto di ingresso del programma. Questo codice crea un'istanza di Foo e lo usa.
Sia Foo.cpp
che Main.cpp
devono includere Foo.h
. Foo.cpp
ne ha bisogno perché sta definendo il codice che supporta l'interfaccia di classe, quindi ha bisogno di sapere cos'è l'interfaccia. Main.cpp
ne ha bisogno perché sta creando un Foo e invocando il suo comportamento, quindi deve sapere qual è il comportamento, la dimensione di un Foo in memoria e come trovarne le funzioni, ecc. ma non ha bisogno dell'attuazione effettiva ancora.
Il compilatore genererà Foo.o
da Foo.cpp
che contiene tutto il codice classe Foo in forma compilata. Genera anche Main.o
che include il metodo principale ei riferimenti non risolti 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 Foo non risolti in Main.o
ma vede che Foo.o
contiene i simboli necessari, quindi "collega i punti" per così dire. Una chiamata di funzione in 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 file Foo.cpp
in Main.cpp
, ci sarebbero due definizioni della classe Foo. Il linker vedrebbe questo e dirà "Non so quale scegliere, quindi questo è un errore". La fase di compilazione avrebbe avuto successo, ma il collegamento non lo avrebbe fatto. (A meno che tu non compili solo Foo.cpp
, ma perché è in un file .cpp
separato?)
Infine, l'idea di diversi tipi di file è irrilevante per un compilatore C / C ++. Compila "file di testo" che si spera contengano codice valido per la lingua desiderata. A volte può essere in grado di dire la lingua in base all'estensione del file. Ad esempio, compila un file .c
senza opzioni del 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 oggetto ( .o
) se contiene codice C ++ valido in formato di 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.