Estou limpando as inclusões em um projeto C ++ em que estou trabalhando e fico pensando se devo incluir explicitamente todos os cabeçalhos usados diretamente em um arquivo específico ou se devo incluir apenas o mínimo necessário.
Aqui está um exemplo Entity.hpp
:
#include "RenderObject.hpp"
#include "Texture.hpp"
struct Entity {
Texture texture;
RenderObject render();
}
(Vamos supor que uma declaração de encaminhamento para RenderObject
não seja uma opção.)
Agora, eu sei que isso RenderObject.hpp
inclui Texture.hpp
- eu sei disso porque cada RenderObject
um tem um Texture
membro. Ainda assim, eu incluir explicitamente Texture.hpp
no Entity.hpp
, porque eu não tenho certeza se é uma boa idéia para contar com ele ser incluído no RenderObject.hpp
.
Então: é uma boa prática ou não?
#ifndef _RENDER_H #define _RENDER_H ... #endif
.#pragma once
resolve isso, não?Respostas:
Você sempre deve incluir todos os cabeçalhos que definem quaisquer objetos usados em um arquivo .cpp nesse arquivo, independentemente do que você sabe sobre o que há nesses arquivos. Você deve incluir guardas em todos os arquivos de cabeçalho para garantir que a inclusão de cabeçalhos várias vezes não importe.
As razões:
Texture
objetos nesse arquivo.RenderObject.hpp
realmente não precisa deTexture.hpp
si.Um corolário é que você nunca deve incluir um cabeçalho em outro cabeçalho, a menos que seja explicitamente necessário nesse arquivo.
fonte
A regra geral é: inclua o que você usa. Se você usar um objeto diretamente, inclua seu arquivo de cabeçalho diretamente. Se você usar um objeto A que use B, mas não use B, inclua apenas Ah
Além disso, enquanto estamos no tópico, você só deve incluir outros arquivos de cabeçalho no arquivo de cabeçalho se realmente precisar dele no cabeçalho. Se você só precisar dele no .cpp, inclua-o apenas lá: esta é a diferença entre uma dependência pública e privada e impedirá que os usuários da sua classe arrastem os cabeçalhos que eles realmente não precisam.
fonte
Sim.
Você nunca sabe quando esses outros cabeçalhos podem mudar. Faz todo o sentido do mundo incluir, em cada unidade de tradução, os cabeçalhos que você conhece que a unidade de tradução precisa.
Temos guardas de cabeçalho para garantir que a inclusão dupla não seja prejudicial.
fonte
As opiniões divergem sobre isso, mas sou de opinião que todo arquivo (seja arquivo de origem c / cpp ou arquivo de cabeçalho h / hpp) deve poder ser compilado ou analisado por si próprio.
Dessa forma, todos os arquivos devem # incluir todos os arquivos de cabeçalho de que precisam - você não deve assumir que um arquivo de cabeçalho já foi incluído anteriormente.
É realmente doloroso se você precisar adicionar um arquivo de cabeçalho e descobrir que ele usa um item definido em outro lugar, sem incluí-lo diretamente ... então você deve procurar (e possivelmente acabar com o errado!)
Por outro lado, (como regra geral) não importa se você # inclui um arquivo que não precisa ...
Como um ponto de estilo pessoal, organizo os arquivos #include em ordem alfabética, divididos em sistema e aplicativo - isso ajuda a reforçar a mensagem "independente e totalmente coerente".
fonte
Depende se essa inclusão transitiva é por necessidade (por exemplo, classe base) ou por causa de um detalhe de implementação (membro privado).
Para esclarecer, a inclusão transitiva é necessária quando a remoção é feita somente após a primeira alteração das interfaces declaradas no cabeçalho intermediário. Como essa já é uma alteração inabalável, qualquer arquivo .cpp que o utilize deve ser verificado de qualquer maneira.
Exemplo: Ah é incluído por Bh que é usado por C.cpp. Se Bh usou Ah para alguns detalhes de implementação, C.cpp não deve assumir que Bh continuará a fazê-lo. Mas se Bh usa Ah para uma classe base, C.cpp pode assumir que Bh continuará incluindo os cabeçalhos relevantes para suas classes base.
Você vê aqui a vantagem real de NÃO duplicar inclusões de cabeçalho. Digamos que a classe base usada por Bh realmente não pertença a Ah e seja refatorada no próprio Bh. Bh agora é um cabeçalho independente. Se o C.cpp incluiu redundantemente Ah, agora ele inclui um cabeçalho desnecessário.
fonte
Pode haver outro caso: você tem Ah, Bh e seu C.cpp, Bh inclui Ah
então em C.cpp, você pode escrever
Então, se você não escrever #include "Ah" aqui, o que pode acontecer? no seu C.cpp, são usados A e B (por exemplo, classe). Mais tarde, você alterou seu código C.cpp, remove os itens relacionados ao B, mas deixa o Bh incluído lá.
Se você incluir Ah e Bh e agora, neste momento, as ferramentas que detectam inclusões desnecessárias podem ajudá-lo a apontar que a inclusão de Bh não é mais necessária. Se você incluir apenas Bh como acima, será difícil para ferramentas / humanos detectar a inclusão desnecessária após a alteração do código.
fonte
Estou adotando uma abordagem ligeiramente diferente das respostas propostas.
Nos cabeçalhos, sempre inclua apenas um mínimo, apenas o necessário para fazer a compilação passar. Use a declaração de encaminhamento sempre que possível.
Nos arquivos de origem, não é tão importante quanto você inclui. Minhas preferências ainda devem incluir o mínimo para que seja aprovado.
Para pequenos projetos, incluindo cabeçalhos aqui e ali, não fará diferença. Mas para projetos de médio a grande porte, isso pode se tornar um problema. Mesmo que o hardware mais recente seja usado para compilar, a diferença pode ser perceptível. O motivo é que o compilador ainda precisa abrir o cabeçalho incluído e analisá-lo. Portanto, para otimizar a compilação, aplique a técnica acima (inclua o mínimo necessário e use a declaração a frente).
Embora um pouco desatualizado, o Design de software C ++ em larga escala (de John Lakos) explica tudo isso em detalhes.
fonte
A boa prática é não se preocupar com sua estratégia de cabeçalho, desde que seja compilada.
A seção de cabeçalho do seu código é apenas um bloco de linhas que ninguém deve olhar até que você obtenha um erro de compilação facilmente resolvido. Entendo o desejo de um estilo "correto", mas nenhuma das maneiras pode realmente ser descrita como correta. A inclusão de um cabeçalho para cada classe tem mais probabilidade de causar erros irritantes de compilação com base em ordem, mas esses erros de compilação também refletem problemas que uma codificação cuidadosa poderia corrigir (embora, sem dúvida, não valha a pena consertar).
E sim, você terá esses problemas com base em pedidos assim que começar a entrar em
friend
terra.Você pode pensar no problema em dois casos.
Caso 1: você tem um pequeno número de classes interagindo entre si, digamos menos de uma dúzia. Você adiciona, remove e modifica regularmente esses cabeçalhos de maneiras que possam afetar suas dependências um do outro. Este é o caso que o seu exemplo de código sugere.
O conjunto de cabeçalhos é pequeno o suficiente para não ser complicado resolver quaisquer problemas que surjam. Quaisquer problemas difíceis são corrigidos reescrevendo um ou dois cabeçalhos. Preocupar-se com sua estratégia de cabeçalho é resolver problemas que não existem.
Caso 2: você tem dezenas de aulas. Algumas das classes representam a espinha dorsal do seu programa, e reescrever seus cabeçalhos forçaria você a reescrever / recompilar uma grande quantidade de sua base de código. Outras classes usam esse backbone para realizar as coisas. Isso representa uma configuração comercial típica. Os cabeçalhos estão espalhados pelos diretórios e você não consegue se lembrar realisticamente dos nomes de tudo.
Solução: Nesse momento, você precisa pensar em suas classes em grupos lógicos e coletar esses grupos em cabeçalhos que impedem que você tenha que repetir
#include
várias vezes. Isso não apenas torna a vida mais simples, como também é uma etapa necessária para tirar proveito dos cabeçalhos pré-compilados .Você acaba tendo
#include
aulas que não precisa, mas quem se importa ?Nesse caso, seu código seria semelhante a ...
fonte