Модель компиляции с разделением
В этой модели определение шаблона класса и определения встроенных функций-членов помещаются в заголовочный файл, а определения невстроенных функций-членов и статических данных-членов– в файл с исходным текстом программы. Иными словами, определения шаблона класса и его членов организованы так же, как определения обычных классов (не шаблонов) и их членов:
// ---- Queue.h ----
// объявляет Queue как экспортируемый шаблон класса
export template <class Type>
class Queue {
// ...
public:
Type& remove();
void add( const Type & );
// ...
};
// ---- Queue.C ----
// экспортированное определение шаблона класса Queue
// находится в Queue.h
#include "Queue.h"
template <class Type>
void Queue<Type>::add( const Type &val ) { ... }
template <class Type>
Type& Queue<Type>::remove() { ... }
Программа, в которой используется конкретизированная функция-член, должна перед конкретизацией включить заголовочный файл:
// ---- User.C ----
#include "Queue.h"
int main() {
// конкретизация Queue<int>
Queue<int> *p_qi = new Queue<int>;
int ival;
// ...
// правильно: конкретизация Queue<int>::add( const int & )
p_qi->add( ival );
// ...
}
Хотя определение шаблона для функции-члена add() не видно в файле User.C, конкретизированный экземпляр Queue<int>::add(const int &) вызывать оттуда можно. Но для этого шаблон класса необходимо объявить экспортируемым.
Если он экспортируется, то для использования конкретизированных функций-членов или статических данных-членов необходимо знать лишь определение самого шаблона. Определения членов могут отсутствовать в тех файлах, где они конкретизируются.
Чтобы объявить шаблон класса экспортируемым, перед словом template в его определении или объявлении нужно поставить ключевое слово export:
export template <class Type>
class Queue { ... };
В нашем примере слово export применено к шаблону класса Queue в файле Queue.h; этот файл включен в файл Queue.C, содержащий определения функций-членов add() и remove(), которые автоматически становятся экспортируемыми и не должны присутствовать в других файлах перед конкретизацией.
Отметим, что, хотя шаблон класса объявлен экспортируемым, его собственное определение должно присутствовать в файле User.C. Конкретизация Queue<int>::add() в User.C вводит определение класса, в котором объявлены функции-члены Queue<int>::add() и Queue<int>::remove(). Эти объявления обязаны предшествовать вызову указанных функций. Таким образом, слово export влияет лишь на обработку функций-членов и статических данных-членов.
экспортируемыми можно объявлять также отдельные члены шаблона. В этом случае ключевое слово export указывается не перед шаблоном класса, а только перед экспортируемыми членами. Например, если автор шаблона класса Queue хочет экспортировать лишь функцию-член Queue<Type>::add() (т.е. изъять из заголовочного файла Queue.h только ее определение), то слово export можно указать именно в определении функции-члена add():
// ---- Queue.h ----
template <class Type>
class Queue {
// ...
public:
Type& remove();
void add( const Type & );
// ...
};
// необходимо, так как remove() не экспортируется
template <class Type>
Type& Queue<Type>::remove() { ... }
// ---- Queue.C ----
#include "Queue.h"
// экспортируется только функция-член add()
export template <class Type>
void Queue<Type>::add( const Type &val ) { ... }
Обратите внимание, что определение шаблона для функции-члена remove() перенесено в заголовочный файл Queue.h. Это необходимо, поскольку remove() более не находится в экспортируемом шаблоне и, следовательно, ее определение должно быть видно во всех файлах, где вызываются конкретизированные экземпляры.
Определение функции-члена или статического члена шаблона объявляется экспортируемым только один раз во всей программе. Поскольку компилятор обрабатывает файлы последовательно, он обычно не в состоянии определить, что эти члены объявлены экспортируемыми в нескольких исходных файлах. В таком случае результаты могут быть следующими:
- при редактировании связей возникает ошибка, показывающая, что один и тот же член шаблона класса определен несколько раз;