Divisão de classs C ++ modeladas em arquivos .hpp / .cpp – é possível?

Estou recebendo erros ao tentar compilar uma class de modelo C ++ que é dividida entre um arquivo .hpp e .cpp :

 $ g++ -c -o main.o main.cpp $ g++ -c -o stack.o stack.cpp $ g++ -o main main.o stack.o main.o: In function `main': main.cpp:(.text+0xe): undefined reference to 'stack::stack()' main.cpp:(.text+0x1c): undefined reference to 'stack::~stack()' collect2: ld returned 1 exit status make: *** [program] Error 1 

Aqui está o meu código:

stack.hpp :

 #ifndef _STACK_HPP #define _STACK_HPP template  class stack { public: stack(); ~stack(); }; #endif 

stack.cpp :

 #include  #include "stack.hpp" template  stack::stack() { std::cerr << "Hello, stack " << this << "!" << std::endl; } template  stack::~stack() { std::cerr << "Goodbye, stack " << this << "." << std::endl; } 

main.cpp :

 #include "stack.hpp" int main() { stack s; return 0; } 

ld é claro correto: os símbolos não estão em stack.o .

A resposta a esta pergunta não ajuda, como já estou fazendo como diz.
Este pode ajudar, mas eu não quero mover todos os methods para o arquivo .hpp – eu não deveria, devo?

É a única solução razoável para mover tudo no arquivo .cpp para o arquivo .hpp e simplesmente include tudo, em vez de vincular como um arquivo de object autônomo? Isso parece muito feio! Nesse caso, eu também poderia reverter para o meu estado anterior e renomear stack.cpp para stack.hpp e stack.hpp com isso.

Não é possível gravar a implementação de uma class de modelo em um arquivo cpp separado e compilar. Todas as maneiras de fazer isso, se alguém afirma, são soluções alternativas para imitar o uso do arquivo cpp separado, mas praticamente se você pretende escrever uma biblioteca de classs de modelo e distribuí-lo com arquivos de header e lib para ocultar a implementação, simplesmente não é possível .

Para saber por que, vamos olhar para o processo de compilation. Os arquivos de header nunca são compilados. Eles são apenas pré-processados. O código pré-processado é então batido com o arquivo cpp que é realmente compilado. Agora, se o compilador tiver que gerar o layout de memory apropriado para o object, ele precisará conhecer o tipo de dados da class de modelo.

Na verdade, deve ser entendido que a class de modelo não é uma class, mas um modelo para uma class cuja declaração e definição são geradas pelo compilador em tempo de compilation após obter as informações do tipo de dados do argumento. Contanto que o layout de memory não possa ser criado, as instruções para a definição do método não poderão ser geradas. Lembre-se que o primeiro argumento do método de class é o operador ‘this’. Todos os methods de class são convertidos em methods individuais com mangling de nome e o primeiro parâmetro como o object no qual ele opera. O argumento ‘this’ é o que realmente informa sobre o tamanho do object que o caso da class template não está disponível para o compilador, a menos que o usuário instancie o object com um argumento de tipo válido. Neste caso, se você colocar as definições do método em um arquivo cpp separado e tentar compilá-lo, o próprio arquivo object não será gerado com as informações da class. A compilation não falhará, ela geraria o arquivo de object, mas não gerará nenhum código para a class de modelo no arquivo de object. Essa é a razão pela qual o vinculador não consegue localizar os símbolos nos arquivos de object e a construção falha.

Agora, qual é a alternativa para ocultar detalhes importantes da implementação? Como todos sabemos, o principal objective por trás da separação da interface da implementação é ocultar os detalhes da implementação em formato binário. É aqui que você deve separar as estruturas de dados e os algoritmos. Suas classs de modelo devem representar apenas estruturas de dados e não os algoritmos. Isso permite que você oculte detalhes de implementação mais valiosos em bibliotecas de classs não-templatizadas separadas, as classs dentro das quais funcionariam nas classs de modelo ou apenas as usaria para armazenar dados. A class de modelo na verdade conteria menos código para atribuir, obter e definir dados. O restante do trabalho seria feito pelas classs do algoritmo.

Espero que esta discussão seja útil.

É possível, desde que você saiba quais instanciações você precisará.

Adicione o seguinte código no final do stack.cpp e ele funcionará:

 template class stack; 

Todos os methods de pilha não-modelo serão instanciados, e a etapa de vinculação funcionará bem.

Você pode fazer isso dessa maneira

 // xyz.h #ifndef _XYZ_ #define _XYZ_ template  class XYZ { //Class members declaration }; #include "xyz.cpp" #endif //xyz.cpp #ifdef _XYZ_ //Class definition goes here #endif 

Isso foi discutido em Daniweb

Também em FAQ, mas usando a palavra-chave de exportação C ++.

Não, não é possível. Não sem a palavra-chave export , que, para todos os efeitos, não existe realmente.

O melhor que você pode fazer é colocar suas implementações de function em um arquivo “.tcc” ou “.tpp” e #include o arquivo .tcc no final do seu arquivo .hpp. No entanto, isso é meramente cosmético; ainda é o mesmo que implementar tudo em arquivos de header. Este é simplesmente o preço que você paga por usar modelos.

Eu acredito que existem duas razões principais para tentar separar o código de modelo em um header e um cpp:

Um é por mera elegância. Nós todos gostamos de escrever código que é fácil de ler, gerenciar e é reutilizável depois.

Outro é a redução dos tempos de compilation.

Estou atualmente (como sempre) codificando software de simulação em conjunto com o OpenCL e gostamos de manter o código para que ele possa ser executado usando os tipos float (cl_float) ou double (cl_double) conforme necessário, dependendo da capacidade de HW. Agora, isso é feito usando um #define REAL no início do código, mas isso não é muito elegante. Alterar a precisão desejada requer recompilar o aplicativo. Como não há tipos reais de tempo de execução, temos que conviver com isso por enquanto. Felizmente, os kernels OpenCL são compilados em tempo de execução, e um tamanho simples (REAL) nos permite alterar o tempo de execução do código do kernel de acordo.

O problema muito maior é que, embora o aplicativo seja modular, ao desenvolver classs auxiliares (como aquelas que pré-calculam constantes de simulação) também é preciso modelar. Todas essas classs aparecem pelo menos uma vez no topo da tree de dependência de class, já que a simulação da class de modelo final terá uma instância de uma dessas classs de fábrica, significando que praticamente toda vez que eu fizer uma pequena alteração na class de fábrica, software tem que ser reconstruído. Isso é muito chato, mas não consigo encontrar uma solução melhor.

Às vezes, é possível ter a maior parte da implementação oculta no arquivo cpp, se você puder extrair a funcionalidade comum para todos os parâmetros de modelo em uma class que não seja de modelo (possivelmente insegura de tipo). Em seguida, o header conterá chamadas de redirecionamento para essa class. Abordagem semelhante é usada, quando lutando com o problema de “template bloat”.

Se você sabe com quais tipos sua pilha será usada, você pode instanciá-los explicitamente no arquivo cpp e manter todo o código relevante lá.

Também é possível exportá-los através de DLLs (!), Mas é bastante complicado obter a syntax correta (combinações específicas de MS-DOS de __declspec (dllexport) e a palavra-chave de exportação).

Nós usamos isso em uma matemática / geom lib que modelou double / float, mas tinha bastante código. (Eu pesquisei na época, não tenho esse código hoje).

O problema é que um template não gera uma class real, é apenas um template dizendo ao compilador como gerar uma class. Você precisa gerar uma class concreta.

A maneira fácil e natural é colocar os methods no arquivo de header. Mas existe outro caminho.

No seu arquivo .cpp, se você tiver uma referência a cada instanciação de modelo e método que você requer, o compilador irá gerá-los para uso em todo o seu projeto.

novo stack.cpp:

 #include  #include "stack.hpp" template  stack::stack() { std::cerr < < "Hello, stack " << this << "!" << std::endl; } template  stack::~stack() { std::cerr < < "Goodbye, stack " << this << "." << std::endl; } static void DummyFunc() { static stack stack_int; // generates the constructor and destructor code // ... any other method invocations need to go here to produce the method code } 

Você precisa ter tudo no arquivo hpp. O problema é que as classs não são realmente criadas até que o compilador veja que elas são necessárias por algum outro arquivo cpp – então, ele deve ter todo o código disponível para compilar a class modelo naquele momento.

Uma coisa que costumo fazer é tentar dividir meus modelos em uma parte genérica não modelada (que pode ser dividida entre cpp / hpp) e a parte de modelo específica do tipo que herda a class sem modelo.

Somente se você #include "stack.cpp no final de stack.hpp . Eu só recomendaria essa abordagem se a implementação fosse relativamente grande e se você renomeie o arquivo .cpp para outra extensão, como para diferenciá-lo do código normal .

Esta é uma questão bastante antiga, mas acho interessante assistir a apresentação de Arthur O’Dwyer no cppcon 2016 . Boa explicação, muito assunto coberto, um deve assistir.

Como os modelos são compilados quando necessário, isso força uma restrição para projetos de vários arquivos: a implementação (definição) de uma class ou function de modelo deve estar no mesmo arquivo que sua declaração. Isso significa que não podemos separar a interface em um arquivo de header separado e que devemos include a interface e a implementação em qualquer arquivo que use os modelos.

Outra possibilidade é fazer algo como:

 #ifndef _STACK_HPP #define _STACK_HPP template  class stack { public: stack(); ~stack(); }; #include "stack.cpp" // Note the include. The inclusion // of stack.h in stack.cpp must be // removed to avoid a circular include. #endif 

Eu não gosto dessa sugestão como uma questão de estilo, mas pode servir para você.

A palavra-chave ‘export’ é a maneira de separar a implementação do template da declaração do template. Isso foi introduzido no padrão C ++ sem uma implementação existente. No devido tempo, apenas alguns compiladores o implementaram. Leia informações detalhadas no artigo Inform IT sobre exportação

1) Lembre-se que o principal motivo para separar os arquivos .h e .cpp é ocultar a implementação da class como um código Obj compilado separadamente que pode ser vinculado ao código do usuário que incluiu um .h da class.

2) As classs sem modelo possuem todas as variables ​​concreta e especificamente definidas nos arquivos .h e .cpp. Portanto, o compilador terá a necessidade de informações sobre todos os tipos de dados usados ​​na class antes de compilar / traduzir  gerando o código de object / máquina As classs de modelo não possuem informações sobre o tipo de dados específico antes do usuário da class instanciar um object passando os dados necessários tipo:

  TClass myObj; 

3) Somente após essa instanciação, o compilador gera a versão específica da class de modelo para corresponder ao (s) tipo (s) de dados passado (s).

4) Portanto, .cpp NÃO pode ser compilado separadamente sem conhecer o tipo de dados específico do usuário. Portanto, ele deve permanecer como código-fonte dentro de “.h” até que o usuário especifique o tipo de dados requerido e, em seguida, ele pode ser gerado para um tipo de dados específico e depois compilado

Eu estou trabalhando com o Visual Studio 2010, se você gostaria de dividir seus arquivos para .h e .cpp, inclua o header do cpp no ​​final do arquivo .h