“Referência indefinida para” construtor de class de modelo

Eu não tenho ideia do porque isso está acontecendo, já que acho que tenho tudo devidamente declarado e definido.

Eu tenho o seguinte programa, projetado com modelos. É uma implementação simples de uma fila, com as funções de membro “add”, “substract” e “print”.

Eu defini o nó para a fila no bem “nodo_colaypila.h”:

#ifndef NODO_COLAYPILA_H #define NODO_COLAYPILA_H #include  template  class cola; template  class nodo_colaypila { T elem; nodo_colaypila* sig; friend class cola; public: nodo_colaypila(T, nodo_colaypila*); }; 

Em seguida, a implementação em “nodo_colaypila.cpp”

 #include "nodo_colaypila.h" #include  template  nodo_colaypila::nodo_colaypila(T a, nodo_colaypila* siguiente = NULL) { elem = a; sig = siguiente;//ctor } 

Depois, a definição e declaração da class de modelo de fila e suas funções:

“cola.h”:

 #ifndef COLA_H #define COLA_H #include "nodo_colaypila.h" template  class cola { nodo_colaypila* ult, pri; public: cola(); void anade(T&); T saca(); void print() const; virtual ~cola(); }; #endif // COLA_H 

“cola.cpp”:

 #include "cola.h" #include "nodo_colaypila.h" #include  using namespace std; template  cola::cola() { pri = NULL; ult = NULL;//ctor } template  void cola::anade(T& valor) { nodo_colaypila  * nuevo; if (ult) { nuevo = new nodo_colaypila (valor); ult->sig = nuevo; ult = nuevo; } if (!pri) { pri = nuevo; } } template  T cola::saca() { nodo_colaypila  * aux; T valor; aux = pri; if (!aux) { return 0; } pri = aux->sig; valor = aux->elem; delete aux; if(!pri) { ult = NULL; } return valor; } template  cola::~cola() { while(pri) { saca(); }//dtor } template  void cola::print() const { nodo_colaypila  * aux; aux = pri; while(aux) { cout <elem <sig; } } 

Então, eu tenho um programa para testar essas funções da seguinte forma:

“main.cpp”

 #include  #include "cola.h" #include "nodo_colaypila.h" using namespace std; int main() { float a, b, c; string d, e, f; cola flo; cola str; a = 3.14; b = 2.71; c = 6.02; flo.anade(a); flo.anade(b); flo.anade(c); flo.print(); cout << endl; d = "John"; e = "Mark"; f = "Matthew"; str.anade(d); str.anade(e); str.anade(f); cout << endl; c = flo.saca(); cout << "First In First Out Float: " << c << endl; cout << endl; f = str.saca(); cout << "First In First Out String: " << f << endl; cout << endl; flo.print(); cout << endl; str.print(); cout << "Hello world!" << endl; return 0; } 

Mas quando eu construo, o compilador gera erros em todas as instâncias da class de modelo:

