O que deve e o que não deve estar em um arquivo de cabeçalho? [fechadas]


71

Que coisas absolutamente nunca devem ser incluídas em um arquivo de cabeçalho?

Se, por exemplo, estou trabalhando com um formato padrão do setor documentado que possui muitas constantes, é uma boa prática defini-las em um arquivo de cabeçalho (se estou escrevendo um analisador para esse formato)?

Quais funções devem entrar no arquivo de cabeçalho?
Que funções não deveriam?


11
Curto e indolor: definições e declarações necessárias em mais de um módulo.
6--12

21
Marcar esta questão como "muito ampla" e fechar é um exagero vergonhoso e absoluto de moderação. Esta pergunta pergunta exatamente o que estou procurando - a pergunta está bem formada e faz uma pergunta muito clara: quais são as melhores práticas? Se isso for "amplo demais" para a engenharia de software ... também poderíamos fechar todo esse fórum.
Gewure

TL; DR. Para C ++, na quarta edição de "A linguagem de programação C ++", escrita por Bjarne Stroustrup (seu criador), na Seção 15.2.2, é descrito o que um cabeçalho deve e não deve conter. Sei que você marcou a pergunta como C, mas alguns conselhos também são aplicáveis. Eu acho que essa é uma boa pergunta ...
horro

Respostas:


57

O que colocar nos cabeçalhos:

  • O conjunto mínimo de #includediretivas necessárias para tornar o cabeçalho compilável quando o cabeçalho é incluído em algum arquivo de origem.
  • Definições de símbolo do pré-processador de coisas que precisam ser compartilhadas e que só podem ser realizadas através do pré-processador. Mesmo em C, os símbolos do pré-processador são melhor reduzidos ao mínimo.
  • Encaminhar declarações de estruturas necessárias para tornar compiláveis ​​as definições de estrutura, protótipos de função e declarações de variáveis ​​globais no corpo do cabeçalho.
  • Definições de estruturas de dados e enumerações compartilhadas entre vários arquivos de origem.
  • Declarações para funções e variáveis ​​cujas definições serão visíveis para o vinculador.
  • Definições de função em linha, mas tome cuidado aqui.

O que não pertence a um cabeçalho:

  • #includeDiretivas gratuitas . Essas inclusões gratuitas causam recompilação de coisas que não precisam ser recompiladas e às vezes podem fazer com que o sistema não seja compilado. Não #includeum arquivo em um cabeçalho se o próprio cabeçalho não precisar desse outro arquivo de cabeçalho.
  • Símbolos do pré-processador cuja intenção pode ser alcançada por algum mecanismo, qualquer mecanismo, exceto o pré-processador.
  • Muitas e muitas definições de estrutura. Divida-os em cabeçalhos separados.
  • Definições em linha de funções que exigem um adicional #include, que estão sujeitas a alterações ou que são grandes demais. Essas funções em linha devem ter pouca ou nenhuma ventilação e, se houver, devem ser localizadas nos itens definidos no cabeçalho.

O que constitui o conjunto mínimo de #includedeclarações?

Isso acaba sendo uma questão não trivial. Uma definição de TL; DR: um arquivo de cabeçalho deve incluir os arquivos de cabeçalho que definem diretamente cada um dos tipos usados ​​diretamente ou que declaram diretamente cada uma das funções usadas no arquivo de cabeçalho em questão, mas não devem incluir mais nada. Um ponteiro ou tipo de referência C ++ não se qualifica como uso direto; referências futuras são preferidas.

Há um lugar para uma #includediretiva gratuita , e isso é em um teste automatizado. Para cada arquivo de cabeçalho em um pacote de software, eu automaticamente giro e compilo o seguinte:

#include "path/to/random/header_under_test"
int main () { return 0; }

A compilação deve estar limpa (ou seja, livre de avisos ou erros). Avisos ou erros relacionados a tipos incompletos ou desconhecidos significam que o arquivo de cabeçalho em teste tem algumas #includediretivas ausentes e / ou declarações avançadas. Observe bem: Só porque o teste passa não significa que o conjunto de #includediretivas seja suficiente, muito menos o mínimo.


