Como devo detectar arquivos #include desnecessários em um grande projeto C ++?

Eu estou trabalhando em um grande projeto C ++ no Visual Studio 2008, e há muitos arquivos com diretivas desnecessárias #include . Às vezes, os #include s são apenas artefatos e tudo será compilado com eles, e em outros casos, as classs podem ser encaminhadas e o #include pode ser movido para o arquivo .cpp . Existem boas ferramentas para detectar esses dois casos?

Embora não revele arquivos de inclusão desnecessários, o Visual Studio tem uma definição /showIncludes (clique com o botão direito do mouse em um arquivo .cpp , Properties->C/C++->Advanced ) que gerará uma tree com todos os arquivos incluídos no momento da compilation. Isso pode ajudar na identificação de arquivos que não devem ser incluídos.

Você também pode dar uma olhada no idioma pimpl para deixá-lo sair com menos dependencies de arquivo de header para tornar mais fácil ver o lixo que você pode remover.

O PC Lint funciona muito bem para isso, e ele encontra todos os tipos de outros problemas para você também. Ele tem opções de linha de comando que podem ser usadas para criar ferramentas externas no Visual Studio, mas descobri que o suplemento do Visual Lint é mais fácil de trabalhar. Até mesmo a versão gratuita do Visual Lint ajuda. Mas dê uma chance ao PC-Lint. Configurá-lo para que ele não lhe dê muitos avisos leva um pouco de tempo, mas você ficará surpreso com o que acontece.

Há uma nova ferramenta baseada em Clang, include-o-que-você-usa , que visa fazer isso.

!!AVISO LEGAL!! Eu trabalho em uma ferramenta de análise estática comercial (não PC Lint). !!AVISO LEGAL!!

Existem vários problemas com uma abordagem simples sem análise:

1) conjuntos de sobrecarga:

É possível que uma function sobrecarregada tenha declarações provenientes de arquivos diferentes. Pode ser que a remoção de um arquivo de header resulte em uma sobrecarga diferente sendo escolhida, em vez de um erro de compilation! O resultado será uma mudança silenciosa na semântica que pode ser muito difícil de rastrear depois.

2) especializações de modelos:

Semelhante ao exemplo de sobrecarga, se você tiver especializações parciais ou explícitas para um modelo, deseje que todas estejam visíveis quando o modelo for usado. Pode ser que as especializações do modelo principal estejam em arquivos de header diferentes. Remover o header com a especialização não causará um erro de compilation, mas poderá resultar em um comportamento indefinido se essa especialização tiver sido selecionada. (Consulte: Visibilidade da especialização de modelo da function C ++ )

Como apontado por ‘msalters’, realizar uma análise completa do código também permite a análise do uso da class. Ao verificar como uma class é usada através de um caminho específico de arquivos, é possível que a definição da class (e, portanto, todas as suas dependencies) possa ser removida completamente ou pelo menos movida para um nível mais próximo da fonte principal na inclusão. tree.

Não conheço nenhuma dessas ferramentas, e pensei em escrever uma no passado, mas acontece que esse é um problema difícil de resolver.

Digamos que seu arquivo de origem inclua ah e bh; ah contém #define USE_FEATURE_X e bh usa #ifdef USE_FEATURE_X . Se #include "ah" é comentado, o arquivo ainda pode compilar, mas pode não fazer o que você espera. Detectar isso programaticamente é não-trivial.

Seja qual for a ferramenta, isso também precisaria conhecer seu ambiente de criação. Se ah parece com:

 #if defined( WINNT ) #define USE_FEATURE_X #endif 

Em seguida, USE_FEATURE_X é definido apenas se WINNT for definido, portanto, a ferramenta precisaria saber quais diretivas são geradas pelo próprio compilador, bem como quais são especificadas no comando de compilation, em vez de em um arquivo de header.

Como Timmermans, não estou familiarizado com nenhuma ferramenta para isso. Mas eu conheci programadores que escreveram um script Perl (ou Python) para tentar comentar cada linha de inclusão uma por vez e depois compilar cada arquivo.


Parece que agora Eric Raymond tem uma ferramenta para isso .

O cpplint.py do Google tem uma regra “include o que você usa” (entre muitos outros), mas, até onde eu sei, não “inclui apenas o que você usa”. Mesmo assim, pode ser útil.

Se você estiver interessado neste tópico em geral, você pode querer dar uma olhada no Projeto de Software C ++ de Grande Escala da Lakos. É um pouco datado, mas entra em muitos problemas de “design físico”, como encontrar o mínimo absoluto de headers que precisam ser incluídos. Eu realmente não vi esse tipo de coisa discutida em outro lugar.

Se seus arquivos de header geralmente começam com

 #ifndef __SOMEHEADER_H__ #define __SOMEHEADER_H__ // header contents #endif 

