Por que precisamos incluir os arquivos .h
e .cpp
enquanto podemos fazê-lo funcionar apenas incluindo o .cpp
arquivo?
Por exemplo: criando uma file.h
declaração contendo, em seguida, criando uma file.cpp
definição contendo e incluindo ambas em main.cpp
.
Como alternativa: criar uma file.cpp
declaração / definições contendo (sem protótipos) incluindo-a main.cpp
.
Ambos trabalham para mim. Não vejo a diferença. Talvez algumas dicas sobre o processo de compilação e vinculação possam ajudar.
Respostas:
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
eMain.cpp
precisam incluirFoo.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 doFoo.cpp
qual contém todo o código da classe Foo no formato compilado. Também gera oMain.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
eMain.o
em um arquivo executável. Ele vê as referências não resolvidas de Foo,Main.o
mas vê queFoo.o
contém os símbolos necessários; portanto, "liga os pontos", por assim dizer. Uma chamada de funçãoMain.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
arquivoMain.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 virFoo.h
eFoo.cpp
, presumo imediatamente que o primeiro contém a declaração da classe e o segundo contém a definição.fonte
Foo.cpp
naMain.cpp
você não precisa incluir o.h
arquivo, você tem menos um arquivo, você ainda ganhar no código de divisão em arquivos separados para facilitar a leitura, e seu comando de compilação é mais simples. Embora compreenda a importância de cabeçalho de arquivos Eu não acho que é issoIf you had included the Foo.cpp file in Main.cpp, there would be two definitions of class Foo.
Frase mais importante sobre a questão.Leia mais sobre o papel do pré-processador C e C ++ , que é conceitualmente a primeira "fase" do compilador C ou C ++ (historicamente, era um programa separado
/lib/cpp
; agora, por motivos de desempenho, ele é integrado ao compilador propriamente ditocc1
oucc1plus
). Leia em particular a documentação docpp
pré-processador GNU . Portanto, na prática, o compilador pré-processa primeiro sua unidade de compilação (ou unidade de tradução ) e depois trabalha no formulário pré-processado.Você provavelmente sempre precisará incluir o arquivo de cabeçalho,
file.h
se ele contiver (conforme exigido por convenções e hábitos ):typedef
,struct
,class
etc, ...)static inline
funçõesObserve que é uma questão de convenções (e conveniência) colocá-las em um arquivo de cabeçalho.
Obviamente, sua implementação
file.cpp
precisa de todas as opções acima, então,#include "file.h"
primeiro você precisa .Esta é uma convenção (mas muito comum). Você pode evitar os arquivos de cabeçalho e copiar e colar seu conteúdo nos arquivos de implementação (ou seja, unidades de tradução). Mas você não deseja isso (exceto, talvez, se seu código C ou C ++ for gerado automaticamente; você poderá fazer o programa gerador copiar e colar, imitando o papel do pré-processador).
O ponto é que o pré-processador está executando operações apenas de texto . Você pode (em princípio) evitá-lo inteiramente copiando e colando ou substituindo-o por outro "pré-processador" ou gerador de código C (como gpp ou m4 ).
Uma questão adicional é que os padrões recentes de C (ou C ++) definem vários cabeçalhos padrão. A maioria das implementações realmente implementa esses cabeçalhos padrão como arquivos (específicos da implementação) , mas acredito que seria possível para uma implementação em conformidade implementar inclusões padrão (como
#include <stdio.h>
para C ou#include <vector>
C ++) com alguns truques de mágica (por exemplo, usando algum banco de dados ou alguns informações dentro do compilador).Se estiver usando compiladores GCC (por exemplo,
gcc
oug++
), você pode usar o-H
sinalizador para se informar sobre todas as inclusões e os-C -E
sinalizadores para obter o formulário pré-processado. Obviamente, existem muitos outros sinalizadores do compilador que afetam o pré-processamento (por exemplo,-I /some/dir/
para adicionar/some/dir/
arquivos de pesquisa incluídos e-D
para predefinir alguma macro do pré-processador, etc, etc ...).NB Versões futuras do C ++ (talvez C ++ 20 , talvez até mais tarde) podem ter módulos C ++ .
fonte
Devido ao modelo de compilação de várias unidades do C ++, você precisa de um código que apareça no seu programa apenas uma vez (definições) e de um código que apareça em cada unidade de tradução do seu programa (declarações).
Daí nasce o idioma do cabeçalho C ++. É convenção por uma razão.
Você pode despejar todo o programa em uma única unidade de tradução, mas isso apresenta problemas com a reutilização de código, teste de unidade e manipulação de dependência entre módulos. Também é apenas uma grande bagunça.
fonte
A resposta selecionada de Por que precisamos escrever um arquivo de cabeçalho? é uma explicação razoável, mas eu queria adicionar detalhes adicionais.
Parece que o racional para arquivos de cabeçalho tende a se perder ao ensinar e discutir C / C ++.
O arquivo de cabeçalhos fornece uma solução para dois problemas de desenvolvimento de aplicativos:
O C / C ++ pode variar de pequenos programas a grandes programas de arquivos de vários milhões de linhas e milhares de arquivos. O desenvolvimento de aplicativos pode variar de equipes de um desenvolvedor a centenas de desenvolvedores.
Você pode usar vários chapéus como desenvolvedor. Em particular, você pode ser o usuário de uma interface para funções e classes ou pode ser o escritor de uma interface de funções e classes.
Ao usar uma função, você precisa conhecer a interface da função, quais parâmetros usar, quais as funções retornadas e você precisa saber o que a função faz. Isso é facilmente documentado no arquivo de cabeçalho sem nunca olhar para a implementação. Quando você leu a implementação de
printf
? Compre, usamos todos os dias.Quando você é desenvolvedor de uma interface, o chapéu muda na outra direção. O arquivo de cabeçalho fornece a declaração da interface pública. O arquivo de cabeçalho define o que outra implementação precisa para usar essa interface. As informações internas e privadas dessa nova interface não são (e não devem) ser declaradas no arquivo de cabeçalho. O arquivo de cabeçalho público deve ser tudo o que alguém precisa para usar o módulo.
Para o desenvolvimento em larga escala, a compilação e a vinculação podem levar muito tempo. De muitos minutos a muitas horas (até muitos dias!). Dividir o software em interfaces (cabeçalhos) e implementações (fontes) fornece um método apenas para compilar arquivos que precisam ser compilados, em vez de reconstruir tudo.
Além disso, os arquivos de cabeçalho permitem que um desenvolvedor forneça uma biblioteca (já compilada) e também um arquivo de cabeçalho. Outros usuários da biblioteca podem nem sempre ver a implementação adequada, mas ainda podem usar a biblioteca com o arquivo de cabeçalho. Você faz isso todos os dias com a biblioteca padrão C / C ++.
Mesmo se você estiver desenvolvendo um aplicativo pequeno, o uso de técnicas de desenvolvimento de software em larga escala é um bom hábito. Mas também precisamos lembrar por que usamos esses hábitos.
fonte
Você também pode estar se perguntando por que não colocar todo o seu código em um arquivo.
A resposta mais simples é a manutenção do código.
Há momentos em que é razoável criar uma classe:
O momento em que é razoável alinhar totalmente em um cabeçalho é quando a classe é realmente uma estrutura de dados com alguns getters e setters básicos e talvez um construtor que aceita valores para inicializar seus membros.
(Os modelos, que precisam ser todos incorporados, são um problema um pouco diferente).
O outro momento para criar uma classe dentro de um cabeçalho é quando você pode usá-la em vários projetos e, especificamente, deseja evitar a vinculação em bibliotecas.
Um momento em que você pode incluir uma classe inteira em uma unidade de compilação e não expor seu cabeçalho é:
Uma classe "impl" usada apenas pela classe que implementa. É um detalhe de implementação dessa classe e externamente não é usado.
Uma implementação de uma classe base abstrata criada por algum tipo de método de fábrica que retorna um ponteiro / referência / ponteiro inteligente) para a classe base. O método de fábrica seria exposto pela própria classe não seria. (Além disso, se a classe tiver uma instância que se registra em uma tabela por meio de uma instância estática, ela nem precisa ser exposta através de uma fábrica).
Uma classe do tipo "functor".
Em outras palavras, onde você não deseja que ninguém inclua o cabeçalho.
Eu sei o que você pode estar pensando ... Se for apenas para manutenção, incluindo o arquivo cpp (ou um cabeçalho totalmente embutido), você poderá editar facilmente um arquivo para "encontrar" o código e apenas reconstruí-lo.
No entanto, "manutenibilidade" não é apenas ter o código arrumado. É uma questão de impactos de mudança. É sabido geralmente que, se você mantiver um cabeçalho inalterado e apenas alterar um
(.cpp)
arquivo de implementação , não será necessário reconstruir a outra fonte, pois não haverá efeitos colaterais.Isso torna "mais seguro" fazer essas alterações sem se preocupar com o efeito indireto, e é isso que realmente significa "manutenção".
fonte
O exemplo do Snowman precisa de uma pequena extensão para mostrar exatamente por que os arquivos .h são necessários.
Adicione outra barra de classe à peça, que também depende da classe Foo.
Foo.h -> contém declaração para a classe Foo
Foo.cpp -> contém a definição (impementação) da classe Foo
Main.cpp -> usa variável do tipo Foo.
Bar.cpp -> também usa variável do tipo Foo.
Agora, todos os arquivos cpp precisam incluir Foo.h Seria um erro incluir Foo.cpp em mais de um outro arquivo cpp. O vinculador falharia porque a classe Foo seria definida mais de uma vez.
fonte
.h
arquivos de qualquer maneira - com guardas de inclusão e é exatamente o mesmo problema que você tem com os.h
arquivos de qualquer maneira. Não é para isso que.h
servem os arquivos..cpp
arquivo.Se você escrever o código inteiro no mesmo arquivo, ele ficará feio.
Em segundo lugar, você não poderá compartilhar sua aula escrita com outras pessoas. De acordo com a engenharia de software, você deve escrever o código do cliente separadamente. O cliente não deve saber como seu programa está funcionando. Eles só precisam de saída Se você escrever o programa inteiro no mesmo arquivo, a segurança do programa será vazada.
fonte