Перейти к основному содержимому

Приложение 1. О шаблонных классах

Объявления классов ListNode и MyStack

// Файл MyStack.h
// Шаблонный класс MyStack на основе односвязного списка.
#ifndef MyStack_h // защита от повторной компиляции
#define MyStack_h // модуль подключен

// Шаблонный класс ListNode (узел односвязного списка)
template<class INF, class FRIEND>
class ListNode // узел списка
{
private:
INF d; // информационная часть узла
ListNode *next; // указатель на следующий узел списка
ListNode(void) { next = nullptr; } //конструктор
friend FRIEND;
};

// Шаблонный класс MyStack на основе односвязного списка.
template<class INF>
class MyStack {
typedef class ListNode<INF, MyStack<INF>> Node;
Node *top;
public:
MyStack(void); // конструктор
~MyStack(void); // освободить динамическую память
bool empty(void); // стек пустой?
bool push(INF n); // добавить узел в вершину стека
bool pop(void); // удалить узел из вершины стека
INF top_inf(void); // считать информацию из вершины стека
};

#endif

Почему мы не можем отделить определение шаблонного класса от его реализации?

к сведению

Основано на ответе на вопрос "Why can’t I separate the definition of my templates class from its declaration and put it inside a .cpp file?" из статьи "Templates | isocpp.org"

Чтобы понять, почему все так, как есть, сначала примите эти факты:

  • Шаблон - это не класс или функция. Шаблон - это "образец", который компилятор использует для создания семейства классов или функций.
  • Для того, чтобы компилятор сгенерировал код, он должен видеть как реализацию шаблона (а не только определение), так и конкретные типы/все, что используется для "заполнения" шаблона. Например, если вы пытаетесь использовать a Foo<int>, компилятор должен видеть как шаблон Foo, так и тот факт, что вы пытаетесь создать конкретный Foo<int>.
  • Ваш компилятор, вероятно, не запоминает детали одного .cpp файла во время компиляции другого .cpp файла. В то же время, некоторые компиляторы умеют смотреть на весь код программы целиком с целью его оптимизировать. Например, так умеет делать компилятор LCC для программирования под процессоры Эльбрус. Смотрите о том, как работает компилятор в лекции.

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

Однако авторами методического пособия рекомендуется придерживаться следующей стратегии:

  1. Определение и реализацию объявить в заголовочном файле (MyStack.h).
  2. Создать файл реализации MyStack.cpp с импортом заголовочного файла MyStack.h (#include "MyStack.h"). Это делается для того, чтобы не отклоняться от общего паттерна описания сборки в файле CMakeLists.txt.
  3. В файле CMakeLists.txt при вызове команды add_library не забыть передать MyStack.h и MyStack.cpp.
к сведению

См. также дополнительные сведения об использовании шаблонов в следующей лабораторной работе.