16.8.2. Модель компиляции с разделением

16.8.2. Модель компиляции с разделением

В этой модели определение шаблона класса и определения встроенных функций-членов помещаются в заголовочный файл, а определения невстроенных функций-членов и статических данных-членов - в файл с исходным текстом программы. Иными словами, определения шаблона класса и его членов организованы так же, как определения обычных классов (не шаблонов) и их членов:

// ---- 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 QueueType::add( const Type &val ) { ... }

template class Type

Type& Queue&Type&::remove() { ... }

Программа, в которой используется конкретизированная функция-член, должна перед конкретизацией включить заголовочный файл:

// ---- User.C ----

#include "Queue.h"

int main() {

// конкретизация Queue

Queue *p_qi = new Queue;

int ival;

// ...

// правильно: конкретизация Queue::add( const int & )

p_qi-add( ival );

// ...

}

Хотя определение шаблона для функции-члена add() не видно в файле User.C, конкретизированный экземпляр Queue::add(const int &) вызывать оттуда можно. Но для этого шаблон класса необходимо объявить экспортируемым.

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

Чтобы объявить шаблон класса экспортируемым, перед словом template в его определении или объявлении нужно поставить ключевое слово export:

export template class Type

class Queue { ... };

В нашем примере слово export применено к шаблону класса Queue в файле Queue.h; этот файл включен в файл Queue.C, содержащий определения функций-членов add() и remove(), которые автоматически становятся экспортируемыми и не должны присутствовать в других файлах перед конкретизацией.

Отметим, что, хотя шаблон класса объявлен экспортируемым, его собственное определение должно присутствовать в файле User.C. Конкретизация Queue::add() в User.C вводит определение класса, в котором объявлены функции-члены Queue::add() и Queue::remove(). Эти объявления обязаны предшествовать вызову указанных функций. Таким образом, слово export влияет лишь на обработку функций-членов и статических данных-членов.

экспортируемыми можно объявлять также отдельные члены шаблона. В этом случае ключевое слово export указывается не перед шаблоном класса, а только перед экспортируемыми членами. Например, если автор шаблона класса Queue хочет экспортировать лишь функцию-член Queue::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 QueueType::add( const Type &val ) { ... }

Обратите внимание, что определение шаблона для функции-члена remove() перенесено в заголовочный файл Queue.h. Это необходимо, поскольку remove() более не находится в экспортируемом шаблоне и, следовательно, ее определение должно быть видно во всех файлах, где вызываются конкретизированные экземпляры.

* Определение функции-члена или статического члена шаблона объявляется экспортируемым только один раз во всей программе. Поскольку компилятор обрабатывает файлы последовательно, он обычно не в состоянии определить, что эти члены объявлены экспортируемыми в нескольких исходных файлах. В таком случае результаты могут быть следующими: при редактировании связей возникает ошибка, показывающая, что один и тот же член шаблона класса определен несколько раз;

* компилятор неоднократно конкретизирует некоторый член одним и тем же множеством аргументов шаблона, что приводит к ошибке повторного определения во время связывания программы;

* компилятор конкретизирует член с помощью одного из экспортированных определений шаблона, игнорируя все остальные.

Следовательно, нельзя утверждать, что при наличии в программе нескольких определений экспортированного члена шаблона обязательно будет сгенерирована ошибка. Создавая программу, надо быть внимательным и следить за тем, чтобы определения членов находились только в одном исходном файле.

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

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