суббота, 19 января 2013 г.

Проблема линковщика с шаблонами в С++

Б-же, как меня з%?%"а эта проблема, убил на нее 2.5 часа, ни один преподаватель в вузе не сказал про эту проблему. Чувствую butthurt. Надеюсь, теперь вы не совершите такую же ошибку...

Предположим, что у нас есть класс (ОН находится в .h файле)

template < typename T>
class Single_Linked_List
{
private:
 Node *head;
 Node *tail;
 Node *current;
 unsigned size;
public:
        void Push_Front(const T& element);
 void Push_Back(const T& element);
 T Pop_Front();
 T Pop_Back();
////и дохрена методов внизу с конструктором деструктором
};

У нас есть .cpp файл, где находится описание всех методов итд:

#include "Single_Linked_List.h"
////
описание всех методов

Теперь мы попробуем потестить наш класс односвязного списка:

        Single_Linked_List<int> sll;
 sll.Push_Front(1);
 sll.Push_Front(1);

ОШИБКА -_-, в чем проблема ?
error LNK2019: unresolved external symbol "public: __thiscall Single_Linked_List::~Single_Linked_List(void)" (??1?$Single_Linked_List@H@@QAE@XZ) referenced in function _wmain 1>ListE.obj : error LNK2019: unresolved external symbol "public: void __thiscall Single_Linked_List::Push_Front(int const &)" (?Push_Front@?$Single_Linked_List@H@@QAEXABH@Z) referenced in function _wmain 1>ListE.obj : error LNK2019: unresolved external symbol "public: __thiscall Single_Linked_List::Single_Linked_List(void)" (??0?$Single_Linked_List@H@@QAE@XZ) referenced in function _wmain 1> : fatal error LNK1120: 3 unresolved externals
Видно, что компилятор ПРОСТО не видит .cpp файл. Теперь, подключим в main.cpp вместо .h файла, .cpp файл. О, ЧУДО, оно работает БЛ%^#АТЬ.
Давайте еще подробнее разберемся что к чему:

Заголовочный файл (header - .h) - файл, где находится описание, но не реализация.
.cpp - файл, где находится реализация. На самом деле они ничем не отличаются, разделение на .h и .cpp файлы - это просто формальность, для того, чтобы отличить, где реализация, а где описание. При компиляции, компилятор включает все include файлы в единую единицу трансляции и делают из него .obj файл. Потом из всех .obj файлов собирается программа. Если в .obj файле есть 1 реализация функции func, то она будет видна и в других .obj файлах, НО, если реализаций много, то будет ошибка линковщика.
В нашем случае реализацию шаблонной функции можно инстанцировать (генерирование типов и функций) множеством способов (func<int>, func<float>, func<double>...) поэтому будет ошибка.

Итак, эту проблему можно решить несколькими способами:

  1. Подключать в main.cpp не .h файла, а его реализацию .cpp файл.
  2. Всю реализацию хранить вместе с описанием (.h или .hpp файлы)
  3. Подключить в конце .h файла .cpp файл.
  4. Использовать export template (не поддерживается многими компиляторами, в том числе VS2010, поэтому я не смогу его продемонстрировать, он реализован только в Comeau)
  5. Просто написать объявление всех вариантов использования списка в cpp файле, например: template class Single_Linked_List<int>; потом с float и т. д.
  6. Использовать глобальную функцию, где опробовать все используемые методы в main'е, например (сделать только объявление глобальной функции в .h и все): 
void TemporaryFunction ()
{
 Single_Linked_List sll;
 sll.Push_Front(0);
 sll.Display();
}

После того, как в .h файле мы сделали такую функцию, теперь в main.cpp работают:
конструктор, деструктор, Push_Front, Display. С другими методами будет ошибка.

Из всех вариантов мне нравится третий, так как он не нарушает самой идеи раздельной компиляции и прост в понимании и реализации. Есть ли есть варианты, добавления и так далее, пишите в комментарии, всем, спасибо, за внимание :)

0 коммент.:

Отправить комментарий