Portanto, se eu tenho uma biblioteca que define uma estrutura, chamada A, e essa biblioteca, chamada B, usa essa estrutura e a biblioteca B é usada pelo programa C, devo incluir o arquivo de cabeçalho da biblioteca A no cabeçalho principal da biblioteca B ou devo Acabei de declarar isso? A biblioteca A é compilada e vinculada à biblioteca B durante sua compilação.
MarcusJ

@ MarcusJ - A primeira coisa que listei em O que não pertence a um cabeçalho foram declarações #include gratuitas. Se o arquivo de cabeçalho B não depender das definições do arquivo de cabeçalho A, não inclua o arquivo de cabeçalho A no arquivo de cabeçalho B. Um arquivo de cabeçalho não é o local para especificar dependências de terceiros ou instruções de compilação. Esses vão para outro lugar, como um arquivo leia-me de nível superior.
David Hammen

11
@ MarcusJ - atualizei minha resposta na tentativa de responder sua pergunta. Observe que não há uma resposta para sua pergunta. Ilustrarei com alguns extremos. Caso 1: O único local em que a biblioteca B usa diretamente a funcionalidade da biblioteca A está nos arquivos de origem da biblioteca B. Caso 2: a Biblioteca B é uma extensão fina da funcionalidade da biblioteca A, com o (s) arquivo (s) de cabeçalho da biblioteca B diretamente usando os tipos e / ou funções definidas na biblioteca A. No caso 1, não há razão para expor a biblioteca A em o (s) cabeçalho (s) da biblioteca B. No caso 2, essa exposição é praticamente obrigatória.
David Hammen

Sim, é o caso 2, desculpe, meu comentário ignorou o fato de que ele está usando os tipos declarados na biblioteca A nos cabeçalhos da biblioteca B, eu estava pensando que poderia encaminhar a declaração, mas acho que não vai funcionar. Obrigado pela atualização.
MarcusJ

Adicionar constantes a um arquivo de cabeçalho é um grande não-não?
mding5692

15

Além do que já foi dito.

Os arquivos H devem sempre conter:

  • Documentação do código fonte !!! No mínimo, qual é o objetivo dos vários parâmetros e valores de retorno das funções.
  • Protetores de cabeçalho, #ifndef MYHEADER_H #definir MYHEADER_H ... #endif

Os arquivos H nunca devem conter:

  • Qualquer forma de alocação de dados.
  • Definições de funções. Funções embutidas podem ser uma exceção rara em alguns casos.
  • Qualquer coisa rotulada static.
  • Typedefs, #defines ou constantes que não têm relevância para o restante do aplicativo.

(Eu também diria que nunca há motivo para usar variáveis ​​globais / externas não constantes, em qualquer lugar, mas isso é uma discussão para outro post.)


11
Eu concordo com tudo, exceto o que você destacou. Se você estiver criando uma biblioteca, sim, você deve documentar ou os usuários da sua biblioteca. Para um projeto interno, não é necessário sobrecarregar os cabeçalhos com a documentação, se você usar nomes de variáveis ​​e funções bons e auto-explicativos.
martiert

5
@martiert Também sou da escola "deixe o código falar por si". No entanto, você deve pelo menos sempre documentar suas funções, mesmo que ninguém além de você as utilize. Os itens de interesse particular são: caso a função possua tratamento de erros, que códigos de erro ela retorna e sob quais condições ela falha? O que acontece com os parâmetros (buffers, ponteiros etc.) se a função falhar? Outra coisa que é muito relevante é: os parâmetros do ponteiro estão retornando algo ao chamador, ou seja, eles esperam memória alocada? ->

