Pergunta

Recentemente postei uma pergunta perguntando quais ações constituiriam o Zen de C++.Recebi excelentes respostas, mas não consegui entender uma recomendação:

  • Torne os arquivos de cabeçalho autossuficientes

Como você garante que seus arquivos de cabeçalho sejam autossuficiente?

Qualquer outro conselho ou prática recomendada relacionada ao design e implementação de arquivos de cabeçalho em C/C++ será bem-vindo.

Editar:eu encontrei essa questão que aborda a minha parte "Melhores Práticas".

Foi útil?

Solução

Um arquivo de cabeçalho auto -suficiente é aquele que não depende do contexto de onde está incluído para funcionar corretamente. Se você garantir que você #include ou defina/declare tudo antes de usá -lo, você tem um cabeçalho auto -suficiente.
Um exemplo de um não Cabeçalho auto -suficiente pode ser algo assim:

----- MyClass.h -----

class MyClass
{
   MyClass(std::string s);
};

-

---- MyClass.cpp -----

#include <string>
#include "MyClass.h"

MyClass::MyClass(std::string s)
{}

Neste exemplo, Myclass.h usos std::string sem primeiro #including. Para que isso funcione, em Myclass.cpp você precisa colocar o #include <string> antes da #include "MyClass.h".
Se o usuário do MyClass não conseguir fazer isso, ele receberá um erro que std :: string não está incluído.

Manter seus cabeçalhos para serem auto -suficientes pode ser frequentemente negligenciado. Por exemplo, você tem um enorme cabeçalho MyClass e adiciona a ele outro pequeno método que usa STRD :: String. Em todos os lugares que esta aula é usada atualmente, já está #included antes do myclass.h. Em seguida, um dia você #include myclass.h como o primeiro cabeçalho e de repente você tem todo esse novo erro em um arquivo que nem tocou (myclass.h)
Manter cuidadosamente seus cabeçalhos para ser auto -suficiente para evitar esse problema.

Outras dicas

NASA's Goddard Space Flight Center (GSFC) publicou padrões de programação C e C ++ que abordam esse problema.

Suponha que você tenha um módulo com um arquivo de origem perverse.c e seu cabeçalho perverse.h.

Garantir que um cabeçalho seja independente

Existe uma maneira muito simples de garantir que um cabeçalho seja independente. No arquivo de origem, o primeiro cabeçalho que você inclui é o cabeçalho do módulo. Se compilar como este, o cabeçalho será independente (auto-suficiente). Caso contrário, conserte o cabeçalho até que seja (confiável1) independente.

perverso.h

#ifndef PERVERSE_H_INCLUDED
#define PERVERSE_H_INCLUDED

#include <stddef.h>

extern size_t perverse(const unsigned char *bytes, size_t nbytes);

#endif /* PERVERSE_H_INCLUDED */

Quase todos os cabeçalhos devem ser protegidos contra a inclusão múltipla. (O padrão <assert.h> O cabeçalho é uma exceção explícita à regra - daí o qualificador 'quase'.)

perverso.C

#include "perverse.h"
#include <stdio.h>   // defines size_t too

size_t perverse(const unsigned char *bytes, size_t nbytes)
{
    ...etc...
}

Observe que, embora fosse tradicionalmente considerado uma boa idéia incluir os cabeçalhos padrão antes dos cabeçalhos do projeto, neste caso, é crucial para a testabilidade que o cabeçalho do módulo (perverse.h) vem antes de todos os outros. A única exceção que eu permitiria é incluir um cabeçalho de configuração à frente do cabeçalho do módulo; No entanto, mesmo isso é duvidoso. Se o cabeçalho do módulo precisar usar (ou talvez apenas 'possa usar') as informações do cabeçalho da configuração, provavelmente deve incluir o próprio cabeçalho da configuração, em vez de depender dos arquivos de origem que o usam para fazê -lo. No entanto, se você precisar configurar qual versão do POSIX solicitar suporte, isso deve ser feito antes que o primeiro cabeçalho do sistema seja incluído.


Nota de rodapé 1: Steve Jessop's Comente para Shoosh's responda É por isso que coloquei o comentário '(confiável)' '(confiável) no meu comentário' consertar '. Ele disse:

Outro fator que dificulta isso é a regra "Os cabeçalhos do sistema pode incluir outros cabeçalhos" no C ++. Se <iostream> inclui <string>, então é muito difícil descobrir que você esqueceu de incluir <string> em algum cabeçalho que não usa [não <iostream> ou <string>]. Compilar o cabeçalho por conta própria não dá erros: é auto-suficiente nesta versão do seu compilador, mas em outro compilador pode não funcionar.

