Язык программирования C++. Пятое издание - Стенли Липпман
Шрифт:
Интервал:
Закладка:
Подобно статическим членам обычного класса, к статическому члену шаблона класса можно обратиться через объект класса или непосредственно, при помощи оператора области видимости. Конечно, чтобы использовать статический член через класс, следует обратиться к его конкретному экземпляру:
Foo<int> fi; // создает экземпляр класса Foo<int>
// и статической переменной-члена ctr
auto ct = Foo<int>::count(); // создает экземпляр Foo<int>::count()
ct = fi.count(); // использует Foo<int>::count()
ct = Foo::count(); // ошибка: экземпляр какого именно
// шаблона создается?
Как и любая другая функция-член, экземпляр статической функции-члена создается только при его использовании в программе.
Упражнения раздела 16.1.2Упражнение 16.9. Что такое шаблон функции? Что такое шаблон класса?
Упражнение 16.10. Что происходит при создании экземпляра шаблона класса?
Упражнение 16.11. Следующее определение шаблона List неправильно. Как его исправить?
template <typename elemType> class ListItem;
template <typename elemType> class List {
public:
List<elemType>();
List<elemType>(const List<elemType> &);
List<elemType>& operator=(const List<elemType> &);
~List();
void insert(ListItem *ptr, elemType value);
private:
ListItem *front, *end;
};
Упражнение 16.12. Напишите собственные версии шаблонов Blob и BlobPtr, включая все константные члены, которые не были представлены в тексте.
Упражнение 16.13. Объясните, какой вид дружественных отношений вы выбрали бы для операторов равенства и сравнения шаблона BlobPtr.
Упражнение 16.14. Напишите шаблон класса Screen, который использует параметры значения для определения высоты и ширины экрана.
Упражнение 16.15. Реализуйте операторы ввода и вывода для своего шаблона Screen. Какие друзья необходимы классу Screen (если таковые вообще имеются) для работы операторов ввода и вывода? Объясните, зачем нужно каждое объявление дружественным (если таковые вообще имеются).
Упражнение 16.16. Перепишите класс StrVec (см. раздел 13.5), как шаблон Vec.
16.1.3. Параметры шаблона
Подобно именам параметров функций, имена параметров шаблона не имеют никакого значения. Обычно параметрам типа присваивают имя Т, но можно использовать любое другое:
template <typename Foo> Foo calc(const Foo& a, const Foo& b) {
Foo tmp = a; // тип tmp совпадает с типом параметров и возвращаемого
// значения
// ...
return tmp; // типы возвращаемого значения и параметров совпадают
}
Параметры шаблона и область видимостиПараметры шаблона следуют обычным правилам области видимости. Имя параметра шаблона применимо сразу после его объявления и до конца объявления или определения шаблона. Подобно любым другим именам, параметр шаблона скрывает любые объявления имен во внешней области видимости. Однако, в отличие от большинства других контекстов, имя, используемое как параметр шаблона, не может быть повторно использовано в пределах шаблона:
typedef double А;
template <typename A, typename В> void f(А а, В b) {
A tmp = а; // tmp имеет тип параметра шаблона А, а не double
double В; // ошибка: повторное объявление параметра шаблона В
}
Согласно обычным правилам сокрытия имен, определение typedef типа А скрывается определением параметра типа по имени А. Таким образом, переменная tmp не будет иметь тип double; она будет иметь любой тип, который будет передан параметру шаблона А при использовании шаблона. Поскольку нельзя многократно использовать имена параметров шаблона, объявление переменной по имени B ошибочно.
Поскольку имя параметра не может быть использовано многократно, в каждом списке параметров шаблона имя параметра шаблона может присутствовать только однажды:
// ошибка: повторение имени V в параметрах шаблона недопустимо
template <typename V, typename V> // ...
Объявления шаблонаОбъявление шаблона должно включить параметры шаблона:
// объявляет, но не определяет compare и Blob
template <typename Т> int compare(const T&, const T&);
template <typename T> class Blob;
Подобно параметрам функций, имена параметров шаблона не должны совпадать с таковыми в объявлениях и определениях того же шаблона:
// все три случая использования calc
// относятся к тому же шаблону функции
template <typename Т> Т calc(const Т&, const Т&); // объявление
template <typename U> U calc(const U&, const U&); // объявление
// определение шаблона
template <typename Type>
Type calc(const Type& a, const Type& b) { /* ... */ }
Конечно, у каждого объявления и определения шаблона должно быть то же количество и вид (т.е. тип или значение) параметров.
По причинам, рассматриваемым в разделе 16.3, объявления всех шаблонов, необходимых данному файлу, обычно располагаются вместе в начале файла перед любым использующим их кодом.
Использование членов типаПомните, как в разделах 7.4 и 7.6 использовался оператор области видимости (::) для обращения к статическим членам и членам типа. В обычном коде (не шаблона) у компилятора есть доступ к определению класса. В результате он знает, является ли имя, к которому обращаются через оператор области видимости, типом или статическим членом. Например, в коде string::size_type, компилятор имеет определение класса string и может узнать, что size_type — это тип.
С учетом того, что Т является параметром типа шаблона, когда компилятор встретит такой код, как T::mem, он не будет знать до времени создания экземпляра, является ли mem типом или статической переменной-членом. Но чтобы обработать шаблон, компилятор должен знать, представляет ли имя тип. Например, если T является именем параметра типа, то как компилятор воспримет следующий код:
T::size_type * p;
Он должен знать, определяется ли переменная по имени p или происходит умножение статической переменной-члена по имени size_type на переменную по имени p.
По умолчанию язык подразумевает, что имя, к которому обращаются через оператор области видимости, не является типом. В результате, если необходимо использовать тип-член параметра типа шаблона, следует явно указать компилятору, что имя является типом. Для этого используется ключевое слово typename:
template <typename Т>
typename Т::value_type top(const T& с) {
if (!c.empty())
return c.back();
else
return typename T::value_type();
}
Функция top() ожидает контейнер в качестве аргумента, она использует ключевое слово typename для определения своего типа возвращаемого значения и создает инициализированный по умолчанию элемент (см. раздел 7.5.3), чтобы возвратить его, если у контейнера с нет никаких элементов.
Когда необходимо уведомить компилятор о том, что имя представляет тип, следует использовать ключевое слово typename, а не class.
Аргументы шаблона по умолчаниюАналогично тому, как можно предоставить аргументы по умолчанию для параметров функции (см. раздел 6.5.1), можно предоставить аргументы шаблона по умолчанию (default template argument). По новому стандарту можно предоставлять аргументы по умолчанию и для шаблонов функций, и для шаблонов классов. Прежние версии языка допускали аргументы по умолчанию только для шаблонов класса.
В качестве примера перепишем функцию сравнения, использующую по умолчанию библиотечный шаблонный объект функции less (см. раздел 14.8.2):
// compare() имеет аргумент шаблона по умолчанию, less<T>
// и заданный по умолчанию аргумент функции, F()
template <typename Т, typename F = less<T>>
int compare(const T &v1, const T &v2, F f = F()) {
if (f(v1, v2)) return -1;
if (f(v2, v1)) return 1;
return 0;
}
Здесь в шаблон добавлен второй параметр типа, F, представляющий тип вызываемого объекта (см. раздел 10.3.2), и определен новый параметр функции, f, который будет связан с вызываемым объектом.