Referência indefinida para `cola (float) :: cola () ‘… (na verdade é cola’ ‘:: cola (), mas isso não me deixa usar assim.)

E assim por diante. Ao todo, 17 advertências, contando as para as funções-membro que estão sendo chamadas no programa.

Por que é isso? Essas funções e construtores foram definidos. Eu pensei que o compilador poderia replace o “T” no modelo com “float”, “string” ou qualquer outra coisa; essa foi a vantagem de usar modelos.

Eu li em algum lugar aqui que eu deveria colocar a declaração de cada function no arquivo de header por algum motivo. Isso esta certo? E se sim, porque?

Desde já, obrigado.

   

Esta é uma questão comum na programação em C ++. Existem duas respostas válidas para isso. Há vantagens e desvantagens para ambas as respostas e sua escolha dependerá do contexto. A resposta comum é colocar toda a implementação no arquivo de header, mas há outra abordagem que será adequada em alguns casos. A escolha é sua.

O código em um modelo é meramente um ‘padrão’ conhecido pelo compilador. O compilador não compilará os construtores cola::cola(...) e cola::cola(...) até que seja forçado a fazê-lo. E devemos garantir que essa compilation aconteça para os construtores pelo menos uma vez em todo o processo de compilation, ou obteremos o erro de ‘referência indefinida’. (Isso se aplica aos outros methods de cola também.)

Entendendo o problema

O problema é causado pelo fato de que main.cpp e cola.cpp serão compilados separadamente primeiro. Em main.cpp , o compilador instanciará implicitamente as classs de modelo cola e cola porque essas instanciações específicas são usadas em main.cpp . A má notícia é que as implementações dessas funções de membro não estão em main.cpp , nem em qualquer arquivo de header incluído no main.cpp , e, portanto, o compilador não pode include versões completas dessas funções em main.o Ao compilar cola.cpp , o compilador não compilará essas instanciações, porque não há instanciações implícitas ou explícitas de cola ou cola . Lembre-se, ao compilar cola.cpp , o compilador não tem idéia de quais instanciações serão necessárias; e não podemos esperar que ele seja compilado para cada tipo para garantir que esse problema nunca aconteça! ( cola , cola , cola , cola< cola > … e assim por diante …)

As duas respostas são:

  • Diga ao compilador, no final de cola.cpp , quais classs de modelo específicas serão necessárias, forçando-o a compilar cola e cola .
  • Coloque a implementação das funções de membro em um arquivo de header que será incluído toda vez que qualquer outra ‘unidade de tradução’ (como main.cpp ) usar a class de modelo.

Resposta 1: Instancie explicitamente o modelo e suas definições de membro

No final de cola.cpp , você deve adicionar linhas explicitamente instanciando todos os modelos relevantes, como

 template class cola; template class cola; 

e você adiciona as seguintes duas linhas no final de nodo_colaypila.cpp :

 template class nodo_colaypila; template class nodo_colaypila; 

Isso garantirá que, quando o compilador estiver compilando o cola.cpp ele compilará explicitamente todo o código para as classs cola e cola . Da mesma forma, nodo_colaypila.cpp contém as implementações das classs nodo_colaypila< ...> .

Nesta abordagem, você deve garantir que toda a implementação seja colocada em um arquivo .cpp (ou seja, uma unidade de tradução) e que a instância explícita seja colocada após a definição de todas as funções (ou seja, no final do arquivo).

Resposta 2: Copie o código no arquivo de header relevante

A resposta comum é mover todo o código dos arquivos de implementação cola.cpp e nodo_colaypila.cpp para cola.h e nodo_colaypila.h . A longo prazo, isso é mais flexível, pois significa que você pode usar instanciações extras (por exemplo, cola ) sem mais nenhum trabalho. Mas isso pode significar que as mesmas funções são compiladas muitas vezes, uma vez em cada unidade de tradução. Esse não é um grande problema, pois o vinculador ignorará corretamente as implementações duplicadas. Mas isso pode retardar um pouco a compilation.

Resumo

A resposta padrão, usada pelo STL, por exemplo, e na maior parte do código que qualquer um de nós escreverá, é colocar todas as implementações nos arquivos de header. Mas em um projeto mais privado, você terá mais conhecimento e controle sobre quais classs de modelo particulares serão instanciadas. Na verdade, esse ‘bug’ pode ser visto como um recurso, pois impede que usuários do seu código usem acidentalmente instanciações que você não testou ou planejou (“Eu sei que isso funciona para cola e cola , Se você quiser usar outra coisa, diga-me primeiro e poderá verificar se funciona antes de ativá-lo. “).

Finalmente, há três outros pequenos erros no código em sua pergunta:

  • Você está perdendo um # #endif no final de nodo_colaypila.h
  • em cola.h nodo_colaypila* ult, pri; deve ser nodo_colaypila *ult, *pri; – ambos são pointers.
  • nodo_colaypila.cpp: O parâmetro padrão deve estar no arquivo de header nodo_colaypila.h , não neste arquivo de implementação.

Você terá que definir as funções dentro do seu arquivo de header.
Você não pode separar a definição de funções de modelo no arquivo de origem e as declarações no arquivo de header.

Quando um modelo é usado de uma maneira que aciona sua intuição, um compilador precisa ver essa definição de modelos específica. Essa é a razão pela qual os modelos geralmente são definidos no arquivo de header no qual eles são declarados.

Referência:
Norma C ++ 03, § 14.7.2.4:

A definição de um modelo de function não exportado, um modelo de function de membro não exportado ou uma function de membro não exportada ou um membro de dados estático de um modelo de class deve estar presente em cada unidade de conversão na qual ela é explicitamente instanciada.

EDITAR:
Para esclarecer a discussão sobre os comentários:
Tecnicamente, existem três maneiras de contornar este problema de vinculação:

  • Para mover a definição para o arquivo .h
  • Adicione instâncias explícitas no arquivo .cpp .
  • #include o arquivo .cpp que define o modelo no arquivo .cpp usando o modelo.

Cada um deles tem seus prós e contras,

Mover as definições para arquivos de header pode aumentar o tamanho do código (os compiladores modernos podem evitar isso), mas aumentará o tempo de compilation com certeza.

Usar a abordagem de instanciação explícita está voltando à abordagem de macro tradicional. Outra desvantagem é que é necessário saber quais tipos de modelo são necessários pelo programa. Para um programa simples, isso é fácil, mas para programas complicados isso se torna difícil de determinar antecipadamente.

Enquanto include arquivos cpp é confuso ao mesmo tempo, compartilha os problemas de ambas as abordagens acima.

Acho primeiro método o mais fácil de seguir e implementar e, portanto, defende usá-lo.

Este link explica onde você está errado:

[35.12] Por que não posso separar a definição de minha class de modelos de sua declaração e colocá-la dentro de um arquivo .cpp?

Coloque a definição de seus construtores, methods de destruidores e outras coisas no seu arquivo de header, e isso irá corrigir o problema.

Isso oferece outra solução:

Como posso evitar erros de linker com minhas funções de template?

No entanto, isso exige que você antecipe como seu modelo será usado e, como uma solução geral, é contra-intuitivo. Ele resolve o caso de canto, no entanto, onde você desenvolve um modelo a ser usado por algum mecanismo interno e quer policiar a maneira como ele é usado.