Приложение 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
) и включение этого файла реализации в конец заголовка. Пример этого содержится в этом ответе.
Однако авторами методического пособия рекомендуется придерживаться следующей стратегии:
- Определение и реализацию объявить в заголовочном файле (
MyStack.h
). - Создать файл реализации
MyStack.cpp
с импортом заголовочного файлаMyStack.h
(#include "MyStack.h"
). Это делается для того, чтобы не отклоняться от общего паттерна описания сборки в файлеCMakeLists.txt
. - В файле
CMakeLists.txt
при вызове командыadd_library
не забыть передатьMyStack.h
иMyStack.cpp
.
к сведению
См. также дополнительные сведения об использовании шаблонов в следующей лабораторной работе.