Os arquivos de cabeçalho são realmente bons? [fechadas]

20

Acho que os arquivos de cabeçalho são úteis ao navegar nos arquivos de origem C ++, porque eles fornecem um "resumo" de todas as funções e membros de dados de uma classe. Por que tantas outras linguagens (como Ruby, Python, Java etc.) não possuem um recurso como este? Essa é uma área em que a verbosidade do C ++ é útil?

Matt Fichman
fonte
Não estou familiarizado com as ferramentas em C e C ++, mas a maioria dos IDEs / editores de código em que trabalhei têm uma exibição de "estrutura" ou "estrutura de tópicos".
Michael K
1
Os arquivos de cabeçalho não são bons - são necessários! - Veja também esta resposta no SO: stackoverflow.com/a/1319570/158483
Pete

Respostas:

31

O objetivo original dos arquivos de cabeçalho era permitir a compilação e a modularidade de passagem única em C. Ao declarar os métodos antes de serem usados, permitia apenas a passagem de compilação única. Essa era já se foi há muito tempo, graças aos nossos computadores poderosos serem capazes de fazer compilação de várias passagens sem problemas e, às vezes, até mais rápido que os compiladores C ++.

O C ++, sendo retrocompatível com o C, precisava manter os arquivos de cabeçalho, mas adicionou muitos deles, o que resultou em um design bastante problemático. Mais em FQA .

Para modularidade, os arquivos de cabeçalho eram necessários como metadados sobre o código nos módulos. Por exemplo. quais métodos (e nas classes C ++) estão disponíveis em qual biblioteca. Era óbvio que o desenvolvedor escrevesse isso, porque o tempo de compilação era caro. Atualmente, não há problema em o compilador gerar esses metadados a partir do próprio código. As linguagens Java e .NET fazem isso normalmente.

Então não. Arquivos de cabeçalho não são bons. Eles eram quando ainda tínhamos que ter o compilador e o vinculador em disquetes separados e a compilação levou 30 minutos. Hoje em dia, eles apenas atrapalham e são sinal de mau design.

Eufórico
fonte
8
Concordo com a maior parte do que você escreve, mas o último parágrafo torna as coisas um pouco simples. Os arquivos de cabeçalho ainda servem a um propósito útil para definir interfaces públicas.
Benjamin Bannier
3
@honk Era o que eu estava falando com a minha pergunta. Mas acho que os IDEs modernos (como sugerido por ElYusubov) negam isso.
precisa
5
Você pode acrescentar que o comitê do C ++ está trabalhando nos Módulos, o que tornaria o C e o C ++ capazes de funcionar sem nenhum cabeçalho OU com os cabeçalhos usados ​​de maneira mais eficiente. Isso tornaria essa resposta mais justa.
Klaim
2
@MattFichman: Gerar algum arquivo de cabeçalho é uma coisa (que a maioria dos editores / IDE de programadores pode fazer automaticamente de alguma maneira), mas o ponto principal de uma API pública é que ela precisa efetivamente ser definida e projetada por um ser humano real.
Benjamin Bannier
2
@honk Sim. Mas não há razão para isso ser definido em arquivos separados. Pode estar no mesmo código da implementação. Assim como o C # está fazendo isso.
Euphoric
17

Embora possam ser úteis para você como uma forma de documentação, o sistema em torno dos arquivos de cabeçalho é extraordinariamente ineficiente.

C foi projetado para que cada passo de compilação construa um único módulo; cada arquivo de origem é compilado em uma execução separada do compilador. Os arquivos de cabeçalho, por outro lado, são injetados nessa etapa de compilação para cada um dos arquivos de origem que os referenciam.

Isso significa que, se o seu arquivo de cabeçalho for incluído em 300 arquivos de origem, ele será analisado e compilado repetidamente, 300 vezes em separado enquanto o programa é criado. Exatamente a mesma coisa com o mesmo resultado, repetidamente. Isso é uma enorme perda de tempo e é uma das principais razões pelas quais os programas C e C ++ demoram tanto para serem criados.

