"Неопределенная ссылка на" конструктор класса шаблона

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

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

Я определил узел для очереди в прекрасном "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;
}

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

неопределенная ссылка на `cola (float):: cola () '... (на самом деле это cola'<'float'>':: cola (), но это не позволяет мне использовать его таким образом.)

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

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

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

Заранее спасибо.

3 ответа

Решение

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

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

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

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

Ссылка:
Стандарт C++03, § 14.7.2.4:

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

РЕДАКТИРОВАТЬ:
Чтобы уточнить обсуждение комментариев:
Технически, есть три способа обойти эту проблему связывания:

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

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

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

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

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

Я считаю, что первый метод проще всего следовать и применять, и, следовательно, отстаивать его использование.

Эта ссылка объясняет, где вы идете не так:

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

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

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

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

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

Другие вопросы по тегам