Eu realmente nunca entendi porque o C ++ precisa de um arquivo de cabeçalho separado com as mesmas funções do arquivo .cpp. Isso torna muito difícil criar classes e refatorá-las, além de adicionar arquivos desnecessários ao projeto. E há o problema de incluir arquivos de cabeçalho, mas verificar explicitamente se já foi incluído.
O C ++ foi ratificado em 1998, então, por que é projetado dessa maneira? Quais são as vantagens de ter um arquivo de cabeçalho separado?
Questão a seguir:
Como o compilador encontra o arquivo .cpp com o código, quando tudo o que incluo é o arquivo .h? Ele pressupõe que o arquivo .cpp tenha o mesmo nome que o arquivo .h, ou ele realmente procura todos os arquivos na árvore de diretórios?
c++
language-design
Marius
fonte
fonte
Respostas:
Você parece estar perguntando sobre a separação de definições de declarações, embora existam outros usos para arquivos de cabeçalho.
A resposta é que o C ++ não "precisa" disso. Se você marcar tudo em linha (de qualquer maneira, automático para as funções de membro definidas em uma definição de classe), não haverá necessidade de separação. Você pode apenas definir tudo nos arquivos de cabeçalho.
Os motivos pelos quais você pode querer separar são:
Se sua pergunta mais geral é "por que o C ++ não é idêntico ao Java?", Então eu tenho que perguntar: "por que você está escrevendo C ++ em vez de Java?" ;-p
Mais seriamente, porém, o motivo é que o compilador C ++ não pode simplesmente acessar outra unidade de tradução e descobrir como usar seus símbolos, da maneira que o javac pode e faz. O arquivo de cabeçalho é necessário para declarar ao compilador o que ele pode esperar estar disponível no momento do link.
O mesmo
#include
acontece com uma substituição textual direta. Se você definir tudo nos arquivos de cabeçalho, o pré-processador acaba criando uma enorme cópia e colar de todos os arquivos de origem no seu projeto, e alimentando-o no compilador. O fato de o padrão C ++ ter sido ratificado em 1998 não tem nada a ver com isso, é o fato de que o ambiente de compilação para C ++ se baseia tão intimamente no de C.Convertendo meus comentários para responder à sua pergunta de acompanhamento:
Não, pelo menos não no momento em que compila o código que usou o arquivo de cabeçalho. As funções às quais você está vinculando nem precisam ser gravadas ainda, não importa o compilador saber em que
.cpp
arquivo elas estarão. Tudo o que o código de chamada precisa saber no momento da compilação é expresso na declaração da função. No momento do link, você fornecerá uma lista de.o
arquivos, ou bibliotecas estáticas ou dinâmicas, e o cabeçalho em vigor é uma promessa de que as definições das funções estarão lá em algum lugar.fonte
C ++ faz dessa maneira porque C fez dessa maneira, então a verdadeira questão é por que C fez dessa maneira? A Wikipedia fala um pouco disso.
fonte
Algumas pessoas consideram os arquivos de cabeçalho uma vantagem:
Por fim, o sistema de cabeçalho é um artefato dos anos 70 quando o C foi projetado. Naquela época, os computadores tinham muito pouca memória e manter o módulo inteiro na memória simplesmente não era uma opção. Um compilador teve que começar a ler o arquivo na parte superior e prosseguir linearmente pelo código-fonte. O mecanismo do cabeçalho permite isso. O compilador não precisa considerar outras unidades de tradução, apenas precisa ler o código de cima para baixo.
E o C ++ manteve esse sistema para compatibilidade com versões anteriores.
Hoje não faz sentido. É ineficiente, propenso a erros e complicado demais. Existem maneiras muito melhores de separar interface e implementação, se esse era o objetivo.
No entanto, uma das propostas para o C ++ 0x era adicionar um sistema de módulo adequado, permitindo a compilação de código semelhante ao .NET ou Java, em módulos maiores, tudo de uma só vez e sem cabeçalhos. Essa proposta não foi aprovada no C ++ 0x, mas acredito que ainda esteja na categoria "adoraríamos fazer isso mais tarde". Talvez em um TR2 ou similar.
fonte
Para o meu entendimento (limitado - normalmente não sou desenvolvedor de C), isso está enraizado no C. Lembre-se de que o C não sabe o que são classes ou namespaces, é apenas um programa longo. Além disso, as funções precisam ser declaradas antes de você usá-las.
Por exemplo, o seguinte deve dar um erro do compilador:
O erro deve ser que "SomeOtherFunction não é declarado" porque você o chama antes da declaração. Uma maneira de corrigir isso é movendo SomeOtherFunction acima de SomeFunction. Outra abordagem é declarar a assinatura das funções primeiro:
Isso permite que o compilador saiba: Procure em algum lugar no código, existe uma função chamada SomeOtherFunction que retorna nula e não aceita parâmetros. Portanto, se você encontrar um código que tente chamar SomeOtherFunction, não entre em pânico e procure por ele.
Agora, imagine que você tenha SomeFunction e SomeOtherFunction em dois arquivos .c diferentes. Você precisa # incluir "SomeOther.c" em Some.c. Agora, adicione algumas funções "particulares" ao SomeOther.c. Como C não conhece funções privadas, essa função também estaria disponível em Some.c.
É aqui que entram os arquivos .h: Eles especificam todas as funções (e variáveis) que você deseja 'Exportar' de um arquivo .c que pode ser acessado em outros arquivos .c. Dessa forma, você ganha algo como um escopo Público / Privado. Além disso, você pode fornecer esse arquivo .h para outras pessoas sem precisar compartilhar seu código-fonte - os arquivos .h também funcionam com arquivos .lib compilados.
Portanto, o principal motivo é realmente a conveniência, a proteção do código-fonte e a dissociação entre as partes do aplicativo.
Isso foi C embora. O C ++ introduziu Classes e modificadores privado / público; portanto, enquanto você ainda pode perguntar se eles são necessários, o C ++ AFAIK ainda requer declaração de funções antes de usá-los. Além disso, muitos desenvolvedores de C ++ também são ou eram devleopers de C e assumiram seus conceitos e hábitos para C ++ - por que mudar o que não está quebrado?
fonte
Primeira vantagem: se você não possui arquivos de cabeçalho, seria necessário incluir arquivos de origem em outros arquivos de origem. Isso faria com que os arquivos inclusos fossem compilados novamente quando o arquivo incluído fosse alterado.
Segunda vantagem: Permite compartilhar as interfaces sem compartilhar o código entre diferentes unidades (diferentes desenvolvedores, equipes, empresas etc.)
fonte
A necessidade de arquivos de cabeçalho resulta das limitações que o compilador possui para saber sobre as informações de tipo para funções e ou variáveis em outros módulos. O programa ou a biblioteca compilada não inclui as informações de tipo exigidas pelo compilador para vincular a qualquer objeto definido em outras unidades de compilação.
Para compensar essa limitação, C e C ++ permitem declarações e essas declarações podem ser incluídas em módulos que as utilizam com a ajuda da diretiva #include do pré-processador.
Idiomas como Java ou C #, por outro lado, incluem as informações necessárias para a ligação na saída do compilador (arquivo de classe ou assembly). Portanto, não é mais necessário manter declarações independentes a serem incluídas pelos clientes de um módulo.
O motivo para as informações de ligação não serem incluídas na saída do compilador é simples: não é necessário no tempo de execução (qualquer verificação de tipo ocorre no tempo de compilação). Seria apenas um desperdício de espaço. Lembre-se de que o C / C ++ vem de uma época em que o tamanho de um executável ou biblioteca importava bastante.
fonte
O C ++ foi projetado para adicionar recursos modernos da linguagem de programação à infraestrutura C, sem alterar desnecessariamente nada sobre o C que não fosse especificamente sobre a própria linguagem.
Sim, neste momento (10 anos após o primeiro padrão C ++ e 20 anos após começar a aumentar seriamente o uso), é fácil perguntar por que ele não possui um sistema de módulos adequado. Obviamente, qualquer nova linguagem projetada hoje não funcionaria como C ++. Mas esse não é o objetivo do C ++.
O objetivo do C ++ é ser evolutivo, uma continuação suave da prática existente, adicionando apenas novos recursos sem (com muita freqüência) quebrar coisas que funcionam adequadamente para sua comunidade de usuários.
Isso significa que isso torna algumas coisas mais difíceis (especialmente para as pessoas que estão iniciando um novo projeto), e algumas coisas mais fáceis (especialmente para as que mantêm o código existente) do que outras línguas.
Então, em vez de esperar que o C ++ se transforme em C # (o que seria inútil, pois já temos C #), por que não escolher a ferramenta certa para o trabalho? Eu mesmo, escrevo partes significativas de novas funcionalidades em uma linguagem moderna (por acaso uso C #) e tenho uma grande quantidade de C ++ existente que estou mantendo em C ++, porque não haveria valor real em reescrevê-lo tudo. Eles se integram muito bem de qualquer maneira, por isso é praticamente indolor.
fonte
Bem, o C ++ foi ratificado em 1998, mas estava em uso por muito mais tempo do que isso, e a ratificação estava principalmente estabelecendo o uso atual, em vez de impor estrutura. E como o C ++ foi baseado em C e C possui arquivos de cabeçalho, o C ++ também os possui.
O principal motivo dos arquivos de cabeçalho é permitir a compilação separada de arquivos e minimizar as dependências.
Digamos que tenho foo.cpp e desejo usar o código dos arquivos bar.h / bar.cpp.
Posso # incluir "bar.h" em foo.cpp e, em seguida, programar e compilar foo.cpp, mesmo que o bar.cpp não exista. O arquivo de cabeçalho atua como uma promessa ao compilador de que as classes / funções em bar.h existirão em tempo de execução e já tem tudo o que precisa saber.
Obviamente, se as funções em bar.h não tiverem corpos quando tento vincular meu programa, ele não será vinculado e receberei um erro.
Um efeito colateral é que você pode fornecer aos usuários um arquivo de cabeçalho sem revelar seu código-fonte.
Outra é que, se você alterar a implementação do seu código no arquivo * .cpp, mas não alterar o cabeçalho, precisará compilar o arquivo * .cpp em vez de tudo o que o usa. Obviamente, se você colocar muita implementação no arquivo de cabeçalho, isso se tornará menos útil.
fonte
Ele não precisa de um arquivo de cabeçalho separado com as mesmas funções que no main. É necessário apenas se você desenvolver um aplicativo usando vários arquivos de código e se usar uma função que não foi declarada anteriormente.
É realmente um problema de escopo.
fonte
Na verdade, os arquivos de cabeçalho se tornam muito úteis ao examinar os programas pela primeira vez, ao verificar os arquivos de cabeçalho (usando apenas um editor de texto) fornece uma visão geral da arquitetura do programa, diferente de outros idiomas em que você precisa usar ferramentas sofisticadas para exibir classes e suas funções de membro.
fonte
Eu acho que a verdadeira (historical) razão por trás de arquivos de cabeçalho estava fazendo como mais fácil para desenvolvedores de compilador ... mas, em seguida, arquivos de cabeçalho não dar vantagens.
Verifique este post anterior para mais discussões ...
fonte
Bem, você pode perfeitamente desenvolver C ++ sem arquivos de cabeçalho. De fato, algumas bibliotecas que usam modelos intensivamente não usam o paradigma dos arquivos de cabeçalho / código (consulte a seção boost). Mas no C / C ++ você não pode usar algo que não está declarado. Uma maneira prática de lidar com isso é usar arquivos de cabeçalho. Além disso, você obtém a vantagem de compartilhar interface sem código / implementação de compartilhamento. E acho que isso não foi previsto pelos criadores de C: quando você usa arquivos de cabeçalho compartilhados, deve usar o famoso:
esse não é realmente um recurso de idioma, mas uma maneira prática de lidar com a inclusão múltipla.
Então, acho que quando C foi criado, os problemas com a declaração de encaminhamento foram subestimados e agora, ao usar uma linguagem de alto nível como C ++, temos que lidar com esse tipo de coisa.
Outro fardo para nós, usuários pobres de C ++ ...
fonte
Se você deseja que o compilador descubra símbolos definidos em outros arquivos automaticamente, você precisa forçar o programador a colocar esses arquivos em locais predefinidos (como a estrutura de pacotes Java determina a estrutura de pastas do projeto). Eu prefiro arquivos de cabeçalho. Além disso, você precisaria das fontes de bibliotecas usadas ou de alguma maneira uniforme para colocar as informações necessárias pelo compilador nos binários.
fonte