(ao invés de usar #pragma uma vez) você poderia mudar isso para:

 #ifndef __SOMEHEADER_H__ #define __SOMEHEADER_H__ // header contents #else #pragma message("Someheader.h superfluously included") #endif 

E como o compilador gera o nome do arquivo cpp que está sendo compilado, isso permite que você saiba pelo menos qual arquivo cpp está causando o header a ser colocado várias vezes.

Dê uma chance ao Include Manager . Ele se integra facilmente no Visual Studio e visualiza seus caminhos de inclusão, o que ajuda você a encontrar coisas desnecessárias. Internamente, usa o Graphviz, mas há muitos resources mais legais. E embora seja um produto comercial, tem um preço muito baixo.

Você pode criar um gráfico de inclusão usando Observador de Dependências de Arquivo de Inclusão C / C ++ e localizar visualmente desnecessários.

O PC-Lint pode realmente fazer isso. Uma maneira fácil de fazer isso é configurá-lo para detectar apenas arquivos de inclusão não utilizados e ignorar todos os outros problemas. Isto é bastante simples – para habilitar apenas a mensagem 766 (“Arquivo de header não usado no módulo”), apenas inclua as opções -w0 + e766 na linha de comando.

A mesma abordagem também pode ser usada com mensagens relacionadas, como 964 (“Arquivo de header não usado diretamente no módulo”) e 966 (“Arquivo de header incluído indiretamente não usado no módulo”).

FWIW Eu escrevi sobre isso com mais detalhes em um post na semana passada em http://www.riverblade.co.uk/blog.php?archive=2008_09_01_archive.xml#3575027665614976318 .

Se você estiver procurando remover arquivos #include desnecessários para diminuir os tempos de compilation, seu tempo e seu dinheiro podem ser mais bem gastos paralelizando seu processo de compilation usando cl.exe / MP , make -j , Xoreax IncrediBuild , distcc / icecream , etc.

Claro, se você já tem um processo de construção paralelo e ainda está tentando acelerar, então, por favor, limpe suas diretivas #include e remova aquelas dependencies desnecessárias.

Comece com cada arquivo de inclusão e assegure-se de que cada arquivo de inclusão inclua apenas o que é necessário para se compilar. Quaisquer arquivos de inclusão que estão faltando para os arquivos C ++, podem ser adicionados aos próprios arquivos C ++.

Para cada inclusão e arquivo de origem, comente cada arquivo de inclusão, um de cada vez, e veja se ele é compilado.

Também é uma boa idéia classificar os arquivos de inclusão em ordem alfabética e, quando isso não for possível, adicione um comentário.

Adicionar um ou ambos os #defines a seguir excluirá frequentemente os arquivos de header desnecessários e poderá melhorar substancialmente os tempos de compilation, especialmente se o código não estiver usando as funções da API do Windows.

 #define WIN32_LEAN_AND_MEAN #define VC_EXTRALEAN 

Consulte http://support.microsoft.com/kb/166474

Se você ainda não estiver usando um header pré-compilado para include tudo o que não vai mudar (headers de plataforma, headers de SDK externos ou partes estáticas já concluídas do seu projeto) fará uma grande diferença nos tempos de compilation.

http://msdn.microsoft.com/en-us/library/szfdksca(VS.71).aspx

Além disso, embora possa ser muito tarde para o seu projeto, organizar seu projeto em seções e não colocar todos os headers locais em um grande header principal é uma boa prática, embora seja necessário um pouco mais de trabalho.

Se você trabalhasse com o Eclipse CDT, poderia experimentar http://includator.com para otimizar sua estrutura de inclusão. No entanto, o Includator pode não saber o suficiente sobre as inclusões predefinidas do VC ++ e configurar o CDT para usar o VC ++, mas as inclusões corretas ainda não estão incorporadas ao CDT.

O mais recente IDE do Jetbrains, o CLion, mostra automaticamente (em cinza) os includes que não são usados ​​no arquivo atual.

Também é possível ter a lista de todos os includes não utilizados (e também funções, methods, etc …) do IDE.

Algumas das respostas existentes afirmam que é difícil. Isso é verdade, porque você precisa de um compilador completo para detectar os casos em que uma declaração de encaminhamento seria apropriada. Você não pode analisar C ++ sem saber o que os símbolos significam; a gramática é simplesmente muito ambígua para isso. Você deve saber se um determinado nome nomeia uma class (pode ser declarada para a frente) ou uma variável (não pode). Além disso, você precisa estar ciente do namespace.

Talvez um pouco atrasado, mas uma vez eu encontrei um script perl do WebKit que fazia exatamente o que você queria. Vai precisar de alguma adaptação eu acredito (eu não sou bem versado em perl), mas deveria fazer o truque:

http://trac.webkit.org/browser/branches/old/safari-3-2-branch/WebKitTools/Scripts/find-extra-includes

(este é um ramo antigo porque o tronco não tem mais o arquivo)

Se houver um header específico que você acha que não é mais necessário (digamos, string.h), você pode comentar isso e incluí-lo abaixo de todos os includes:

 #ifdef _STRING_H_ # error string.h is included indirectly #endif 

É claro que seus headers de interface podem usar uma convenção #define diferente para registrar sua inclusão na memory CPP. Ou nenhuma convenção, caso em que esta abordagem não funcionará.

Então reconstrua. Existem três possibilidades:

  • Isso cria ok. string.h não era crítico para a compilation, e o include para ele pode ser removido.

  • As viagens #error. string.g foi incluído indiretamente de alguma forma Você ainda não sabe se string.h é necessário. Se for necessário, você deve include diretamente # (veja abaixo).

  • Você recebe algum outro erro de compilation. string.h foi necessário e não está sendo incluído indiretamente, portanto, o include estava correto para começar.

Note que dependendo da inclusão indireta quando seu .h ou .c usa diretamente outro .h é quase certamente um erro: você está prometendo que seu código irá requerer apenas o header, desde que algum outro header que você esteja usando requeira, o que provavelmente não é o que você quis dizer.

As advertências mencionadas em outras respostas sobre headers que modificam o comportamento, em vez de declarar coisas que causam falhas de compilation, também se aplicam aqui.