Meu estilo pessoal com C ++ sempre coloca declarações de classe em um arquivo de inclusão e definições em um arquivo .cpp, como estipulado na resposta de Loki para arquivos de cabeçalho em C ++, separação de código . É certo que parte da razão de eu gostar desse estilo provavelmente tem a ver com todos os anos que passei codificando o Modula-2 e o Ada, ambos com um esquema semelhante com arquivos de especificação e arquivos corporais.
Eu tenho um colega de trabalho, muito mais experiente em C ++ do que eu, que insiste em que todas as declarações de C ++ devem, sempre que possível, incluir as definições existentes no arquivo de cabeçalho. Ele não está dizendo que esse é um estilo alternativo válido, ou mesmo um estilo um pouco melhor, mas esse é o novo estilo universalmente aceito que todo mundo está usando no C ++.
Eu não sou tão flexível quanto costumava ser, então não estou realmente ansiosa para me meter nesse vagão dele até ver mais algumas pessoas lá em cima com ele. Então, quão comum é esse idioma realmente?
Só para dar alguma estrutura às respostas: agora é o Caminho , muito comum, um tanto comum, incomum ou maluco?
Respostas:
Seu colega de trabalho está errado, a maneira comum é e sempre foi colocar o código nos arquivos .cpp (ou em qualquer extensão que você desejar) e nas declarações nos cabeçalhos.
Ocasionalmente, há algum mérito em colocar o código no cabeçalho, isso pode permitir inlining mais inteligente pelo compilador. Mas, ao mesmo tempo, isso pode destruir seus tempos de compilação, pois todo o código deve ser processado toda vez que for incluído pelo compilador.
Por fim, muitas vezes é irritante ter relações com objetos circulares (às vezes desejado) quando todo o código é o cabeçalho.
Bottom line, você estava certo, ele está errado.
Edição: Eu estive pensando sobre sua pergunta. Há um caso em que o que ele diz é verdade. modelos. Muitas bibliotecas "modernas" mais recentes, como o boost, fazem uso pesado de modelos e geralmente são "apenas cabeçalho". No entanto, isso só deve ser feito ao lidar com modelos, pois é a única maneira de fazê-lo ao lidar com eles.
Edição: algumas pessoas gostariam de um pouco mais de esclarecimento, aqui estão algumas reflexões sobre a desvantagem de escrever o código "somente cabeçalho":
Se você pesquisar, verá muitas pessoas tentando encontrar uma maneira de reduzir o tempo de compilação ao lidar com o aumento. Por exemplo: Como reduzir o tempo de compilação com o Boost Asio , que está vendo uma compilação de 14s de um único arquivo 1K com o impulso incluído. Os 14s podem não parecer "explodir", mas certamente são muito mais longos do que o típico e podem aumentar rapidamente. Ao lidar com um grande projeto. As bibliotecas apenas de cabeçalho afetam os tempos de compilação de maneira bastante mensurável. Nós apenas toleramos porque o impulso é muito útil.
Além disso, há muitas coisas que não podem ser feitas apenas nos cabeçalhos (até o boost tem bibliotecas às quais você precisa vincular determinadas partes, como threads, sistema de arquivos, etc.). Um exemplo principal é que você não pode ter objetos globais simples apenas nas bibliotecas de cabeçalho (a menos que recorra à abominação que é um singleton), pois você encontrará erros de definição múltipla. NOTA: As variáveis embutidas do C ++ 17 tornarão esse exemplo específico possível no futuro.
Como ponto final, ao usar o boost como um exemplo de código apenas de cabeçalho, um grande detalhe geralmente é esquecido.
O impulso é uma biblioteca, não um código no nível do usuário. para que não mude com tanta frequência. No código do usuário, se você colocar tudo nos cabeçalhos, cada pequena alteração fará com que você precise recompilar todo o projeto. Isso é um monumental desperdício de tempo (e não é o caso de bibliotecas que não mudam de compilação para compilação). Quando você divide as coisas entre cabeçalho / origem e, melhor ainda, usa declarações de encaminhamento para reduzir inclusões, você pode economizar horas de recompilação quando somadas ao longo de um dia.
fonte
No dia em que os codificadores C ++ concordam com o Caminho , os cordeiros se deitam com os leões, os palestinos abraçam os israelenses e os gatos e os cães podem se casar.
A separação entre arquivos .h e .cpp é arbitrária neste momento, um vestígio de otimizações do compilador há muito tempo. Para mim, as declarações pertencem ao cabeçalho e as definições pertencem ao arquivo de implementação. Mas isso é apenas hábito, não religião.
fonte
O código nos cabeçalhos geralmente é uma má ideia, pois força a recompilação de todos os arquivos que incluem o cabeçalho quando você altera o código real em vez das declarações. Isso também desacelerará a compilação, pois você precisará analisar o código em todos os arquivos que incluem o cabeçalho.
Um motivo para ter código nos arquivos de cabeçalho é que geralmente é necessário que a palavra-chave inline funcione corretamente e ao usar modelos instanciados em outros arquivos cpp.
fonte
O que pode estar informando o colega de trabalho é uma noção de que a maioria dos códigos C ++ deve ser modelada para permitir a máxima usabilidade. E se estiver modelado, tudo precisará estar em um arquivo de cabeçalho, para que o código do cliente possa vê-lo e instancia-lo. Se é bom o suficiente para o Boost e o STL, é bom o suficiente para nós.
Não concordo com esse ponto de vista, mas pode ser de onde vem.
fonte
Acho que seu colega de trabalho é inteligente e você também está correto.
As coisas úteis que achei que colocar tudo nos cabeçalhos são as seguintes:
Não há necessidade de escrever e sincronizar cabeçalhos e fontes.
A estrutura é simples e nenhuma dependência circular força o codificador a criar uma estrutura "melhor".
Portátil, fácil de incorporar a um novo projeto.
Concordo com o problema do tempo de compilação, mas acho que devemos notar que:
É muito provável que a alteração do arquivo de origem altere os arquivos de cabeçalho que levam a todo o projeto a ser recompilado novamente.
A velocidade de compilação é muito mais rápida do que antes. E se você tem um projeto a ser construído com muito tempo e alta frequência, isso pode indicar que o design do seu projeto apresenta falhas. Separe as tarefas em diferentes projetos e módulos, para evitar esse problema.
Por fim, eu só quero apoiar seu colega de trabalho, apenas na minha opinião pessoal.
fonte
Frequentemente, colocarei funções de membro triviais no arquivo de cabeçalho, para permitir que elas sejam incorporadas. Mas, para colocar todo o corpo do código lá, apenas para ser consistente com os modelos? Isso é loucura.
Lembre-se: uma consistência tola é o hobgoblin das mentes pequenas .
fonte
A<B>
em um arquivo cpp e o usuário deseja umA<C>
?Como Tuomas disse, seu cabeçalho deve ser mínimo. Para ser completo, expandirei um pouco.
Eu pessoalmente uso 4 tipos de arquivos em meus
C++
projetos:Além disso, associo isso a outra regra: não defina o que você pode encaminhar declarar. Embora, é claro, eu seja razoável lá (usar o Pimpl em todos os lugares é um aborrecimento).
Isso significa que eu prefiro uma declaração direta a uma
#include
diretiva nos meus cabeçalhos sempre que eu puder me safar deles.Por fim, também uso uma regra de visibilidade: limite os escopos dos meus símbolos o máximo possível, para que eles não poluam os escopos externos.
Juntando tudo:
O salva-vidas aqui é que na maioria das vezes o cabeçalho para a frente é inútil: só é necessário em caso de
typedef
outemplate
e assim é o cabeçalho implementação;)fonte
Para adicionar mais diversão, você pode adicionar
.ipp
arquivos que contêm a implementação do modelo (que está sendo incluído.hpp
), enquanto.hpp
contém a interface.Além do código de modelo (dependendo do projeto, pode ser a maioria ou a minoria de arquivos), existe um código normal e aqui é melhor separar as declarações e as definições. Forneça também declarações avançadas sempre que necessário - isso pode afetar o tempo de compilação.
fonte
Geralmente, ao escrever uma nova classe, colocarei todo o código na classe, para que não precise procurar em outro arquivo. Depois que tudo estiver funcionando, divido o corpo dos métodos no arquivo cpp , deixando os protótipos no arquivo hpp.
fonte
Se esse novo caminho for realmente o Caminho , poderíamos estar correndo em direções diferentes em nossos projetos.
Porque tentamos evitar todas as coisas desnecessárias nos cabeçalhos. Isso inclui evitar cascata de cabeçalho. O código nos cabeçalhos provavelmente precisará incluir outro cabeçalho, que precisará de outro cabeçalho e assim por diante. Se formos forçados a usar modelos, tentamos evitar o excesso de cabeçalhos com itens de modelo.
Também usamos o padrão "ponteiro opaco" quando aplicável.
Com essas práticas, podemos criar versões mais rápidas do que a maioria de nossos colegas. E sim ... alterar o código ou os membros da classe não causará grandes reconstruções.
fonte
Eu pessoalmente faço isso nos meus arquivos de cabeçalho:
Não gosto de misturar o código para os métodos com a classe, pois acho difícil pesquisar rapidamente.
Eu não colocaria TODOS os métodos no arquivo de cabeçalho. O compilador (normalmente) não será capaz de alinhar métodos virtuais e (provavelmente) apenas alinhará pequenos métodos sem loops (depende totalmente do compilador).
Fazer os métodos na classe é válido ... mas, de um ponto de vista legível, eu não gosto. Colocar os métodos no cabeçalho significa que, quando possível, eles serão incorporados.
fonte
IMHO, ele só tem mérito se estiver fazendo modelos e / ou metaprogramação. Muitos motivos já mencionados mencionam que você limita os arquivos de cabeçalho a apenas declarações. Eles são apenas isso ... cabeçalhos. Se você deseja incluir código, compile-o como uma biblioteca e vincule-o.
fonte
Coloquei toda a implementação fora da definição de classe. Eu quero ter os comentários doxygen fora da definição de classe.
fonte
Eu acho que é absolutamente absurdo colocar TODAS as suas definições de função no arquivo de cabeçalho. Por quê? Porque o arquivo de cabeçalho é usado como a interface PUBLIC para sua classe. É o lado de fora da "caixa preta".
Quando você precisar examinar uma classe para referenciar como usá-la, consulte o arquivo de cabeçalho. O arquivo de cabeçalho deve fornecer uma lista do que pode ser feito (comentado para descrever os detalhes de como usar cada função) e incluir uma lista das variáveis de membro. NÃO DEVE incluir COMO cada função individual é implementada, porque é uma carga de informações desnecessárias e apenas atravessa o arquivo de cabeçalho.
fonte
Isso realmente não depende da complexidade do sistema e das convenções internas?
No momento, estou trabalhando em um simulador de rede neural incrivelmente complexo, e o estilo aceito que devo usar é:
Definições de classe em classname.h
Código de classe em classnameCode.h
código executável em classname.cpp
Isso divide as simulações criadas pelo usuário das classes base criadas pelo desenvolvedor e funciona melhor na situação.
No entanto, ficaria surpreso ao ver as pessoas fazendo isso em, digamos, um aplicativo gráfico ou qualquer outro aplicativo que não tenha o objetivo de fornecer aos usuários uma base de código.
fonte
O código do modelo deve estar apenas nos cabeçalhos. Além disso, todas as definições, exceto as linhas, devem estar em .cpp. O melhor argumento para isso seria as implementações da biblioteca std que seguem a mesma regra. Você não discordaria que os desenvolvedores da std lib estariam certos quanto a isso.
fonte
libstdc++
Parece que o GCC (AFAICS) coloca quase nadasrc
e quase tudoinclude
, independentemente de ele "estar" em um cabeçalho. Portanto, não acho que seja uma citação precisa / útil. De qualquer forma, não acho que o stdlibs seja um modelo para o código do usuário: eles são obviamente escritos por codificadores altamente qualificados, mas para serem usados , e não lidos: eles abstraem a alta complexidade que a maioria dos codificadores não deveria pensar. , precisam de coisas feias em_Reserved
__names
todos os lugares para evitar conflitos com o usuário, comentários e espaçamento estão abaixo do que eu recomendaria etc. Eles são exemplares de maneira estreita.Acho que seu colega de trabalho está certo, desde que ele não entre no processo para escrever o código executável no cabeçalho. O equilíbrio certo, eu acho, é seguir o caminho indicado pelo GNAT Ada, onde o arquivo .ads fornece uma definição de interface perfeitamente adequada do pacote para seus usuários e seus filhos.
A propósito, Ted, você já viu neste fórum a pergunta recente sobre a ligação Ada à biblioteca CLIPS que você escreveu há vários anos e que não está mais disponível (as páginas da Web relevantes agora estão fechadas). Mesmo se feita para uma versão antiga do Clips, essa ligação pode ser um bom exemplo de início para alguém disposto a usar o mecanismo de inferência CLIPS em um programa Ada 2012.
fonte