Язык программирования C++. Пятое издание - Стенли Липпман
Шрифт:
Интервал:
Закладка:
template <typename It> // параметр типа для конструктора
Blob<T>::Blob(It b, It е) :
data(std::make_shared<std::vector<T>>(b, e)) { }
Здесь определяется член шаблона класса, у которого есть один параметр типа шаблона Т. Сам член является шаблоном функции, имеющий параметр типа It.
Создание экземпляров и шаблоны-членыЧтобы создать экземпляр шаблона-члена шаблона класса, следует предоставить аргументы для параметров шаблона и класса, и функции. Как обычно, аргументы для параметров шаблона класса определяются типом объекта, через который происходит вызов шаблона-члена. Так же как обычно, компилятор, как правило, выводит тип аргументов шаблона для собственных параметров шаблона-члена из аргументов, переданных при вызове (см. раздел 16.1.1):
int ia[] = {0,1,2,3,4,5,6,7,8,9};
vector<long> vi = {0,1,2,3,4,5,6,7,8,9};
list<const char*> w = {"now", "is", "the", "time"};
// создает экземпляр класса Blob<int>
// и конструктор Blob<int> с двумя параметрами типа int*
Blob<int> a1(begin(ia), end(ia));
// создает экземпляр конструктора Blob<int> с двумя параметрами
// типа vector<long>::iterator
Blob<int> а2(vi.begin(), vi.end());
// создает экземпляр класса Blob<string> и конструктор Blob<string>
// с двумя параметрами типа list<const char*>::iterator
Blob<string> a3(w.begin(), w.end());
При определении a1 указывается явно, что компилятор должен создать экземпляр шаблона Blob с параметром типа int. Параметр типа для его собственных параметров конструктора будет выведен из типа результатов вызова функций begin(ia) и end(ia). Этим типом является int*. Таким образом, определение a1 создает следующий экземпляр:
Blob<int>::Blob(int*, int*);
Определение а2 использует уже готовый экземпляр класса Blob<int> и создает экземпляр конструктора с параметром типа It, замененным на vector<short>::iterator. Определение a3 (явно) создает экземпляр шаблона Blob с собственным параметром шаблона типа string и (неявно) экземпляр конструктора шаблона-члена этого класса с собственным параметром типа list<const char*>.
Упражнения раздела 16.1.4Упражнение 16.21. Напишите собственную версию типа DebugDelete.
Упражнение 16.22. Пересмотрите программы TextQuery из раздела 12.3 так, чтобы указатель-член shared_ptr использовал тип DebugDelete как свою функцию удаления (см. раздел 12.1.4).
Упражнение 16.23. Предскажите, когда будет выполняться оператор вызова в вашей основной программе запроса. Если предсказание неправильно, убедитесь, что понимаете почему.
Упражнение 16.24. Добавьте в свой шаблон Blob конструктор, получающий два итератора.
16.1.5. Контроль создания экземпляра
Тот факт, что экземпляр шаблона создается только при его использовании (см. раздел 16.1.1), означает, что создание того же экземпляра может происходить в нескольких объектных файлах. Когда два или более отдельно откомпилированных файла исходного кода используют тот же шаблон с теми же аргументами шаблона, создание экземпляра этого шаблона осуществляется в каждом из этих файлов.
В больших системах дополнительные затраты на создание экземпляра того же шаблона в нескольких файлах могут оказаться существенными. По новому стандарту можно избежать этих дополнительных затрат за счет явного создания экземпляра (explicit instantiation). Его форма такова:
extern template объявление; // объявление создания экземпляра
template объявление; // определение создания экземпляра
где объявление — это объявление класса или функции, в котором все параметры шаблона заменены аргументами шаблона. Например:
// объявление и определение создания экземпляра
extern template class Blob<string>; // объявление
template int compare(const int&, const int&); // определение
Когда компилятор встретит внешнее (extern) объявление шаблона, он не будет создавать код его экземпляра в этом файле. Объявление экземпляра как extern является обещанием того, что будет и не внешнее создание экземпляра в другом месте программы. Вполне может быть несколько внешних объявлений для каждого экземпляра, однако по крайней мере одно определение экземпляра должно быть.
Поскольку компилятор автоматически создает экземпляр шаблона при его использовании, объявление extern должно располагаться перед любым кодом, который использует этот экземпляр:
// Application.cc
// экземпляры этих шаблонов должны быть созданы
// в другом месте программы
extern template class Blob<string>;
extern template int compare(const int&, const int&);
Blob<string> sa1, sa2; // экземпляр создается в другом месте
// экземпляры Blob<int> и его конструктор initializer_list создаются
// в этом файле
Blob<int> a1 = {0,1,2,3,4,5,6,7,8,9};
Blob<int> a2(a1); // экземпляр конструктора копий
// создается в этом файле
int i = compare(a1[0], а2[0]); // экземпляр создается в другом месте
Файл Application.o будет создавать экземпляр класса Blob<int> наряду с его конструктором initializer_list и конструктором копий. Экземпляры функции compare<int> и класса Blob<string> не будут созданы в этом файле. Определения этих шаблонов должны быть в каком-то другом файле программы:
// templateBuild.cc
// файл создания экземпляра должен предоставить обычное определение для
// каждого типа и функции, которые другие файлы объявляют внешними
template int compare(const int&, const int&);
template class Blob<string>; // создает экземпляры всех членов
// шаблона класса
В отличие от объявления, когда компилятор видит определение экземпляра, он создает код. Таким образом, файл templateBuild.o будет содержать определения функции compare() для экземпляра типа int и класса Blob<string>. При построении приложения следует скомпоновать файл templateBuild.o с файлом Application.o.
Для каждого объявления экземпляра где-нибудь в программе должно быть определение явного создания экземпляра.
Определения экземпляров создают экземпляры всех членовОпределение экземпляра для шаблона класса создает экземпляры всех членов этого шаблона, включая встраиваемые функции-члены. Когда компилятор видит определение экземпляра, он не может знать, какие функции-члены использует программа. Следовательно, в отличие от обычного способа создания экземпляра шаблона класса, компилятор создает экземпляры всех членов этого класса. Даже если член класса не будет использоваться, его экземпляр будет создан все равно. Следовательно, явное создание экземпляра можно использовать только для таких типов, которые применимы со всеми членами данного шаблона.
Определение экземпляра используется только для таких типов, которые применимы со всеми функциями-членами шаблона класса.
Упражнения раздела 16.1.5Упражнение 16.25. Объясните значение этих объявлений:
extern template class vector<string>;
template class vector<Sales_data>;
Упражнение 16.26. Предположим, что класс NoDefault не имеет стандартного конструктора. Можно ли явно создать экземпляр vector<NoDefault>? Если нет, то почему?
Упражнение 16.27. Объясните по каждому помеченному оператору, происходит ли создание экземпляра. Если создается экземпляр шаблона, объясните, почему; если нет, то тоже почему.
template <typename Т> class Stack { };
void f1(Stack<char>); // (a)
class Exercise {
Stack<double> &rsd; // (b)
Stack<int> si; // (c)
};
int main() {
Stack<char> *sc; // (d)
f1(*sc); // (e)
int iObj = sizeof(Stack<string>); // (f)
}
16.1.6. Эффективность и гибкость
Библиотечные типы интеллектуальных указателей (см. раздел 12.1) являются хорошим примером грамотно спроектированных шаблонов.
Очевидное различие между указателями shared_ptr и unique_ptr в стратегии, которую они используют для управления содержащимися в них указателями: один класс предоставляет совместную собственность; а другой — единоличною собственность на хранимый указатель. Это различие и является основанием для создания данных классов.