'

Лекция 23. Шаблоны (часть 3)

Понравилась презентация – покажи это...





Слайд 0

Лекция 23. Шаблоны (часть 3) Красс Александр Alexander.Krass@gmail.com СПбГУ ИТМО, 2008


Слайд 1

Темы Частичная специализация Неполная специализация Шаблонные члены класса Шаблоны как параметры шаблона Функциональные объекты Traits


Слайд 2

Частичная специализация Нам уже знакома полная специализация: template <typename T> class vector { … }; template<> class vector<bool> { …}; Частичная специализация производится для подтипа: template <typename T> class vector<T*> { }; // специализация вектора для указателей. vector<T*> может проходить по своему содержимому и вызывать delete для каждого элемента. STL такой специализации нет.


Слайд 3

Частичная специализация (2) А для чего еще можно выполнять частичную специализацию?


Слайд 4

Неполная специализация Неполная специализация позволяет нам указать часть параметров шаблона. template <typename Data, typename Key> class Map {…}; Вариант Map для случая, когда ключ задается целым: template <typename Data> class Map<Data, int> {…}; Неполная специализация не нужна для шаблонных функций, т.к. там можно обойтись перегрузкой.


Слайд 5

Шаблонные члены класса Иногда имеет смысл делать шаблонными не весь класс, а отдельные методы. Рассмотрим шаблонный класс Array: template <typename T> class Array { … }; Array это динамический массив в стиле C++. Когда ему нужно перераспределить память, он вызывает оператор new, а когда удалить, оператор delete. Очевидно, что для разных случаев нужны разные стратегии управления памятью. (Пояснить?)


Слайд 6

Шаблонные члены класса (2) Вынесем стратегию управления памятью в отдельный класс: class StdMemoryMgr { public: template <typename T> static T* alloc(int n) { return new T[n]; } template <typename T> static void free(T *p) { delete[] p; } }; Реализуя разные версии этого класса, мы поддерживаем разные стратегии управления памятью. Например, вот так: class NoFreeMemoryMgr { public: template <typename T> static T* alloc(int n) { return new T[n]; } template <typename T> static void free(T *p) {} }; Класс NoFreeMemoryMgr можно использовать, если удаление очень дорого и оно нам не критично,


Слайд 7

Шаблонные члены класса (3) Теперь наш класс Array будет иметь следующий вид: template <typename T, typename Allocator = StdMemoryMgr> class Array { … }; Выделение памяти в классе будет производиться так: T* buf = Allocator::alloc<T>(size); Освобождение памяти так: Allocator::free<T>(buf);


Слайд 8

Шаблоны как параметры шаблона Аргументами шаблона могут быть не только простые типы, но и типы основанные на шаблонах К примеру, рассмотрим структуру данных стек. У него есть следующие операции: Push – помещаем элемент в стек Pop – удаляем элемент из стека Size – количество элементов в стеке Стек может быть реализован или на базе списка или на базе массива. Если мы автора класса Stack, мы не можем за пользователя решать, какую структуру нам использовать. Вынесем структуру в параметр шаблона template <class Type, class Container=Array<Type> > class Stack { void Push(const Type &v) { impl.push_back (v); } void Size() const { impl.size(); } … private: Container impl; }; Использование: Stack<int> si1; // стек на базе структуры данных по умолчанию (массива) Stack<int, List<int> > si2; // стек на базе списке. Обратите внимание на пробел между знаками >.


Слайд 9

