Garantir que os cabeçalhos sejam explicitamente incluídos no arquivo CPP


9

Eu acho que geralmente é uma boa prática para #includeo cabeçalho qualquer tipo usado em um arquivo CPP, independentemente do que já está incluído no arquivo HPP. Portanto, eu poderia #include <string>tanto no HPP quanto no CPP, por exemplo, mesmo que ainda pudesse compilar se o ignorasse no CPP. Dessa forma, não preciso me preocupar se meu HPP usou uma declaração de encaminhamento ou não.

Existem ferramentas que podem impor esse #includeestilo de codificação? Devo aplicar esse estilo de codificação?

Como o pré-processador / compilador não se importa se #includeé proveniente do HPP ou CPP, não recebo feedback se esquecer de seguir esse estilo.

Respostas:


7

Esse é um desses tipos de padrões de codificação "deveria", em vez de "deve". O motivo é que você precisaria praticamente escrever um analisador de C ++ para aplicá-lo.

Uma regra muito comum para os arquivos de cabeçalho é que eles devem permanecer sozinhos. Um arquivo de cabeçalho não deve exigir que outros arquivos de cabeçalho sejam #incluídos antes de incluir o cabeçalho em questão. Este é um requisito testável. Dado algum cabeçalho aleatório foo.hh, o seguinte deve compilar e executar:

#include "foo.hh"
int main () {
   return 0;
}

Esta regra tem consequências no que diz respeito ao uso de outras classes em algum cabeçalho. Às vezes, essas consequências podem ser evitadas através da declaração direta dessas outras classes. Isso não é possível com muitas classes de biblioteca padrão. Não há como encaminhar uma instanciação de modelo como std::stringou std::vector<SomeType>. Você precisa #includedesses cabeçalhos STL no cabeçalho, mesmo que o único uso do tipo seja como argumento para uma função.

Outro problema está nas coisas que você arrasta acidentalmente. Exemplo: considere o seguinte:

arquivo foo.cc:

#include "foo.hh"
#include "bar.hh"

void Foo::Foo () : bar() { /* body elided */ }

void Foo::do_something (int item) {
   ...
   bar.add_item (item);
   ...
}

Aqui baré uma classe Foomembro de dados que é do tipo Bar. Você fez a coisa certa aqui e #included bar.hh, embora isso devesse ter sido incluído no cabeçalho que define a classe Foo. No entanto, você não incluiu o material usado por Bar::Bar()e Bar::add_item(int). Existem muitos casos em que essas chamadas podem resultar em referências externas adicionais.

Se você analisar foo.ocom uma ferramenta como nm, parece que as funções foo.ccestão chamando todos os tipos de coisas para as quais você não fez o apropriado #include. Então, você deve adicionar #includediretrizes para essas referências externas incidentais foo.cc? A resposta é absolutamente não. O problema é que é muito difícil distinguir as funções que são chamadas incidentalmente daquelas que são chamadas diretamente.


2

Devo aplicar esse estilo de codificação?

Provavelmente não. Minha regra é a seguinte: a inclusão do arquivo de cabeçalho não pode depender da ordem.

Você pode verificar isso facilmente com a regra simples de que o primeiro arquivo incluído pelo xc é xh


11
Você pode elaborar sobre isso? Não vejo como isso realmente verificaria a independência da ordem.
detly

11
na verdade, não garante a independência da ordem - apenas garante que #include "x.h"funcionará sem exigir precedentes #include. Isso é bom o suficiente se você não abusar #define.
Kevin cline

11
Ah eu vejo. Ainda é uma boa ideia então.
detly

2

Se você precisar aplicar uma regra de que determinados arquivos de cabeçalho devem permanecer por si mesmos, use as ferramentas que já possui. Crie um makefile básico que compila cada arquivo de cabeçalho individualmente, mas não gera um arquivo de objeto. Você poderá especificar em qual modo compilar o arquivo de cabeçalho (modo C ou C ++) e verificar se ele pode ser independente. Você pode supor que a saída não contém nenhum falso positivo, que todas as dependências necessárias são declaradas e que a saída é precisa.

