Embora você possa incluir .cpp
arquivos como você mencionou, é uma má idéia.
Como você mencionou, as declarações pertencem aos arquivos de cabeçalho. Isso não causa problemas quando incluído em várias unidades de compilação, porque não inclui implementações. Incluir a definição de uma função ou membro da classe várias vezes normalmente causa um problema (mas nem sempre) porque o vinculador fica confuso e gera um erro.
O que deve acontecer é que cada .cpp
arquivo inclui definições para um subconjunto do programa, como uma classe, grupo de funções logicamente organizado, variáveis estáticas globais (use com moderação, se houver), etc.
Cada unidade de compilação ( .cpp
arquivo) inclui as declarações necessárias para compilar as definições que ela contém. Ele monitora as funções e as classes mencionadas, mas não contém, para que o vinculador possa resolvê-las mais tarde, quando combinar o código do objeto em um executável ou biblioteca.
Exemplo
Foo.h
-> contém declaração (interface) para a classe Foo.
Foo.cpp
-> contém definição (implementação) para a classe Foo.
Main.cpp
-> contém o método principal, ponto de entrada do programa. Esse código instancia um Foo e o usa.
Ambos Foo.cpp
e Main.cpp
precisam incluir Foo.h
. Foo.cpp
precisa porque está definindo o código que suporta a interface da classe, portanto, precisa saber o que é essa interface. Main.cpp
precisa dele porque está criando um Foo e invocando seu comportamento; portanto, ele precisa saber qual é esse comportamento, o tamanho de um Foo na memória e como encontrar suas funções, etc., mas ainda não precisa da implementação real.
O compilador irá gerar a Foo.o
partir do Foo.cpp
qual contém todo o código da classe Foo no formato compilado. Também gera o Main.o
que inclui o método principal e as referências não resolvidas da classe Foo.
Agora vem o vinculador, que combina os dois arquivos de objeto Foo.o
e Main.o
em um arquivo executável. Ele vê as referências não resolvidas de Foo, Main.o
mas vê que Foo.o
contém os símbolos necessários; portanto, "liga os pontos", por assim dizer. Uma chamada de função Main.o
agora está conectada ao local real do código compilado, portanto, em tempo de execução, o programa pode ir para o local correto.
Se você incluísse o Foo.cpp
arquivo Main.cpp
, haveria duas definições da classe Foo. O vinculador veria isso e diria "Não sei qual escolher, então isso é um erro". A etapa de compilação seria bem-sucedida, mas a vinculação não. (A menos que você apenas não compile, Foo.cpp
mas por que está em um .cpp
arquivo separado ?)
Finalmente, a ideia de diferentes tipos de arquivo é irrelevante para um compilador C / C ++. Ele compila "arquivos de texto" que esperançosamente contêm código válido para o idioma desejado. Às vezes, é possível saber o idioma com base na extensão do arquivo. Por exemplo, compile um .c
arquivo sem opções de compilador e ele assumirá C, enquanto uma extensão .cc
ou .cpp
diria para ele assumir C ++. No entanto, posso dizer facilmente a um compilador para compilar um arquivo .h
ou mesmo .docx
como C ++, e ele emitirá um .o
arquivo object ( ) se contiver código C ++ válido em formato de texto sem formatação. Essas extensões são mais para o benefício do programador. Se eu vir Foo.h
e Foo.cpp
, presumo imediatamente que o primeiro contém a declaração da classe e o segundo contém a definição.