Функциональные объекты Рассмотрим функцию find1, которая ищет указанное число в массиве типа int: const int* find1 ( const int* pool, int n, int x ) { const int* p = pool; for ( int i = 0; i<n; i++ ) { if ( *p == x ) return p; // success p++; } return 0; // fail } Пример использования: int A[100]; // заполнить A int* p = find1(A,100,5); // найти 5 в массиве


Слайд 10

Функциональные объекты (2) Обобщим нашу функцию find, добавив возможность поиска элемента по произвольному условию: const int* find2 ( const int* pool, int n, bool (*cond)(int) ) { const int* p = pool; for ( int i = 0; i<n; i++ ) { if ( cond(*p) ) return p; // success p++; } return 0; // fail } Пример использования: int A[100]; bool cond_e5 ( int x ) { return x==5; } int* p = find2(A,100,cond_e5); // найти элемент массива такой, что cond_e5(A[i]) равен true Другой пример такого дизайна функция qsort из стандартной библиотеки: void qsort ( void* base, // first element size_t num, // number of elements size_t width, // element size int (*compare)(const void*, const void*) ); // comparing function


Слайд 11

Функциональные объекты (3) В итоге для реализации обобщенного поведения некоторой функции нам надо передать в нее callback, который и будет выполнять настройку поведения этой функции Этот подход имеет и достоинства и недостатки: Механизм очень гибкий Низкая скорость из-за лишних вызовов функций. (Функции по указателю нельзя встроить) Хорошо бы сохранить гибкость callback и увеличить скорость. Воспользуемся помощью классов и шаблонов.


Слайд 12

Функциональные объекты (4) Тип с определенным оператором вызова функции называется функциональным типом. Функциональные типы делятся на встроенные и определяемые пользователем Указатель на функцию – это встроенный тип Класс с оператором вызова функции – определяемый пользователем


Слайд 13

Функциональные объекты (5) После замены указателей на функциональные объекты и приведения функции к шаблонному виду получим: template < typename T, typename Comparator > T* find3 ( T* pool, int n, Comparator comp ) { T* p = pool; for ( int i = 0; i<n; i++ ) { if ( comp(*p) ) return p; // success p++; } return 0; // fail } Новая версия имеет следующие преимущества: Ищет в массиве любого типа Ищет по любому критерию Она быстрее, чем find2, если оператор вызова функции встраивается.


Слайд 14

Функциональные объекты (6) Теперь мы можем использовать find3 следующим образом: Определим предикат поиска: template< typename T, T N> class less { public: bool operator()(T x) const { return x < N; } }; 2. Используем: int *p = find3(A, 100, less<int, 5>()); В STL есть аналогичная функция std::find и набор аналогичных предикатов на все случаи жизни.


Слайд 15

Traits Предположим, нам надо написать набор математических функций, работающих с разными типами данных: float, double, long double, плюс типы, определеяемые пользователем. При реализации этих функций нам хотелось бы знать о свойствах этих типов: Максимальное и минимальное значение Точность И пр. Реализация этих функций должна быть максимально обобщенной. Следовательно, используем шаблоны:


Слайд 16

Traits (2) Все эти величины для стандартных типов определены в файле float.h: /* Smallest value such that 1.0+xxx_EPSILON != 1.0 */ #define DBL_EPSILON 2.2204460492503131e-016 #define FLT_EPSILON 1.192092896e-07F #define LDBL_EPSILON 1.08420217248550443412e-019L /* max value */ #define DBL_MAX 1.7976931348623158e+308 #define FLT_MAX 3.402823466e+38F #define LDBL_MAX 1.189731495357231765e+4932L Но как организовать доступ к этим величинам из наших функций, в зависимости от типа аргументов? template <typename T> bool IsZero(T val) { return abs(val) < 10 * eps; } // eps – своя для каждого типа. Решение заключается в использовании Traits-ов


Слайд 17

Traits (3) Определим обобщенный trait для всех типов: template <typename T> class float_attrs { /* Пусто. Мы ничего не можем сказать про неизвестный тип. */}; template < > class float_attrs<double> // для double { public: typedef double float_type; static inline float_type epsilon() { return DBL_EPSILON; } . . . }; 2. Теперь определим специализацию этого класса для различных типов: template < > struct float_attrs<float> // для float { public: typedef float float_type; static inline float_type epsilon() { return FLT_EPSILON; } . . . };


Слайд 18

Traits (4) Функция IsZero: template <typename T> bool IsZero(T val) { return abs(val) < 10 * float_attrs<T>::epsilon(); } Использование: IsZero(1.f); // используем float_attrs<float> float_attrs это и есть трейтс, хранящий атрибуты чисел с плавающей запятой. Другое применение traits-ов это реализация стратегий, изменяющих поведение объекта.


Слайд 19

20 Спасибо за внимание Вопросы?


×

HTML:





Ссылка: