Por que o C ++ possui arquivos de cabeçalho e arquivos .cpp?
Por que o C ++ possui arquivos de cabeçalho e arquivos .cpp?
Respostas:
Bem, o principal motivo seria separar a interface da implementação. O cabeçalho declara "o que" uma classe (ou o que está sendo implementado) fará, enquanto o arquivo cpp define "como" ele executará esses recursos.
Isso reduz as dependências para que o código que usa o cabeçalho não precise necessariamente conhecer todos os detalhes da implementação e quaisquer outras classes / cabeçalhos necessários apenas para isso. Isso reduzirá os tempos de compilação e também a quantidade de recompilação necessária quando algo na implementação for alterado.
Não é perfeito, e você geralmente recorreria a técnicas como o Pimpl Idiom para separar adequadamente a interface e a implementação, mas é um bom começo.
Uma compilação em C ++ é feita em 2 fases principais:
O primeiro é a compilação de arquivos de texto "de origem" em arquivos binários de "objeto": O arquivo CPP é o arquivo compilado e é compilado sem qualquer conhecimento sobre os outros arquivos CPP (ou mesmo bibliotecas), a menos que seja fornecido por declaração bruta ou inclusão de cabeçalho. O arquivo CPP geralmente é compilado em um arquivo "objeto ".OBJ ou .O.
A segunda é a ligação de todos os arquivos "objetos" e, portanto, a criação do arquivo binário final (uma biblioteca ou um executável).
Onde o HPP se encaixa em todo esse processo?
A compilação de cada arquivo CPP é independente de todos os outros arquivos CPP, o que significa que se A.CPP precisar de um símbolo definido em B.CPP, como:
// A.CPP
void doSomething()
{
doSomethingElse(); // Defined in B.CPP
}
// B.CPP
void doSomethingElse()
{
// Etc.
}
Ele não será compilado porque o A.CPP não tem como saber que "doSomethingElse" existe ... A menos que haja uma declaração no A.CPP, como:
// A.CPP
void doSomethingElse() ; // From B.CPP
void doSomething()
{
doSomethingElse() ; // Defined in B.CPP
}
Em seguida, se você tiver o C.CPP que usa o mesmo símbolo, copie / cole a declaração ...
Sim, há um problema. As cópias / pastas são perigosas e difíceis de manter. O que significa que seria legal se tivéssemos alguma maneira de NÃO copiar / colar e ainda declarar o símbolo ... Como podemos fazer isso? Pela inclusão de algum arquivo de texto, que geralmente é sufixado por .h, .hxx, .h ++ ou, o meu preferido para arquivos C ++, .hpp:
// B.HPP (here, we decided to declare every symbol defined in B.CPP)
void doSomethingElse() ;
// A.CPP
#include "B.HPP"
void doSomething()
{
doSomethingElse() ; // Defined in B.CPP
}
// B.CPP
#include "B.HPP"
void doSomethingElse()
{
// Etc.
}
// C.CPP
#include "B.HPP"
void doSomethingAgain()
{
doSomethingElse() ; // Defined in B.CPP
}
include
funciona?A inclusão de um arquivo irá, em essência, analisar e depois copiar e colar seu conteúdo no arquivo CPP.
Por exemplo, no código a seguir, com o cabeçalho A.HPP:
// A.HPP
void someFunction();
void someOtherFunction();
... a fonte B.CPP:
// B.CPP
#include "A.HPP"
void doSomething()
{
// Etc.
}
... se tornará após a inclusão:
// B.CPP
void someFunction();
void someOtherFunction();
void doSomething()
{
// Etc.
}
No caso atual, isso não é necessário e B.HPP possui a doSomethingElse
declaração da função e B.CPP possui a doSomethingElse
definição da função (que é, por si só, uma declaração). Porém, em um caso mais geral, em que o B.HPP é usado para declarações (e código embutido), não pode haver uma definição correspondente (por exemplo, enumerações, estruturas simples, etc.), portanto, a inclusão pode ser necessária se o B.CPP usa essas declarações de B.HPP. Em suma, é "bom gosto" que uma fonte inclua por padrão seu cabeçalho.
O arquivo de cabeçalho é, portanto, necessário, porque o compilador C ++ não pode procurar apenas declarações de símbolo e, portanto, você deve ajudá-lo incluindo essas declarações.
Uma última palavra: você deve colocar os protetores de cabeçalho em torno do conteúdo dos arquivos HPP, para garantir que várias inclusões não quebrem nada, mas, apesar de tudo, acredito que o principal motivo da existência de arquivos HPP esteja explicado acima.
#ifndef B_HPP_
#define B_HPP_
// The declarations in the B.hpp file
#endif // B_HPP_
ou ainda mais simples
#pragma once
// The declarations in the B.hpp file
You still have to copy paste the signature from header file to cpp file, don't you?
Não há necessidade. Desde que o CPP "inclua" o HPP, o pré-compilador fará automaticamente a cópia e colar do conteúdo do arquivo HPP no arquivo CPP. Eu atualizei a resposta para esclarecer isso.
While compiling A.cpp, compiler knows the types of arguments and return value of doSomethingElse from the call itself
. Não, não faz. Ele conhece apenas os tipos fornecidos pelo usuário, que, na metade do tempo, nem se preocupam em ler o valor de retorno. Então, conversões implícitas acontecem. E então, quando você tem o código:, foo(bar)
você nem pode ter certeza de que foo
é uma função. Portanto, o compilador precisa ter acesso às informações nos cabeçalhos para decidir se a fonte é compilada corretamente ou não ... Então, uma vez que o código é compilado, o vinculador vincula apenas as chamadas de funções.
Seems, they're just a pretty ugly arbitrary design.
: Se C ++ tivesse sido criado em 2012, de fato. Mas lembre-se de que o C ++ foi construído sobre o C na década de 1980 e, na época, as restrições eram bem diferentes na época (IIRC, foi decidido para fins de adoção manter os mesmos vinculadores que os do C).
foo(bar)
é uma função - se é obtida como um ponteiro? De fato, por falar em design ruim, culpo C, não C ++. Eu realmente não gosto de algumas restrições de C puro, como ter arquivos de cabeçalho ou funções retornar um e apenas um valor, enquanto utiliza vários argumentos na entrada (não é natural que uma entrada e uma saída se comportem de maneira semelhante ; por isso vários argumentos, mas única saída) :)?
Why can't I be sure, that foo(bar) is a function
foo poderia ser um tipo, então você teria um construtor de classe chamado. In fact, speaking of bad design, I blame C, not C++
: Eu posso culpar C por muitas coisas, mas ter sido projetado nos anos 70 não será um deles. Novamente, as restrições daquele tempo ... such as having header files or having functions return one and only one value
: as tuplas podem ajudar a atenuar isso, além de transmitir argumentos por referência. Agora, qual seria a sintaxe para recuperar vários valores retornados e valeria a pena mudar o idioma?
Como C, onde o conceito se originou, tem 30 anos e, naquela época, era a única maneira viável de vincular código de vários arquivos.
Hoje, é um hack horrível que destrói totalmente o tempo de compilação em C ++, causa inúmeras dependências desnecessárias (porque as definições de classe em um arquivo de cabeçalho expõem muitas informações sobre a implementação) e assim por diante.
Como no C ++, o código executável final não carrega nenhuma informação de símbolo, é um código de máquina mais ou menos puro.
Portanto, você precisa de uma maneira de descrever a interface de um pedaço de código, separado do próprio código. Esta descrição está no arquivo de cabeçalho.
Porque o C ++ os herdou de C. Infelizmente.
Porque as pessoas que criaram o formato da biblioteca não queriam "desperdiçar" espaço para informações raramente usadas, como macros do pré-processador C e declarações de função.
Como você precisa dessas informações para informar ao seu compilador "esta função estará disponível mais tarde quando o vinculador estiver executando seu trabalho", eles precisaram criar um segundo arquivo onde essas informações compartilhadas poderiam ser armazenadas.
A maioria das linguagens após o C / C ++ armazena essas informações na saída (Java bytecode, por exemplo) ou elas não usam um formato pré-compilado, sempre são distribuídas no formato de origem e compilam em tempo real (Python, Perl).
É a maneira pré-processador de declarar interfaces. Você coloca a interface (declarações de método) no arquivo de cabeçalho e a implementação no cpp. Os aplicativos que usam sua biblioteca precisam apenas conhecer a interface, que eles podem acessar através de #include.
Frequentemente, você desejará ter uma definição de interface sem precisar enviar o código inteiro. Por exemplo, se você tiver uma biblioteca compartilhada, enviaria um arquivo de cabeçalho com ele, que define todas as funções e símbolos usados na biblioteca compartilhada. Sem os arquivos de cabeçalho, você precisaria enviar a fonte.
Dentro de um único projeto, os arquivos de cabeçalho são usados, IMHO, para pelo menos dois propósitos:
Respondendo à resposta de MadKeithV ,
Isso reduz as dependências para que o código que usa o cabeçalho não precise necessariamente conhecer todos os detalhes da implementação e quaisquer outras classes / cabeçalhos necessários apenas para isso. Isso reduzirá o tempo de compilação e também a quantidade de recompilação necessária quando algo na implementação for alterado.
Outro motivo é que um cabeçalho fornece um ID exclusivo para cada classe.
Então, se tivermos algo como
class A {..};
class B : public A {...};
class C {
include A.cpp;
include B.cpp;
.....
};
Teremos erros, quando tentarmos construir o projeto, já que A faz parte de B, com cabeçalhos evitaríamos esse tipo de dor de cabeça ...