Se você estiver usando um IDE, ainda poderá realizar isso sem um makefile (dependendo do seu IDE). Basta criar um projeto adicional, adicionar os arquivos de cabeçalho que deseja verificar e alterar as configurações para compilá-lo como um arquivo C ou C ++. No MSVC, por exemplo, você alteraria a configuração "Item Type" em "Configuration Properties-> General".


1

Eu não acho que essa ferramenta exista, mas ficaria feliz se alguma outra resposta me refutasse.

O problema ao escrever uma ferramenta desse tipo é que ela relata com facilidade um resultado falso, então eu estimo a vantagem líquida de tal ferramenta em quase zero.

A única maneira de uma ferramenta funcionar é se ela redefinir sua tabela de símbolos apenas para o conteúdo do arquivo de cabeçalho processado, mas você terá o problema de que os cabeçalhos que formam a API externa de uma biblioteca delegam as declarações reais para cabeçalhos internos.
Por exemplo, <string>na implementação libc ++ do GCC, não declara nada, mas inclui apenas um monte de cabeçalhos internos que contêm as declarações reais. Se a ferramenta redefinir sua tabela de símbolos exatamente para o que foi declarado por <string>si só, isso não seria nada.
Você pode ter a ferramenta para diferenciar entre #include ""e #include <>, mas isso não ajuda se uma biblioteca externa usa #include ""para incluir seus cabeçalhos internos na API.


1

não #Pragma onceconsegue isso? Você pode incluir algo quantas vezes quiser, diretamente ou via inclusões encadeadas, e desde que haja um #Pragma oncepróximo a cada um deles, o cabeçalho será incluído apenas uma vez.

Quanto à imposição, talvez você possa criar um sistema de compilação que inclua cada cabeçalho por si só com alguma função principal fictícia, apenas para garantir que ele seja compilado. #ifdefA cadeia inclui para obter melhores resultados com esse método de teste.


1

Sempre inclua arquivos de cabeçalho no arquivo CPP. Isso não apenas reduz significativamente o tempo de compilação, mas também poupa muitos problemas se você optar por usar cabeçalhos pré-compilados. Na minha experiência, vale a pena praticar o trabalho de fazer declarações avançadas. Quebre a regra somente quando necessário.


Você pode explicar como isso "reduzirá significativamente o tempo de compilação"?
Mawg diz que restabelece Monica

11
Isso garante que cada unidade de compilação (arquivo CPP) obtenha apenas o mínimo de arquivos de inclusão no tempo de compilação. Caso contrário, se você incluir inclusos nos arquivos H, as cadeias de dependência falsas se formarão rapidamente e você acabará puxando todos os inclusos em cada compilação.
Gvozden

Com os guardas incluídos, eu poderia questionar "significativamente", mas suponho que o acesso ao disco (para descobrir aqueles gaúchos inlcuídos) seja "lento", então cederá o ponto. Obrigado por esclarecer
Mawg diz reintegrar Monica

0

Eu diria que existem vantagens e desvantagens nesta convenção. Por um lado, é bom saber exatamente o que seu arquivo .cpp está incluindo. Por outro lado, a lista de inclusões pode crescer facilmente para um tamanho ridículo.

Uma maneira de incentivar essa convenção não é incluir nada em seus próprios cabeçalhos, mas apenas nos arquivos .cpp. Portanto, qualquer arquivo .cpp usando o cabeçalho não será compilado, a menos que você inclua explicitamente todos os outros cabeçalhos dos quais depende.

Provavelmente, algum compromisso razoável está em ordem aqui. Por exemplo, você pode decidir que não há problema em incluir cabeçalhos de biblioteca padrão em seus próprios cabeçalhos, mas não mais.


3
Se você deixar inclusos fora dos cabeçalhos, inclua a ordem começa a importar ... e eu definitivamente quero evitar isso.
M. Dudley

11
-1: cria um pesadelo de dependência. Um acessório para xh poderia exigir uma mudança no arquivo a cada .cpp que inclui xh
kevin Cline

11
@ M.Dudley, como eu disse, existem vantagens e desvantagens.
Dima
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.