BoolStack< T, SIZE >::isEmpty () const

{

returnm_pTop == m_Data;

}

//*****************************************************************************

// Реализация метода определения заполненности стека

template< typenameT, intSIZE >

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< typenameT >

classMyTemplate

{

T * m_pData;

const intm_size;

public:

MyTemplate ( int_size );

~MyTemplate ();
};

//*****************************************************************************

#endif // _MYTEMPLATE_HPP_

mytemplate.cpp

#include"mytemplate.hpp"

//*****************************************************************************

template<typenameT >

MyTemplate< T >::MyTemplate ( int_size )

: m_size( _size )

{

m_pData = newT[ m_size ];

}

//*****************************************************************************

template<typenameT >

MyTemplate< T >::~MyTemplate ()

{

delete[] m_pData;

}

//*****************************************************************************

test.cpp

#include "mytemplate.hpp"

//*****************************************************************************

intmain ()

{

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 classMyTemplate< int>;

template classMyTemplate< std::string >;

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

Дополнительно, в С++11 был введен синтаксис внешнего явного инстанцирования, который помогает компилятору не делать повторяющиеся инстанцирования одного и того же шаблона в различных CPP-файлах:

extern template classMyTemplate< int>;

Такое объявление блокирует попытки неявного инстанцирования, как и обычное явное инстанцирование, однако, само по себе, не генерирует никакого машинного кода. Компилятор лишь получает информацию, что такой случай уже был инстанцирован где-либо еще в других CPP-файлах, при этом, конечный машинный код должен быть найден и состыкован на этапе компоновки, а в данном контексте можно сэкономить на времени компиляции, не делая лишней работы.

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

До редакции стандарта С++’11 предусматривался третий способ организации компоновки шаблонов - ключевое слово export - однако с годами этот метод не прижился на практике, и ведущие производители компиляторов отказались от его реализации в виду слишком сложной схемы. В новой редакции стандарта этот механизм был объявлен устаревшим.

Наши рекомендации