"Undefined ссылка на" конструктор класса шаблона

Я понятия не имею, почему это происходит, так как я думаю, что все правильно объявлено и определено.

У меня есть следующая программа, разработанная с использованием шаблонов. Это простая реализация очереди, с функциями-членами "add", "substract" и "print".

Я определил node для очереди в тонком "nodo_colaypila.h":

#ifndef NODO_COLAYPILA_H
#define NODO_COLAYPILA_H

#include <iostream>

template <class T> class cola;

template <class T> class nodo_colaypila
{
        T elem;
        nodo_colaypila<T>* sig;
        friend class cola<T>;
    public:
        nodo_colaypila(T, nodo_colaypila<T>*);

};

Затем реализация в "nodo_colaypila.cpp"

#include "nodo_colaypila.h"
#include <iostream>

template <class T> nodo_colaypila<T>::nodo_colaypila(T a, nodo_colaypila<T>* siguiente = NULL)
{
    elem = a;
    sig = siguiente;//ctor
}

Затем определение и объявление класса шаблона очереди и его функций:

"cola.h":

#ifndef COLA_H
#define COLA_H

#include "nodo_colaypila.h"

template <class T> class cola
{
        nodo_colaypila<T>* ult, pri;
    public:
        cola<T>();
        void anade(T&);
        T saca();
        void print() const;
        virtual ~cola();

};


#endif // COLA_H

"cola.cpp":

#include "cola.h"
#include "nodo_colaypila.h"

#include <iostream>

using namespace std;

template <class T> cola<T>::cola()
{
    pri = NULL;
    ult = NULL;//ctor
}

template <class T> void cola<T>::anade(T& valor)
{
    nodo_colaypila <T> * nuevo;

    if (ult)
    {
        nuevo = new nodo_colaypila<T> (valor);
        ult->sig = nuevo;
        ult = nuevo;
    }
    if (!pri)
    {
        pri = nuevo;
    }
}

template <class T> T cola<T>::saca()
{
    nodo_colaypila <T> * aux;
    T valor;

    aux = pri;
    if (!aux)
    {
        return 0;
    }
    pri = aux->sig;
    valor = aux->elem;
    delete aux;
    if(!pri)
    {
        ult = NULL;
    }
    return valor;
}

template <class T> cola<T>::~cola()
{
    while(pri)
    {
        saca();
    }//dtor
}

template <class T> void cola<T>::print() const
{
    nodo_colaypila <T> * aux;
    aux = pri;
    while(aux)
    {
        cout << aux->elem << endl;
        aux = aux->sig;
    }
}

Затем у меня есть программа для проверки этих функций следующим образом:

"main.cpp"

#include <iostream>
#include "cola.h"
#include "nodo_colaypila.h"

using namespace std;

int main()
{
    float a, b, c;
    string d, e, f;
    cola<float> flo;
    cola<string> 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;
}

Но когда я создаю, компилятор выдает ошибки в каждом экземпляре класса шаблона:

undefined ссылка на `cola (float):: cola() '... (это фактически cola'<'float' > ':: cola(), но это doesn "Позвольте мне использовать его так.)

И так далее. В общей сложности 17 предупреждений, считая те, что для функций-членов вызывается в программе.

Почему это? Определены эти функции и конструкторы. Я думал, что компилятор может заменить "Т" в шаблоне "float", "string" или что-то еще; это было преимуществом использования шаблонов.

Я где-то читал, что по какой-то причине я должен поместить объявление каждой функции в заголовочный файл. Это правильно? И если да, то почему?

Спасибо заранее.

Ответ 1

Это общий вопрос в программировании на C++. На это есть два действительных ответа. Есть преимущества и недостатки обоих ответов, и ваш выбор будет зависеть от контекста. Общий ответ заключается в том, чтобы поместить всю реализацию в файл заголовка, но в некоторых случаях будет использоваться другой подход. Выбор за вами.

Код в шаблоне - это просто "шаблон", известный компилятору. Компилятор не будет компилировать конструкторы cola<float>::cola(...) и cola<string>::cola(...), пока это не будет принудительно. И мы должны убедиться, что эта компиляция выполняется для конструкторов хотя бы один раз во всем процессе компиляции, или мы получим ошибку "undefined reference". (Это относится и к другим методам cola<T>.)

Понимание проблемы

Проблема вызвана тем, что main.cpp и cola.cpp будут сначала скомпилированы отдельно. В main.cpp компилятор будет неявно создавать экземпляры классов шаблонов cola<float> и cola<string>, поскольку эти конкретные экземпляры используются в main.cpp. Плохая новость заключается в том, что реализации этих функций-членов не находятся в main.cpp, ни в любом заголовочном файле, включенном в main.cpp, и поэтому компилятор не может включать полные версии этих функций в main.o. При компиляции cola.cpp компилятор также не будет компилировать эти экземпляры, потому что не существует явных или явных экземпляров cola<float> или cola<string>. Помните, что при компиляции cola.cpp компилятор не имеет понятия, какие потребуются экземпляры; и мы не можем ожидать, что он будет компилироваться для каждого типа, чтобы эта проблема никогда не происходила! (cola<int>, cola<char>, cola<ostream>, cola< cola<int> >... и так далее...)

Два ответа:

  • Сообщите компилятору, в конце cola.cpp, какие именно классы шаблонов потребуются, заставляя его компилировать cola<float> и cola<string>.
  • Поместите реализацию функций-членов в файл заголовка, который будет включаться каждый раз, когда любая другая "единица перевода" (например, main.cpp) использует класс шаблона.

Ответ 1: Явным образом создайте экземпляр шаблона и его определения членов

В конце cola.cpp вы должны добавить строки, явно создающие экземпляры всех соответствующих шаблонов, например

template class cola<float>;
template class cola<string>;

и вы добавите следующие две строки в конце nodo_colaypila.cpp:

template class nodo_colaypila<float>;
template class nodo_colaypila<std :: string>;

Это гарантирует, что когда компилятор компилирует cola.cpp, он будет явно компилировать весь код для классов cola<float> и cola<string>. Аналогично, nodo_colaypila.cpp содержит реализации классов nodo_colaypila<...>.

В этом подходе вы должны убедиться, что вся реализация помещена в один файл .cpp (т.е. одна единица перевода) и что явная компоновка помещается после определения всех функций (т.е. в конце файл).

Ответ 2: Скопируйте код в соответствующий файл заголовка

Общий ответ - переместить весь код из файлов реализации cola.cpp и nodo_colaypila.cpp в cola.h и nodo_colaypila.h. В конечном счете это более гибко, поскольку это означает, что вы можете использовать дополнительные экземпляры (например, cola<char>) без какой-либо дополнительной работы. Но это может означать, что одни и те же функции компилируются много раз, один раз в каждой единицы перевода. Это не большая проблема, так как компоновщик будет правильно игнорировать дубликаты реализаций. Но это может немного замедлить компиляцию.

Резюме

Ответ по умолчанию, используемый STL, например, и в большинстве кода, который любой из нас напишет, заключается в том, чтобы поместить все реализации в файлы заголовков. Но в более частном проекте у вас будет больше знаний и контроля над тем, какие конкретные классы шаблонов будут созданы. Фактически, эту "ошибку" можно рассматривать как функцию, так как она не позволяет пользователям вашего кода случайно использовать экземпляры, которые вы не тестировали или не планировали ( "Я знаю, что это работает для cola<float> и cola<string>, если вы хотите использовать что-то еще, скажите мне сначала, и он сможет проверить, работает ли он, прежде чем включать его." ).

Наконец, есть три других незначительных опечатки в коде в вашем вопросе:

  • Вам не хватает #endif в конце nodo_colaypila.h
  • в cola.h nodo_colaypila<T>* ult, pri; должен быть nodo_colaypila<T> *ult, *pri; - оба являются указателями.
  • nodo_colaypila.cpp: параметр по умолчанию должен находиться в файле заголовка nodo_colaypila.h, а не в этом файле реализации.

Ответ 2

Вам нужно будет определить функции внутри вашего файла заголовка.
Вы не можете отделить определение функций шаблона в исходном файле и объявления в заголовочном файле.

Когда шаблон используется таким образом, чтобы запускать его intstation, компилятор должен видеть, что определенные определения шаблонов. Именно по этой причине шаблоны часто определяются в заголовочном файле, в котором они объявлены.

Справка:
стандарт С++ 03, § 14.7.2.4:

Определение неэкспортированного шаблона функции, шаблон неэкспортируемой функции-члена или неэкспортированная функция-член или статический элемент данных шаблона класса должны присутствовать в каждая единица перевода, в которой она явно создается.

EDIT:
Чтобы прояснить обсуждение комментариев:
Технически существует три способа обойти эту проблему:

  • Чтобы переместить определение в файл .h
  • Добавить явные экземпляры в файле .cpp.
  • #include файл .cpp, определяющий шаблон в файле .cpp с помощью шаблона.

У каждого из них есть свои плюсы и минусы,

Перемещение определений в файлы заголовков может увеличить размер кода (современные компиляторы могут избежать этого), но наверняка увеличит время компиляции.

Использование метода явного инстанцирования возвращается к традиционному макроподобному подходу. Другой недостаток заключается в том, что необходимо знать, какие типы шаблонов необходимы программе. Для простой программы это легко, но для сложной программы это становится трудно определить заранее.

Хотя включение файлов cpp запутывает, в то же время разделяет проблемы обоих вышеуказанных подходов.

Я нахожу первый метод, который проще всего выполнить и реализовать, и, следовательно, использовать его.

Ответ 3

В этой ссылке объясняется, где вы ошибетесь:

[35.12] Почему я не могу отделить определение класса шаблонов от его объявления и поместить его в файл .cpp?

Поместите определение ваших конструкторов, методов деструкторов и многое другое в свой файл заголовка, и это устранит проблему.

Это предлагает другое решение:

Как избежать ошибок компоновщика с моими функциями шаблона?

Однако это требует, чтобы вы ожидали, как будет использоваться ваш шаблон, и, в качестве общего решения, противоречат интуиции. Это разрешает угловой случай, хотя вы разрабатываете шаблон, который будет использоваться каким-то внутренним механизмом, и вы хотите, чтобы он отслеживал, как он используется.