Todas as línguas modernas evitam intencionalmente essa absurda pouca ineficiência. Em vez disso, normalmente em linguagens compiladas, os metadados necessários são armazenados na saída da compilação, permitindo que o arquivo compilado atue como uma espécie de referência de pesquisa rápida que descreve o conteúdo do arquivo compilado. Todos os benefícios de um arquivo de cabeçalho, criado automaticamente sem nenhum trabalho adicional de sua parte.

Como alternativa em idiomas interpretados, todo módulo carregado é mantido na memória. Fazer referência ou incluir ou exigir alguma biblioteca lerá e compilará o código-fonte associado, que permanece residente até o término do programa. Se você também precisar dele em outro lugar, não haverá trabalho adicional, pois já foi carregado.

Em ambos os casos, você pode "navegar" pelos dados criados por esta etapa usando as ferramentas do idioma. Normalmente, o IDE terá algum tipo de navegador de classe. E se o idioma tiver um REPL, também poderá ser usado frequentemente para gerar um resumo da documentação de qualquer objeto carregado.

tylerl
fonte
5
não é rigorosamente verdade - a maioria, se não todos, os compiladores pré-compilam os cabeçalhos como eles os veem, portanto, incluí-los na próxima unidade de compilação não é pior do que encontrar o bloco de código pré-compilado. Não é diferente, digamos, do código .NET repetidamente 'construindo' system.data.types para cada arquivo C # que você compila. A razão pela qual os programas C / C ++ demoram tanto é porque eles são realmente compilados (e otimizados). Tudo o que um programa .NET precisa deve ser analisado em IL, o tempo de execução faz a compilação real via JIT. Dito isto, 300 arquivos de origem no código C # também levam algum tempo para serem compilados.
Gbjbaanb
7

Não está claro o que você quer dizer com navegando no arquivo por funções e membros de dados. No entanto, a maioria dos IDEs fornece ferramentas para navegar na classe e ver membros de dados da classe.

Por exemplo: O Visual Studio possui Class Viewe Object browserque fornece muito bem a funcionalidade solicitada. Como nas seguintes capturas de tela.

insira a descrição da imagem aqui

insira a descrição da imagem aqui

EL Yusubov
fonte
4

Uma desvantagem adicional dos arquivos de cabeçalho é que o programa depende da ordem de inclusão e o programador precisa executar etapas extras para garantir a correção.

Isso, associado ao sistema macro de C (herdado de C ++), leva a muitas armadilhas e situações confusas.

Para ilustrar, se um cabeçalho definisse algum símbolo usando macros para seu uso, e outro cabeçalho usasse o símbolo de outra maneira, como o nome de uma função, a ordem de inclusão influenciaria bastante o resultado final. Há muitos exemplos assim.

jcora
fonte
2

Eu sempre gostei de arquivos de cabeçalho, pois eles fornecem uma forma de interface para a implementação, juntamente com algumas informações extras, como variáveis ​​de classe, tudo em um arquivo fácil de visualizar.

Eu vejo muitos códigos C # (que não precisam de 2 arquivos por classe) escritos usando 2 arquivos por classe - a implementação da classe real e outra com uma interface. Esse design é bom para zombar (essencial em alguns sistemas) e ajuda a definir a documentação da classe sem precisar usar o IDE para exibir os metadados compilados. Eu iria tão longe para dizer suas boas práticas.

Portanto, o C / C ++ exigindo um equivalente (das sortes) de uma interface nos arquivos de cabeçalho é uma coisa boa.

Eu sei que existem defensores de outros sistemas que não gostam deles por razões que incluem 'é mais difícil hackear código se você precisar colocar 2 arquivos', mas minha atitude é que apenas hackear código não é uma boa prática. e, depois de começar a escrever / projetar código com um pouco mais de atenção, você definirá cabeçalhos / interfaces normalmente.

