Язык программирования C++. Пятое издание - Стенли Липпман
Шрифт:
Интервал:
Закладка:
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, который будет связан с вызываемым объектом.
Предоставлено также значение по умолчанию для этого параметра шаблона и соответствующего ему параметра функции. Аргумент шаблона по умолчанию определяет, что функция compare() будет использовать библиотечный класс less объекта функции, экземпляр которого создается с тем же параметром типа, что и функция compare(). Заданный по умолчанию аргумент функции указывает, что параметр f будет инициализирован по умолчанию объектом типа F.
Когда пользователи вызывают эту версию функции compare(), они могут предоставить собственный оператор сравнения, но не обязаны делать это:
bool i = compare(0, 42); // использует less; i равно -1
// результат зависит от isbn в item1 и item2
Sales_data item1(cin), item2(cin);
bool j = compare(item1, item2, compareIsbn);
Первый вызов использует заданный по умолчанию аргумент функции, которым является объект типа less<T>. В этом вызове Т имеет тип int, поэтому у объекта будет тип less<int>. Этот экземпляр функции compare() будет использовать для сравнения тип less<int>.
Во втором вызове передается функция compareIsbn() (см. раздел 11.2.2) и два объекта типа Sales_data. Когда функция compare() вызывается с тремя аргументами, типом третьего аргумента должен быть вызываемый объект, возвращающий тип, приводимый к типу bool и получающий аргументы типа, совместимого с типами первых двух аргументов. Как обычно, типы параметров шаблона выводятся из соответствующих им аргументов функции. В этом вызове тип T выводится как тип Sales_data, а тип F — как тип compareIsbn().
Как и с аргументами функций по умолчанию, у параметра шаблона может быть аргумент по умолчанию, только если у всех параметров справа от него также есть аргументы по умолчанию.
Аргументы по умолчанию шаблона и шаблоны классаВсякий раз, когда используется шаблон класса, за именем шаблона всегда должны следовать угловые скобки. Скобки означают, что класс будет создан как экземпляр шаблона. В частности, если шаблон класса предоставляет аргументы по умолчанию для всех своих параметров и следует использовать именно их, то после имени шаблона следует поместить пустую пару угловых скобок:
template <class Т = int> class Numbers { // по умолчанию Т - это int
public:
Numbers(Т v = 0): val(v) { } // различные операции с числами
private:
Т val;
};
Numbers<long double> lots_of_precision;
Numbers<> average_precision; // пустые <> означают тип по умолчанию
Здесь создаются два экземпляра шаблона Numbers: версия average_ precision — экземпляр Numbers с заменой параметра Т типом int; версия lots_of_precision — экземпляр Numbers с заменой параметра Т типом long double.
Упражнения раздела 16.1.3Упражнение 16.17. Каковы (если есть) различия между параметром типа, объявленным с ключевым словом typename и ключевым словом class? Когда должно использоваться ключевое слово typename?
Упражнение 16.18. Объясните каждое из следующих объявлений шаблона функции и укажите, допустимы ли они. Исправьте все найденные ошибки.
(a) template <typename Т, U, typename V> void f1(T, U, V);
(b) template <typename T> T f2(int &T);
(c) inline template <typename T> T foo(T, unsigned int*);
(d) template <typename T> f4(T, T);
(e) typedef char Ctype;
template <typename Ctype> Ctype f5(Ctype a);
Упражнение 16.19. Напишите функцию, получающую ссылку на контейнер и выводящую его элементы. Используйте переменную size_type и функцию-член size() контейнера для контроля цикла, вывода элементов.
Упражнение 16.20. Перепишите функцию из предыдущего упражнения так, чтобы использовать для контроля цикла итераторы, возвращаемые функциями begin() и end().
16.1.4. Шаблоны-члены
У класса (обычного или шаблона класса) может быть функция-член, которая сама является шаблоном. Такие члены называются шаблонами-членами (member template). Шаблоны-члены не могут быть виртуальными.
Шаблоны-члены обычных (не шаблонных) классовВ качестве примера обычного класса, у которого есть шаблон-член, определим класс, подобный стандартному типу функции удаления (deleter), используемой указателем unique_ptr (см. раздел 12.1.5). Как и у стандартной функции удаления, у данного класса будет перегруженный оператор вызова функции (см. раздел 14.8), который, получив указатель, выполняет для него оператор delete. В отличие от стандартной функции удаления, новый класс будет также выводить сообщения при каждом запуске. Поскольку создаваемую функцию удаления предстоит использовать с любым типом, сделаем оператор вызова шаблоном:
// класс объекта функции, вызывающий оператор delete для указателя
class DebugDelete {
public:
DebugDelete(std::ostream &s = std::cerr): os(s) { }
// подобно любым шаблонам функции, тип Т выводится компилятором
template <typename Т> void operator()(Т *p) const
{ os << "deleting unique_ptr" << std::endl; delete p; }
private:
std::ostream &os;
};
Как и любой другой шаблон, шаблон-член начинается с собственного списка параметров шаблона. У каждого объекта класса DebugDelete есть переменная-член типа ostream для вывода и функция-член, которая сама является шаблоном. Этот класс можно использовать вместо оператора delete:
double* p = new double;
DebugDelete d; // объект, способный действовать как оператор delete
d(p); // вызывает DebugDelete::operator()(double*), удаляющий p
int* ip = new int;
// вызывает operator()(int*) для временного объекта DebugDelete
DebugDelete()(ip);
Поскольку вызов объекта DebugDelete удаляет переданный ему указатель, его можно также использовать как функцию удаления для указателя unique_ptr. Чтобы переопределить функцию удаления указателя unique_ptr, укажем тип функции удаления в скобках и предоставим объект типа функции удаления конструктору (см. раздел 12.1.5):
// удалить объект, на который указывает p
// создает экземпляр DebugDelete::operator()<int>(int *)
unique_ptr<int, DebugDelete> p(new int, DebugDelete());
// удаляет объект, на который указывает sp
// создает экземпляр DebugDelete::operator()<string>(string*)
unique_ptr<string, DebugDelete> sp(new string, DebugDelete());
Здесь указано, что у функции удаления p будет тип DebugDelete и что предоставлен безымянный объект этого типа в конструкторе p().
Деструктор класса unique_ptr вызывает оператор вызова типа DebugDelete. Таким образом, при каждом вызове деструктора класса unique_ptr создается также экземпляр оператора вызова класса DebugDelete. Таким образом, определения выше создадут следующие экземпляры:
// примеры создания экземпляров шаблонов-членов DebugDelete
void DebugDelete::operator()(int *p) const { delete p; }
void DebugDelete::operator()(string *p) const { delete p; }
Шаблоны-члены шаблонов классаШаблон-член можно также определить и для шаблона класса. В данном случае у и класса, и у его члена будут собственные, независимые параметры шаблона.
В качестве примера снабдим класс Blob конструктором, который получает два итератора, обозначающих диапазон копируемых элементов. Поскольку желательно обеспечить поддержку итераторов в различных видах последовательностей, сделаем этот конструктор шаблоном:
template <typename Т> class Blob {
template <typename It> Blob(It b, It e);
// ...
};
У этого конструктора есть свой собственный параметр типа шаблона, It, который он использует для типа двух параметров функции.
В отличие от обычных функций-членов шаблонов класса, шаблоны-члены являются шаблонами функций. При определении шаблона-члена вне тела шаблона класса следует предоставить список параметров шаблона для шаблона класса и для шаблона функции. Список параметров для шаблона класса располагается сначала, затем следует список параметров шаблона-члена:
template <typename Т> // параметр типа для класса
template <typename It> // параметр типа для конструктора