Veja também o responda por Toby Speight Sobre Iwyu - inclua o que você usa.


Apêndice: Combinando essas regras com cabeçalhos GCC pré -compilados

As regras do GCC para cabeçalhos pré -compilados permitem apenas uma dessas unidades de cabeçalho por tradução e deve aparecer antes de qualquer tokens C.

GCC 4.4.1 Manual, §3.20 usando cabeçalhos pré -compilados

Um arquivo de cabeçalho pré -compilado pode ser usado apenas quando essas condições se aplicam:

  • Apenas um cabeçalho pré -compilado pode ser usado em uma compilação específica.
  • Um cabeçalho pré -compilado não pode ser usado quando o primeiro token C for visto. Você pode ter diretivas de pré -processador antes de um cabeçalho pré -compilado; Você pode até incluir um cabeçalho pré -compilado de dentro de outro cabeçalho, desde que não haja tokens C antes do #include.
  • [...]
  • Quaisquer macros definidos antes que o cabeçalho pré -compilado seja incluído deve ser definido da mesma maneira que quando o cabeçalho pré -compilado foi gerado ou não deve afetar o cabeçalho pré -compilado, o que geralmente significa que eles não aparecem no cabeçalho pré -compilado.

Para uma primeira aproximação, essas restrições significam que o cabeçalho pré -compilado deve ser o primeiro do arquivo. Uma segunda aproximação observa que, se 'config.h' contiver apenas declarações #Define, ela poderá aparecer à frente do cabeçalho pré -compilado, mas é muito mais provável que (a) os definem da config.h afete o restante do código e (b) O cabeçalho pré -compilado precisa incluir o config.h de qualquer maneira.

Os projetos em que trabalho não estão configurados para usar cabeçalhos pré-compilados e as restrições definidas pelo GCC mais a anarquia induzida por mais de 20 anos de manutenção e extensão intensiva por uma população diversificada de codificadores significa que seria muito difícil adicioná-los .

Dados os requisitos divergentes entre as diretrizes do GSFC e os cabeçalhos pré-compilados do GCC (e assumindo que os cabeçalhos pré-compilados estejam em uso), acho que garantiria a autocontreança e a idempotência dos cabeçalhos usando um mecanismo separado. Eu já faço isso para os principais projetos em que trabalho - reorganizar os cabeçalhos para atender às diretrizes do GSFC não é uma opção fácil - e o script que eu uso é chkhdr, mostrado abaixo. Você pode até fazer isso como uma etapa de 'construção' no diretório de cabeçalho-garantir que todos os cabeçalhos sejam independentes como uma regra de 'compilação'.

script chkhdr

Eu uso isso chkhdr Script para verificar se os cabeçalhos são independentes. Embora o Shebang diga 'Korn Shell', o código está realmente bem com Bash ou mesmo o Bourne Shell original (System V-Ish).

#!/bin/ksh
#
# @(#)$Id: chkhdr.sh,v 1.2 2010/04/24 16:52:59 jleffler Exp $
#
# Check whether a header can be compiled standalone

tmp=chkhdr-$$
trap 'rm -f $tmp.?; exit 1' 0 1 2 3 13 15

cat >$tmp.c <<EOF
#include HEADER /* Check self-containment */
#include HEADER /* Check idempotency */
int main(void){return 0;}
EOF

options=
for file in "$@"
do
    case "$file" in
    (-*)    options="$options $file";;
    (*)     echo "$file:"
            gcc $options -DHEADER="\"$file\"" -c $tmp.c
            ;;
    esac
done

rm -f $tmp.?
trap 0

Acontece que eu nunca precisei passar nenhuma opção que contenha espaços para o script para que o código não seja sólido no manuseio de opções de espaços. Lidar com eles no Bourne/Korn Shell pelo menos torna o script mais complexo sem benefício; Usar Bash e uma matriz pode ser melhor.

Uso:

chkhdr -Wstrict-prototypes -DULTRA_TURBO -I$PROJECT/include header1.h header2.h

Padrão GSFC disponível via Internet Archive

O URL ligado acima não é mais funcional (404). Você pode encontrar o padrão C ++ (582-2003-004) em Everyspec.com (na página 2); O padrão C (582-2000-005) parece estar faltando em ação.

No entanto, o padrão de codificação NASA C referenciado pode ser acessado e baixado através do Internet Archive:

http://web.archive.org/web/20090412090730/http://software.gsfc.nasa.gov/assetsbytype.cfm?typeasset=standard

Veja também: Devo usar #include em cabeçalhos?