gbjbaanb
fonte
Interface e Implementação em arquivos diferentes? Se você precisar disso por design, ainda é muito comum definir a interface (ou classe abstrata) em linguagens como Java / C # em um arquivo e ocultar a (s) implementação (ões) em outros arquivos. Isso não requer arquivos de cabeçalho antigos e complicados.
Petter Nordlander
Eh? Não foi o que eu disse - você define a interface e a implementação da classe em 2 arquivos.
gbjbaanb
2
bem, isso não requer arquivos de cabeçalho e não melhora os arquivos de cabeçalho. Sim, eles são necessários em idiomas com legado, mas, como conceito, não são necessários para dividir a interface / implementação em arquivos diferentes (na verdade, eles pioram).
Petter Nordlander 01/01
@ Petter Eu acho que você entendeu errado, conceitualmente eles não são diferentes - existe um cabeçalho para fornecer uma interface na linguagem C e, embora você esteja certo - você pode combinar os 2 (como muitos fazem com o código C somente de cabeçalho). as melhores práticas que eu já vi seguidas em muitos lugares. Todos os desenvolvedores de C # que eu vi separaram a interface da classe, por exemplo. É uma boa prática fazer isso, pois você pode incluir o arquivo de interface em vários outros arquivos sem poluição. Agora, se você acha que essa divisão é uma prática recomendada (é), a maneira de alcançá-la em C é usando arquivos de cabeçalho.
precisa
Se os cabeçalhos fossem realmente úteis para separar a interface e a implementação, coisas como o PImpl não seriam necessárias para ocultar componentes privados e desacoplar os usuários finais de terem que recompilar contra eles. Em vez disso, temos o pior dos dois mundos. Os cabeçalhos fingem uma fachada de separação rapidamente descartada, ao mesmo tempo em que abrem uma infinidade de armadilhas, que exigem um código detalhado, se você precisar contorná-las.
Underscore_d
1

Na verdade, eu diria que os arquivos de cabeçalhos não são ótimos porque eles confundem a interface e a implementação. A intenção da programação em geral, e especialmente do OOP, é ter uma interface definida e ocultar os detalhes da implementação, mas um arquivo de cabeçalho C ++ mostra os métodos, a herança e os membros públicos (interface), bem como métodos privados e membros privados (alguma parte da implementação). Sem mencionar que, em alguns casos, você acaba inserindo código ou construtores no arquivo de cabeçalho, e algumas bibliotecas incluem código de modelo nos cabeçalhos, o que realmente combina implementação com interface.

A intenção original, acredito, era possibilitar que o código usasse outras bibliotecas, objetos etc. sem precisar importar todo o conteúdo do script. Tudo o que você precisa é do cabeçalho para compilar e vincular. Economiza tempo e alterna dessa maneira. Nesse caso, é uma ideia decente, mas é apenas uma maneira de resolver esses problemas.

Quanto à navegação na estrutura do programa, a maioria dos IDEs oferece essa capacidade, e existem muitas ferramentas que irão lançar as interfaces, fazer análise de código, descompilação, etc. para que você possa ver o que está acontecendo nos bastidores.

Por que outras línguas não implementam o mesmo recurso? Bem, porque outros idiomas vêm de outras pessoas, e esses designers / criadores têm uma visão diferente de como as coisas devem funcionar.

A melhor resposta é ficar com o que o trabalho que você precisa fazer e fazer você feliz.

Maurice Reeves
fonte
0

Em muitas linguagens de programação, quando um programa é subdividido em várias unidades de compilação, o código em uma unidade só poderá usar as coisas definidas em outra se o compilador tiver algum meio de saber quais são essas coisas. Em vez de exigir que um compilador que processa uma unidade de compilação examine o texto inteiro de cada unidade de compilação que define qualquer coisa usada na unidade atual, é melhor que o compilador receba, enquanto processa cada unidade, o texto completo da unidade a ser compilada com um pouco de informação de todos os outros.

Em C, o programador é responsável por criar dois arquivos para cada unidade - um contendo informações que só serão necessárias ao compilar essa unidade e um contendo informações que também serão necessárias ao compilar outras unidades. Este é um design bastante desagradável, hacky, mas evita a necessidade de um padrão de linguagem especificar qualquer coisa sobre como os compiladores devem gerar e processar arquivos intermediários. Muitas outras linguagens usam uma abordagem diferente, na qual um compilador gerará a partir de cada arquivo de origem um arquivo intermediário que descreve tudo naquele arquivo de origem que deveria estar acessível ao código externo. Essa abordagem evita a necessidade de informações duplicadas em dois arquivos de origem, mas requer que uma plataforma de compilação tenha definido a semântica de arquivos de uma maneira que não é necessária para C.

supercat
fonte