O que deve entrar em um arquivo .h?

93

Ao dividir seu código em vários arquivos, o que exatamente deve ir para um arquivo .h e o que deve ir para um arquivo .cpp?

Enrico Tuvera Jr
fonte
Pergunta relacionada: stackoverflow.com/questions/333889/…
Spoike
7
Esta é uma questão de puro estilo, mas acredito que as declarações C ++ vão para um .hpparquivo enquanto as declarações C vão para um .harquivo. Isso é muito útil ao misturar código C e C ++ (por exemplo, módulos legados em C).
Thomas Matthews
@ThomasMatthews Faz sentido. Essa prática é freqüentemente usada?
ty
@lightningleaf: Sim, a prática é frequentemente usada, especialmente ao misturar as linguagens C ++ e C.
Thomas Matthews

Respostas:

113

Os arquivos de cabeçalho ( .h) são projetados para fornecer as informações que serão necessárias em vários arquivos. Coisas como declarações de classe, protótipos de função e enumerações geralmente vão em arquivos de cabeçalho. Em uma palavra, "definições".

Os arquivos de código ( .cpp) são projetados para fornecer as informações de implementação que só precisam ser conhecidas em um arquivo. Em geral, corpos de função e variáveis ​​internas que devem / nunca serão acessadas por outros módulos são o que pertence aos .cpparquivos. Em uma palavra, "implementações".

A pergunta mais simples de se fazer para determinar o que pertence onde é "se eu mudar isso, terei que mudar o código em outros arquivos para fazer as coisas compilarem novamente?" Se a resposta for "sim", provavelmente ele pertence ao arquivo de cabeçalho; se a resposta for "não", provavelmente ele pertence ao arquivo de código.

Âmbar
fonte
4
Exceto que os dados da classe privada precisam ir para o cabeçalho. Os modelos devem ser completamente definidos no cabeçalho (a menos que você use um dos poucos compiladores compatíveis export). A única maneira de contornar o # 1 é PIMPL. # 2 seria possível se exportfosse suportado e pode ser possível usando c ++ 0x e externmodelos. IMO, arquivos de cabeçalho em c ++ perdem muito de sua utilidade.
KitsuneYMG
23
Tudo bem, mas com terminologia imprecisa. Em uma palavra, "declarações" - o termo "definição" é sinônimo de "implementação". Apenas o código declarativo, código embutido, definições de macro e código de modelo devem estar em um cabeçalho; ou seja, nada que instancia código ou dados.
Clifford
8
Tenho que concordar com Clifford. Você usa os termos declaração e definição vagamente e de forma intercambiável. Mas eles têm significados precisos em C ++. Exemplos: Uma declaração de classe introduz o nome de uma classe, mas não diz o que ela contém. Uma definição de classe lista todos os membros e funções de amigo. Ambos podem ser colocados em arquivos de cabeçalho sem problemas. O que você chama de "protótipo de função" é uma declaração de função . Mas uma definição de função é aquela coisa que contém o código da função e deve ser colocada em um arquivo cpp - a menos que seja embutido ou (parte de) um modelo.
Sellibitze
5
Eles têm significados precisos em C ++, eles não têm significados precisos em inglês. Minha resposta foi escrita neste último.
Amber
54

O fato é que, em C ++, isso é um pouco mais complicado do que a organização de cabeçalho / origem C.

O que o compilador vê?

O compilador vê um grande arquivo de origem (.cpp) com seus cabeçalhos incluídos corretamente. O arquivo de origem é a unidade de compilação que será compilada em um arquivo de objeto.

Então, por que os cabeçalhos são necessários?

Porque uma unidade de compilação pode precisar de informações sobre uma implementação em outra unidade de compilação. Assim, pode-se escrever, por exemplo, a implementação de uma função em uma fonte, e escrever a declaração dessa função em outra fonte que precise usá-la.

Nesse caso, existem duas cópias das mesmas informações. O que é mal ...

A solução é compartilhar alguns detalhes. Embora a implementação deva permanecer na Fonte, a declaração de símbolos compartilhados, como funções, ou definição de estruturas, classes, enums, etc., pode precisar ser compartilhada.

Os cabeçalhos são usados ​​para colocar esses detalhes compartilhados.

Mova para o cabeçalho as declarações do que precisa ser compartilhado entre várias fontes

Nada mais?

Em C ++, existem algumas outras coisas que podem ser colocadas no cabeçalho porque também precisam ser compartilhadas:

  • código embutido
  • modelos
  • constantes (geralmente aquelas que você deseja usar dentro dos interruptores ...)

Mova para o cabeçalho TUDO o que precisa ser compartilhado, incluindo implementações compartilhadas

Isso significa que pode haver fontes dentro dos cabeçalhos?

Sim. Na verdade, existem muitas coisas diferentes que podem estar dentro de um "cabeçalho" (ou seja, compartilhado entre as fontes).

  • Declarações de encaminhamento
  • declarações / definição de funções / estruturas / classes / modelos
  • implementação de código embutido e modelo

