Diferença entre implementar uma classe dentro de um arquivo .h ou em um arquivo .cpp
Pergunta
Fiquei me perguntando quais são as diferenças entre declarar e implementar uma classe apenas em um arquivo de cabeçalho, em comparação com a abordagem normal na qual você protipo no cabeçalho e implementa no arquivo .cpp eficaz.
Para explicar melhor sobre o que estou falando, quero dizer diferenças entre a abordagem normal:
// File class.h
class MyClass
{
private:
//attributes
public:
void method1(...);
void method2(...);
...
};
//file class.cpp
#include "class.h"
void MyClass::method1(...)
{
//implementation
}
void MyClass::method2(...)
{
//implementation
}
e a Just Header abordagem:
// File class.h
class MyClass
{
private:
//attributes
public:
void method1(...)
{
//implementation
}
void method2(...)
{
//implementation
}
...
};
Eu posso obter a principal diferença: no segundo caso, o código está incluído em todos os outros arquivos que precisam que ele gerem mais instâncias das mesmas implementações, portanto, uma redundância implícita; enquanto no primeiro caso o código é compilado por si mesmo e depois todas as chamadas referidas ao objeto de MyClass
estão ligados à implementação em class.cpp
.
Mas existem outras diferenças? É mais conveniente usar uma abordagem em vez de outra, dependendo da situação? Também li em algum lugar que definir o corpo de um método diretamente em um arquivo de cabeçalho é uma solicitação implícita ao compilador para embalar esse método, é verdade?
Solução
A principal diferença prática é que, se as definições de função do membro estiverem no corpo do cabeçalho, é claro que elas serão compiladas uma vez para cada unidade de tradução, que inclui esse cabeçalho. Quando o seu projeto contém algumas centenas ou milhares de arquivos de origem, e a classe em questão é bastante usada, isso pode significar muita repetição. Mesmo que cada classe seja usada apenas por 2 ou 3 outras, mais código no cabeçalho, mais trabalho a fazer.
Se as definições de função do membro estiverem em uma unidade de tradução (arquivo .cpp), elas serão compiladas uma vez e apenas as declarações de função serão compiladas várias vezes.
É verdade que as funções de membro definidas (não apenas declaradas) na definição de classe são implicitamente inline
. Mas inline
Não significa o que as pessoas podem adivinhar razoavelmente que isso significa. inline
diz que é legal que várias definições da função apareçam em diferentes unidades de tradução e posteriormente sejam vinculadas. Isso é necessário se a classe estiver em um arquivo de cabeçalho que diferentes arquivos de origem vão usar, portanto o idioma tenta ser útil.
inline
também é uma dica para o compilador de que a função poderia ser usada, mas, apesar do nome, isso é opcional. Quanto mais sofisticado o seu compilador for, melhor será capaz de tomar suas próprias decisões sobre a inline e, menos a necessidade é de dicas. Mais importante que a tag embutida é se a função está disponível para o compilador. Se a função for definida em uma unidade de tradução diferente, ela não estará disponível quando a chamada para ela será compilada e, portanto, se alguma coisa estiver embutida na chamada, terá que ser o vinculador, não o compilador.
Você pode ver melhor as diferenças, considerando uma terceira maneira possível de fazê -lo:
// File class.h
class MyClass
{
private:
//attributes
public:
void method1(...);
void method2(...);
...
};
inline void MyClass::method1(...)
{
//implementation
}
inline void MyClass::method2(...)
{
//implementation
}
Agora que o inline implícito está fora do caminho, permanecem algumas diferenças entre essa abordagem de "todos os cabeçalhos" e a abordagem "Header Plus Source". Como você divide seu código entre as unidades de tradução tem consequências para o que acontece como é construído.
Outras dicas
Sim, o compilador tentará embalar um método declarado diretamente no arquivo de cabeçalho como:
class A
{
public:
void method()
{
}
};
Eu posso pensar em seguir as conveniências na separação da implementação nos arquivos de cabeçalho:
- Você não terá código Bloat devido ao mesmo código ser incluído em várias unidades de tradução
- Seu tempo de compilação reduzirá drasticamente. Lembre -se de que, para qualquer modificação no compilador de arquivos do cabeçalho, deve criar todos os outros arquivos que o incluam direta ou indiretamente. Eu acho que será muito frustrante para alguém construir todo o binário novamente apenas para adicionar um espaço no arquivo de cabeçalho.
Qualquer alteração em um cabeçalho que inclua a implementação forçará todas as outras classes que incluem esse cabeçalho para recompilar e revincular.
Como os cabeçalhos mudam com menos frequência do que as implementações, colocando a implementação em um arquivo separado, você pode economizar tempo de compilação considerável.
Como algumas outras respostas já apontaram, sim, definindo um método dentro de um arquivo class
O bloco fará com que o compilador seja embutido.
Sim, definir métodos dentro da definição de classe é equivalente a declará -los inline
. Não há outra diferença. Não há benefício em definir tudo no arquivo de cabeçalho.
Algo assim geralmente é visto em C ++ com classes de modelo, pois as definições de membros do modelo também devem ser incluídas no arquivo de cabeçalho (devido ao fato de que a maioria dos compiladores não suporta export
). Mas com aulas comuns não-templos, não há sentido em fazer isso, a menos que você realmente queira declarar seus métodos como inline
.
Para mim, a principal diferença é que um arquivo de cabeçalho é como uma "interface" para a classe, dizendo aos clientes dessa classe quais são seus métodos públicos (as operações que ele suporta), sem que os clientes se preocupem com a implementação específica desses. No sentido, é uma maneira de encapsular seus clientes das alterações de implementação, porque apenas as alterações do arquivo CPP e, portanto, o tempo de compilação é muito menor.
Uma vez no passado, criei um módulo protegendo a partir de diferenças em várias distribuições do CORBA e esperava -se que trabalhasse uniformemente em várias combinações de OS/compilador/corba. Torná -lo implementado em um arquivo de cabeçalho tornou mais fácil adicioná -lo a um projeto com uma inclusão simples. A mesma técnica garantiu que o código foi recompilado ao mesmo tempo em que o código que chamava de recompilação quando IE estava sendo compilado com uma biblioteca diferente ou em um sistema operacional diferente.
Então, meu argumento é que, se você tem uma pequena biblioteca que deve ser reutilizável e recompilável em vários projetos, tornando -o um cabeçalho oferece vantagens na integração com outros projetos, em vez de adicionar arquivos extras ao projeto principal ou recompilar uma lib externa /arquivo obj.