Certifique -se de incluir tudo o que você precisa no cabeçalho, em vez de assumir que algo que você incluiu inclui outra coisa que você precisa.

Pergunta antiga, nova resposta. :-)

Agora existe uma ferramenta chamada inclua o que você usa que foi projetado para analisar seu código exatamente para esse tipo de problema. Nos sistemas debian e derivados, ele pode ser instalado como o iwyu pacote.

A ideia é que um arquivo de cabeçalho não dependa de um arquivo de cabeçalho anterior para ser compilado.Portanto, a ordem dos arquivos de cabeçalho não é significativa.Parte disso é incluir em um arquivo de cabeçalho todos os outros arquivos de cabeçalho necessários.A outra parte é definir seus cabeçalhos para que eles não sejam processados ​​mais de uma vez.

A ideia é que se você precisar adicionar um objeto foo à sua classe, você só precisa #include foo.h e não precisa bar.h na frente dele para que foo.h compile (por exemplo,há uma chamada em foo que retorna uma instância do objeto bar.Você pode não estar interessado nesta chamada, mas precisará adicionar bar.h para que o compilador saiba o que está sendo referenciado).

Não tenho certeza se sempre concordaria com esse conselho.Um projeto grande terá centenas de arquivos de cabeçalho e a compilação acabará lendo os arquivos comuns centenas de vezes apenas para ignorar os #ifdefs.O que vi feito neste caso é um arquivo de cabeçalho de arquivos de cabeçalho que é padrão para o projeto e inclui os trinta mais comuns.É sempre o primeiro na lista de inclusões.Isso pode acelerar o tempo de compilação, mas torna a manutenção do cabeçalho geral uma tarefa qualificada.

Você gostaria de usar o método descrito no Manual de pré -processador GNU C:

2.4 Cabeçalhos únicos

Se um arquivo de cabeçalho for incluído duas vezes, o compilador processará seu conteúdo duas vezes. É muito provável que isso cause um erro, por exemplo, quando o compilador vê a mesma definição de estrutura duas vezes. Mesmo que não, certamente perderá tempo.

A maneira padrão de impedir isso é incluir todo o conteúdo real do arquivo em um condicional, como este:

/* File foo.  */
#ifndef FILE_FOO_SEEN
#define FILE_FOO_SEEN

o arquivo inteiro

#endif /* !FILE_FOO_SEEN */

Esta construção é comumente conhecida como um embrulho #ifndef. Quando o cabeçalho estiver incluído novamente, o condicional será falso, porque FILE_FOO_SEEN é definido. O pré -processador pulará o conteúdo inteiro do arquivo, e o compilador não o verá duas vezes.

O CPP otimiza ainda mais. Lembra -se de quando um arquivo de cabeçalho tem um wrapper '#ifndef'. Se um subsequente '#include'Especifica esse cabeçalho e a macro no'#ifndef'Ainda está definido, não se preocupa em rescindir o arquivo.

Você pode colocar comentários fora do invólucro. Eles não interferirão nessa otimização.

A macro FILE_FOO_SEEN é chamado de Macro controlando ou Macro de guarda. Em um arquivo de cabeçalho do usuário, o nome da macro não deve começar com '_'. Em um arquivo de cabeçalho do sistema, ele deve começar com '__'Para evitar conflitos com programas de usuário. Em qualquer tipo de arquivo de cabeçalho, o nome da macro deve conter o nome do arquivo e algum texto adicional, para evitar conflitos com outros arquivos de cabeçalho.

Esta é uma grande pergunta. Eu acho que vou reexaminar a prática de colocar um stdafx.h conforme o primeiro incluir em cada arquivo .cpp ao usar o Visual Studio. Se você usar arquivos de cabeçalho pré-compilado, ele não se encontra de qualquer maneira, pode muito bem ter arquivos de cabeçalho mais amigáveis.

Obrigado Jalf pela correção. A partir de Wikipedia

O Visual C ++ não compilará nada antes do #include "stdafx.h" no arquivo de origem, a menos que a opção de compilação /yu'stdafx.h 'seja desmarcada (por padrão); Ele assume todo o código da fonte até e incluir essa linha já está compilada.

Então, isso significa que os cabeçalhos pré-compilados quebram a regra de cabeçalho auto-suficiente, certo?

Não tendo visto sua outra pergunta, meu primeiro pensamento sobre isso seria proteger meus arquivos de cabeçalho de várias chamadas (deixe meus cabeçalhos se defenderem).

#ifndef MY_PROTECTED_HEADER_H
#define MY_PROTECTED_HEADER_H
/*
 * Stuff here
 */
#endif /* MY_PROTECTED_HEADER_H */
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top