Torna-se complicado e, em alguns casos (dependências circulares entre símbolos), impossível mantê-lo em um cabeçalho.

Os cabeçalhos podem ser divididos em três partes

Isso significa que, em um caso extremo, você poderia ter:

  • um cabeçalho de declaração de encaminhamento
  • um cabeçalho de declaração / definição
  • um cabeçalho de implementação
  • uma fonte de implementação

Vamos imaginar que temos um MyObject modelado. Poderíamos ter:

// - - - - MyObject_forward.hpp - - - - 
// This header is included by the code which need to know MyObject
// does exist, but nothing more.
template<typename T>
class MyObject ;

.

// - - - - MyObject_declaration.hpp - - - - 
// This header is included by the code which need to know how
// MyObject is defined, but nothing more.
#include <MyObject_forward.hpp>

template<typename T>
class MyObject
{
   public :
      MyObject() ;
   // Etc.
} ;

void doSomething() ;

.

// - - - - MyObject_implementation.hpp - - - - 
// This header is included by the code which need to see
// the implementation of the methods/functions of MyObject,
// but nothing more.
#include <MyObject_declaration.hpp>

template<typename T>
MyObject<T>::MyObject()
{
   doSomething() ;
}

// etc.

.

// - - - - MyObject_source.cpp - - - - 
// This source will have implementation that does not need to
// be shared, which, for templated code, usually means nothing...
#include <MyObject_implementation.hpp>

void doSomething()
{
   // etc.
} ;

// etc.

Uau!

Na "vida real", geralmente é menos complicado. A maior parte do código terá apenas um cabeçalho / organização de origem simples, com algum código embutido no código-fonte.

Mas em outros casos (objetos modelados se conhecendo), tive que ter para cada objeto uma declaração separada e cabeçalhos de implementação, com uma fonte vazia incluindo esses cabeçalhos apenas para me ajudar a ver alguns erros de compilação.

Outra razão para dividir cabeçalhos em cabeçalhos separados pode ser acelerar a compilação, limitando a quantidade de símbolos analisados ​​ao estritamente necessário e evitando recompilação desnecessária de uma fonte que se preocupa apenas com a declaração direta quando uma implementação de método inline é alterada.

Conclusão

Você deve tornar sua organização de código o mais simples possível e o mais modular possível. Coloque o máximo possível no arquivo de origem. Exponha apenas nos cabeçalhos o que precisa ser compartilhado.

Mas no dia em que você tiver dependências circulares entre objetos modelados, não se surpreenda se sua organização de código se tornar um pouco mais "interessante" que a organização de cabeçalho / fonte simples ...

^ _ ^

paercebal
fonte
17

além de todas as outras respostas, direi o que você NÃO coloca em um arquivo de cabeçalho: a
usingdeclaração (o mais comum é using namespace std;) não deve aparecer em um arquivo de cabeçalho porque poluem o namespace do arquivo de origem no qual está incluída .

Adrien Plisson
fonte
+1 com uma advertência que você pode usar, desde que seja em algum namespace de detalhes (ou um namespace anônimo). Mas sim, nunca use usingpara trazer coisas para um namespace global em um cabeçalho.
KitsuneYMG
+1 Este é muito mais fácil de responder. :) Além disso, os arquivos de cabeçalho não devem conter namespaces anônimos .
Sellibitze
É normal que os arquivos de cabeçalho contenham namespaces anônimos, desde que você entenda o que isso significa, ou seja, que cada unidade de tradução terá uma cópia diferente do que você definiu para namespace. Funções inline em namespaces anônimos são recomendadas em C ++ para casos em que você usaria static inlineem C99, por causa de algo a ver com o que acontece quando você combina vinculação interna com modelos. Os namespaces Anon permitem que você "oculte" funções, preservando a ligação externa.
Steve Jessop
Steve, o que você escreveu não me convenceu. Escolha um exemplo concreto em que você acha que um namespace anon faz total sentido em um arquivo de cabeçalho.
sellibitze
6

O que compila em nada (pegada binária zero) vai para o arquivo de cabeçalho.

As variáveis ​​não compilam em nada, mas as declarações de tipo sim (porque elas apenas descrevem como as variáveis ​​se comportam).

funções não, mas funções inline sim (ou macros), porque elas produzem código apenas quando chamadas.

os modelos não são códigos, eles são apenas uma receita para criar código. então eles também vão em arquivos h.

Pavel Radzivilovsky
fonte
1
"funções inline ... produzem código apenas onde chamadas". Isso não é verdade. funções embutidas podem ou não ser embutidas em sites de chamada, mas mesmo se elas estiverem embutidas, o corpo da função real ainda existe da mesma forma que existe para uma função não embutida. O motivo pelo qual não há problema em ter funções embutidas nos cabeçalhos não tem nada a ver com o fato de elas gerarem código, é porque as funções embutidas não acionam a regra de definição única, portanto, ao contrário das funções não embutidas, não há problemas em vincular duas unidades de tradução diferentes que incluem o cabeçalho.
Steve Jessop
3

