|
Программирование >> Разработка устойчивых систем
Полная специализация для void* tempiate<> class Stack<void *> { void** data: std::size t count; std::size t capacity: enum {INIT = 5}; public: Stack О { count = 0: capacity = INIT; data = new void*[INIT]: void push(void* const & t) { if (count == capacity) { std::size t newCapacity = 2*capacity: void** newData = new void*[newCapacity]: std::memcpy(newData, data. count*sizeof(void*)); delete [] data; data = newData: capacity = newCapacity; assert(count < capacity): data[count++] = t; void popO { assert(count > 0): --count; void* topO const { assert(count > 0): return data[count-l]; std::size t sizeO const {return count;} Неполная специализация для других указателей tempiate<class Т> class Stack<T*> : private Stack<void *> { typedef Stack<void *> Base: public: void push(T* const & t) {Base::push(t):} void popO {Base: ;pop();} T* topO const {return static cast<T*>(Base::top());} std::size t sizeO {return Base::size();} #endif NOBLOATJ /:- Этот простой стек автоматически расширяется по мере заполнения. Специализация для void* является полной, на что указывает префикс templateo (с пустым списком параметров шаблона). Как уже упоминалось, в специализации шаблона класса должны быть реализованы все функции класса. Экономия достигается для всех остальных указателей. Неполная специализация для прочих типов указателей создается закрытым наследованием от Stacl void*>, поскольку мы всего лишь задействуем Stacl void*> в реализации и не желаем предоставлять пользователю прямой доступ к его интерфейсу. Функции класса в каждой специализации ограничиваются простым перенаправлением вызова к соответствующим функциям Разрешение имен Встречая в программе идентификатор, компилятор должен сначала определить тип и область видимости (а в случае переменных - срок жизни) для сущности, пред- Stacl void*>. Таким образом, при каждой специализации шаблона для типа указателя, отличного от void*, размер сгенерированного кода составит лишь ничтожную часть того размера, который бы потребовался при использовании основного шаблона. Ниже приводится тестовая программа: : C05:NobloatTest.cpp #inc1ude <1ostream> linclude <stnng> linclude Nobloat.h using namespace std; tempiate<c1ass StackType> void emptyTheStack(StackType& stk) { while (stk.SizeO > 0) { cout stk.topO endl; stk.popO; Перегрузка для emptyTheStack (не специализация!) tempiate<class T> void emptyTheStack(Stack<T*>& stk) { while (stk.SizeO > 0) { cout *stk.top() endl; Stk.popO: int mainO { Stack<int> si: sl.push(l): sl.push(2): emptyTheStack(si): Stack<int *> s2: int i - 3: int j - 4: s2.push(&i): s2.push(&j): emptyTheStack(s2): } III- Для удобства в программу включены два шаблона функции emptyTheStack. Поскольку шаблоны функций не поддерживают неполной специализации, мы предоставляем перегруженные шаблоны. Вторая версия emptyTheStack более специализирована по сравнению с первой, поэтому она выбирается при каждом использовании типов указателей. В программе создаются три специализации шаблона класса: Stack<int>, Stack<void*> и Stack<int*>. Специализация Stack<void*> создается косвенно, поскольку Stack<int*> является производной от нее. Программа, в которой задействованы специализации для разных типов указателей, обеспечивает существенную экономию по сравнению с простым использованием одного шаблона Stack. Так называемое правило Кёнига, по имени Эндрю Кёнига (Andrew Koenig), который первым предложил эту методику комитету по стандартизации С++. ADL применяется как при наличии шаблонов, так и без них. ставляемой данным идентификатором. Использование шаблонов усложняет ситуацию. Когда компилятор впервые встречает шаблон, он еще не располагает полной информацией о нем, а значит, не может определить, правильно ли он используется. По этой причине компиляция шаблонов выполняется в два этапа. Имена в шаблонах в первой фазе компилятор разбирает определение шаблона, ищет очевидные синтаксические ошибки и разрешает все имена, которые может разрешить на этой стадии. К этой категории относятся имена, не зависящие от параметров шаблона и разрешаемые стандартным способом или, при необходимости, с помощью аргументов (см. далее). Имена, которые компилятор разрешить не может, называются зависимыми именами, поскольку они так или иначе зависят от параметров шаблона. Разрешение этих имен невозможно до специализации шаблона с фактически переданными аргументами. Следовательно, вторая фаза компиляции шаблона наступает при специализации. Здесь компилятор определяет, нужно ли использовать переопределенную специализацию вместо основного шаблона. Но прежде чем переходить к примерам, нужно разобраться еще с двумя терминами. Уточненными именами называются имена, построенные по следующим схемам: класс::имя; объект.имя; указатель на объект->имя. Примеры уточненных имен: MyClass::f(): x.fO: p->f(): Уточненные имена неоднократно встречались на страницах книги, причем в последний раз при описании ключевого слова typename. Они называются уточненными, потому что целевое имя (как f) явно связывается с конкретным классом или пространством имен. По этой информации компилятор узнает, где искать объявления этих имен. Другой важный термин - поиск с учетом аргументов (Argument-Dependent Lookup, ADL) Этот механизм изначально разрабатывался для упрощения вызова внешних функций (в том числе операторов), объявленных в пространствах имен. Рассмотрим пример: linclude <iostream> linclude <string> ... std: :string sChello ): std::cout s std::endl: Как обычно в заголовочных файлах, в этом фрагменте отсутствует директива using namespace std. Без этой директивы все имена в пространстве имен std должны
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |