{
return m_pTop == m_Data;
}
//*****************************************************************************
// Реализация метода определения заполненности стека
template < typename T, int SIZE >
BoolStack< T, SIZE >::isFull () const
{
return (m_pTop - m_Data) == SIZE;
}
//*****************************************************************************
#endif // _STACK_FIXED_ARRAY_HPP_
Следует отметить, что предыдущая тестовая программа полностью компилируется как с прежней, так и с новой реализацией, хотя внутренний способ хранения значительно изменился.
Компоновка шаблонов
Размещение реализаций методов классов-шаблонов в заголовочных файлах обуславливается особенностями их компиляции и компоновки. Как было указано ранее, не вызванный нигде метод шаблонного класса не инстанцируется, даже если инстанцируется сам класс..
Такая модель делает непригодным классический вариант размещения кода шаблона класса в паре заголовочный файл - файл реализации. Представим процесс на следующем примере. Пусть имеется простейший шаблон, объявление которого помещено в заголовочный файл, а реализация методов - в CPP-файл. При этом, клиентский код, использующий данную абстракцию находится в отдельном от шаблона CPP-файле:
mytemplate.hpp
#ifndef _MYTEMPLATE_HPP_
#define _MYTEMPLATE_HPP_
//*****************************************************************************
template < typename T >
class MyTemplate
{
T * m_pData;
const int m_size;
public:
MyTemplate (int _size);
~MyTemplate ();
};
//*****************************************************************************
#endif // _MYTEMPLATE_HPP_
mytemplate.cpp
#include "mytemplate.hpp"
//*****************************************************************************
template < typename T >
MyTemplate< T >::MyTemplate (int _size)
: m_size(_size)
{
m_pData = new T[ m_size ];
}
//*****************************************************************************
template < typename T >
MyTemplate< T >::~MyTemplate ()
{
delete[] m_pData;
}
//*****************************************************************************
test.cpp
#include "mytemplate.hpp"
//*****************************************************************************
int main ()
{
MyTemplate< int > o(10);
}
//*****************************************************************************
Компиляция такой программы проходит успешно, в отличие от компоновки. Компоновщик выдаст загадочную на первый взгляд ошибку - будто бы отсутствует определение конструктора и деструктора:
error LNK2019: unresolved external symbol "public: __thiscall MyTemplate<int>::~MyTemplate<int>(void)" (??1?$MyTemplate@H@@QAE@XZ) referenced in function _main
error LNK2019: unresolved external symbol "public: __thiscall MyTemplate<int>::MyTemplate<int>(int)" (??0?$MyTemplate@H@@QAE@H@Z) referenced in function _main
Если задуматься, то такая ошибка является абсолютно логичной. Файлы test.cpp и mytemplate.cpp компилируются отдельными сеансами обработки. Каждый из этих исходных файлов должен породить собственный объектный файл. Выполняется следующий сценарий:
1. В файле mytemplate.cpp шаблон никак не инстанцируется, соответственно объектный файл не будет содержать машинного кода для определенных в нем методов (ведь они же никому не нужны в этом файле).
2. Файл test.cpp, включив заголовочный файл mytemplate.hpp, инстанцирует шаблон и вызывает искомые конструктор (явно) и деструктор (неявно). В данном объектном файле их нет, но компилятор не обращает на это никакого внимания, ведь поиск тел функций - задача этапа компоновки.
3. Компоновщик начинает работу, и не находит тел конструктора и деструктора, т.к. в месте инстанцирования тела не доступны, а в месте определения - тела не инстанцируются.
Именно по этой причине большинство обобщенного кода помещается в заголовочные файлы. Попав в место использования из заголовочного файла, гарантируется инстанцирование тел вызванных методов. Даже если несколько CPP-файлов программы инстанцируют один и тот же шаблон с одними и теми же аргументами, компоновщик исключит из рассмотрения сгенерированные в каждом объектом файле функции-дубликаты, зная о таких особенностях компоновки шаблонного кода.
Альтернативой включению тел методов в заголовочные файлы является механизм явного инстанцирования (explicit instantiation). Если заранее известно с какими типами будет инстанцирован шаблон класса, то можно заранее искусственно инстанцировать тела всех методов в CPP-файле реализации. Например, известно ограничение, что шаблон MyTemplate будет инстанцироваться только с двумя типами - int и std::string. В таком случае в конце файла mytemplate.cpp следует добавить следующие директивы явного инстанцирования:
template class MyTemplate< int >;
template class MyTemplate< std::string >;
Увидев такие директивы, компилятор инстанцирует все известные ему тела методов шаблона MyTemplate с указанными типами, будет сгенерирован реальный машинный код, который попадет в объектный файл. В последствии, при использовании данного шаблона в другом CPP-файле, компоновщик без труда обнаружит подходящие функции.
Дополнительно, в С++11 был введен синтаксис внешнего явного инстанцирования, который помогает компилятору не делать повторяющиеся инстанцирования одного и того же шаблона в различных CPP-файлах:
extern template class MyTemplate< int >;
Такое объявление блокирует попытки неявного инстанцирования, как и обычное явное инстанцирование, однако, само по себе, не генерирует никакого машинного кода. Компилятор лишь получает информацию, что такой случай уже был инстанцирован где-либо еще в других CPP-файлах, при этом, конечный машинный код должен быть найден и состыкован на этапе компоновки, а в данном контексте можно сэкономить на времени компиляции, не делая лишней работы.
Модель явного инстанцирования подходит для шаблонов, используемых конечными прикладными программами, когда набор типов, с которыми производится инстанцирование, конечен и заранее известен. Такая модель совершенно не подходит для библиотек шаблонов, которые потенциально могут быть инстанцированы с любым подходящим под описание типом данных. В таких случаях доступна только модель включения - т.е. размещения тел методов в заголовочном файле.
До редакции стандарта С++’11 предусматривался третий способ организации компоновки шаблонов - ключевое слово export - однако с годами этот метод не прижился на практике, и ведущие производители компиляторов отказались от его реализации в виду слишком сложной схемы. В новой редакции стандарта этот механизм был объявлен устаревшим.