Em geral, você coloca declarações no arquivo de cabeçalho e definições no arquivo de implementação (.cpp). A exceção a isso são os modelos, em que a definição também deve ir no cabeçalho.

Esta pergunta e outras semelhantes a ela têm sido feitas com frequência no SO - consulte Por que os arquivos de cabeçalho e os arquivos .cpp no ​​C ++? e Arquivos de cabeçalho C ++, separação de código, por exemplo.

Comunidade
fonte
claro, você também pode colocar definições de classe em arquivos de cabeçalho. Eles nem precisam ser modelos.
Sellibitze
1

Suas declarações de classe e função mais a documentação e as definições para funções / métodos embutidos (embora alguns prefiram colocá-los em arquivos .inl separados).

Alexander Gessler
fonte
1

Principalmente o arquivo de cabeçalho contém o esqueleto da classe ou declaração (não muda com frequência)

e o arquivo cpp contém a implementação da classe (muda freqüentemente).

Ashish
fonte
5
Evite usar terminologia fora do padrão. O que é "esqueleto de classe", o que é "implementação de classe"? Além disso, o que você chama de declaração no contexto de classes provavelmente inclui definições de classe.
sellibitze
0

o arquivo de cabeçalho (.h) deve ser para declarações de classes, structs e seus métodos, protótipos, etc. A implementação desses objetos é feita em cpp.

em .h

    class Foo {
    int j;

    Foo();
    Foo(int)
    void DoSomething();
}
Jose
fonte
0

Eu esperaria ver:

  • declarações
  • comentários
  • definições marcadas inline
  • modelos

a verdadeira resposta é o que não colocar:

  • definições (pode levar a coisas sendo multiplicadas)
  • usando declarações / diretivas (força-as em qualquer pessoa, incluindo seu cabeçalho, pode causar intervalos de nome)
jk.
fonte
1
Você certamente pode colocar definições de classe em arquivos de cabeçalho também. Uma declaração de classe não diz nada sobre seus membros.
Sellibitze
0

O cabeçalho define algo, mas não diz nada sobre a implementação. (Excluindo modelos neste "metafore".

Com isso dito, você precisa dividir as "definições" em subgrupos, existem, neste caso, dois tipos de definições.

  • Você define o "layout" de sua estrutura, contando apenas o quanto é necessário para os grupos de uso ao redor.
  • As definições de uma variável, função e classe.

Agora, é claro que estou falando sobre o primeiro subgrupo.

O cabeçalho existe para definir o layout de sua estrutura, a fim de ajudar o resto do software a usar a implementação. Você pode querer vê-lo como uma "abstração" de sua implementação, o que é dito com veemência, mas acho que se encaixa muito bem neste caso.

Como os cartazes anteriores disseram e mostraram, você declara áreas de uso público e privado e seus cabeçalhos, isso também inclui variáveis ​​públicas e privadas. Agora, não quero entrar no design do código aqui, mas você pode querer considerar o que você colocou em seus cabeçalhos, já que essa é a camada entre o usuário final e a implementação.

Filip Ekberg
fonte
0
  • Arquivos de cabeçalho - não devem mudar durante o desenvolvimento com muita frequência -> você deve pensar e escrevê-los de uma vez (no caso ideal)
  • Arquivos fonte - mudanças durante a implementação
nothrow
fonte
Esta é uma prática. Para alguns projetos menores, pode ser o caminho a percorrer. Mas você pode tentar descontinuar funções e seus protótipos (em arquivos de cabeçalho), em vez de alterar sua assinatura ou removê-los. Pelo menos até mudar o número principal. Como quando 1.9.2 subiu para 2.0.0 beta.
TamusJRoyce
0

Cabeçalho (.h)

  • Macros e inclui necessários para as interfaces (o mínimo possível)
  • A declaração das funções e classes
  • Documentação da interface
  • Declaração de funções / métodos inline, se houver
  • externo para variáveis ​​globais (se houver)

Corpo (.cpp)

  • Resto de macros e inclui
  • Inclui o cabeçalho do módulo
  • Definição de funções e métodos
  • Variáveis ​​globais (se houver)

Como regra geral, você coloca a parte "compartilhada" do módulo no .h (a parte que os outros módulos precisam ser capazes de ver) e a parte "não compartilhada" no .cpp

PD: Sim, incluí variáveis ​​globais. Eu os usei algumas vezes e é importante não defini-los nos cabeçalhos, ou você obterá muitos módulos, cada um definindo sua própria variável.

EDIT: Modificado após o comentário de David

Khelben
fonte
Como regra geral, o mínimo possível de inclusões deve estar no arquivo .h, e o arquivo .cpp deve incluir todos os cabeçalhos necessários. Isso reduz o tempo de compilação e não polui os namespaces.
David Thornley