11
Deveria ser óbvio para o chamador que tratamento de erro é feito dentro da função e o que não é feito. Se a função espera um buffer alocado, provavelmente deixará as verificações fora dos limites também para o chamador. Se a função depende de outra função a ser executada, isso deve ser documentado (por exemplo, execute link_list_init () antes de link_list_add ()). E, finalmente, se a função tiver um "efeito colateral", como a criação de arquivos, threads, timers ou qualquer outra coisa, isso deve ser indicado na documentação. ->

11
Talvez a "documentação do código fonte" seja muito ampla aqui, isso realmente pertença ao código fonte. A "documentação de uso" com entrada e saída, pré e pós-condições e efeitos colaterais deve definitivamente ir para lá, não em épico, mas em uma forma breve .
Secure

2
Um pouco tardio, mas +1 na documentação. Por que essa classe existe? O código não fala por si. O que essa função faz? RTFC (leia o arquivo .cpp) é um acrônimo obsceno de quatro letras. Nunca se deve usar o RTFC para entender. O protótipo no cabeçalho deve resumir, em algum comentário extraível (por exemplo, doxygen), quais são os argumentos e o que a função faz. Por que esse membro de dados existe, o que contém e o valor em metros, pés ou mais? Esse também é outro assunto para comentários (extraíveis).
11133 David Hammen

4

Eu provavelmente nunca diria nunca, mas as instruções que geram dados e código à medida que são analisadas não devem estar em um arquivo .h.

Macros, funções embutidas e modelos podem parecer dados ou código, mas não geram código à medida que são analisados, mas quando são usados. Esses itens geralmente precisam ser usados ​​em mais de um .c ou .cpp, para que pertençam ao .h.

Na minha opinião, um arquivo de cabeçalho deve ter a interface prática mínima para um correspondente .c ou .cpp. A interface pode incluir #defines, classe, typedef, definições de estrutura, protótipos de função e definições externas menos preferidas para variáveis ​​globais. No entanto, se uma declaração for usada em apenas um arquivo de origem, provavelmente ela deve ser excluída do arquivo .he deve estar contida no arquivo de origem.

Alguns podem discordar, mas meu critério pessoal para arquivos .h é que eles #incluem todos os outros arquivos .h que precisam ser capazes de compilar. Em alguns casos, pode haver muitos arquivos, portanto, temos alguns métodos eficazes para reduzir dependências externas, como declarações de encaminhamento para classes, que permitem usar ponteiros para objetos de uma classe sem incluir o que poderia ser uma grande árvore de arquivos de inclusão.


3

O arquivo de cabeçalho deve ter a seguinte organização:

  • tipo e definições constantes
  • declarações de objetos externos
  • declarações de função externa

Os arquivos de cabeçalho nunca devem conter definições de objeto, apenas definições de tipo e declarações de objeto.


E as definições de funções embutidas?
Kos

Se a função embutida for uma função “auxiliar” usada apenas dentro de um módulo C, coloque-a apenas no arquivo .c. Se a função embutida precisar ser visível para dois ou mais módulos, coloque-a dentro do arquivo de cabeçalho.
theD

Além disso, se a função precisar ser visível através de um limite da biblioteca, não a faça incorporar, pois isso força todos os que usam a biblioteca a recompilarem toda vez que modificar as coisas.
Donal Fellows

@DonalFellows: Essa é uma solução invertida. Uma regra melhor: não coloque itens em cabeçalhos sujeitos a modificações frequentes. Não há nada de errado em incluir uma pequena função em um cabeçalho se a função não tiver fanout e uma definição clara que só será alterada se a estrutura de dados subjacente for alterada. Se a definição da função mudar porque a definição da estrutura subjacente mudou, sim, você precisará recompilar tudo, mas você precisará fazer isso de qualquer maneira, porque a definição da estrutura mudou.
David Hammen

0

Instruções que geram dados e código à medida que são analisadas, não devem estar em um .harquivo. No que diz respeito ao meu ponto de vista, um arquivo de cabeçalho deve ter apenas a interface prática mínima para um .cou .cpp.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.