Программирование >>  Полиморфизм без виртуальных функций в с++ 

1 ... 113 114 115 [ 116 ] 117 118 119 ... 144


слово, помогающее распознать шаблон, так что такого рода ухищрения оказались излишними.

Угловые скобки <. . . > были выбраны вместо фигурных по ряду причин: во-первых, .многим пользователям казалось, что они легче читаются; во-вторых, как уже от.мечалось выше, фигурные скобки и так часто используются в синтаксисе С и С-ы-.

Тем не менее существовала определенная проблема. В предложении

List<List<int>> а;

на первый взгляд, объявляется список списков целых. На самом деле, это синтаксическая ошибка, поскольку лексема (сдвиг вправо или оператор вывода) - не то же самое, что две лексемы >. Разумеется, простой лексический трюк решил бы данную проблему, но я решил не запутывать ни гра.мматику, ни лексический анализатор. Однако с тех пор эта ошибка встречалась так часто, что теперь я испытываю сильнейшее желание ликвидировать проблему.

15.8. Методы композиции

Шаблоны поддерживают несколько безопасных и весьма мощных приемов ко.мпозиции. Напри.мер, это средство можно применять рекурсивно:

template<class Т> class List { /* ... */ };

List<int> li;

List< List<int> > Hi;

List< List< List<int> > > llli;

Если нужны конкретные составные типы, то их легко определить с помощью наследования:

template<class Т> class List2 : public List< List<T> > { }; template<class T> class List3 : public List2< List<T> > { };

List2<int> 1112; List3<int> 11113;

Подобное использование наследования несколько необычно, поскольку не добавляется никаких новых членов. Наследование не сопряжено с дополнительными расходами времени или памяти; это всего лишь метод композиции. Если бы композицию нельзя было осуществить с помощью наследования, то для шаблонов пришлось бы изобрести какие-то особые механизмы композиции, иначе язык оказался бы гораздо беднее.

Переменные данных составных типов можно использовать точно так же, как и соответствующие определенные типы, но не наоборот:

void f() {

Hi = 1112; правильно 1112 = Hi; ошибка

Причина - открытое наследование определяет отношение подтипа.



Для того чтобы разрешить присваивание в обоих направлениях, понадобилось бы расширение языка, вводящее настоящие параметризованные синонимы. Например:

template<class Т> typedef List< List<T> > List4;

void {List< List<T> >& Istl, List4& lst2) {

Istl = lst2; lst2 = Istl;

Технически такое расширение реализовать несложно, но вряд ли стоит вводить еще одно средство для переименования.

Также благодаря наследованию можно частично задавать аргументы шаблонов в определении нового типа:

template<class U, class V> class X ( /* ... */ }; template<class U> class XX : public X<U,int> { };

В обычном случае наследование от шаблона класса позволяет подправить базовый класс с помощью информации, нужной производному классу. Благодаря этому К0.МП03ИЦИЯ становится более гибкой. Например:

template<class Т> class Base { /* ... */ };

class Derived : public Base<Derived> { /* ... */ };

Описанная методика позволяет информации о производном классе попадать в определение базового класса. См. также раздел 14.2.7.

15.8.1. Представление стратегии реализации

Еще одно применение наследования и шаблонов для композиции - .метод передачи объектов, представляющих стратегии реализации. Напри.мер, семантику сравнения для сортировки или средства распределения и освобождения памяти для контейнера можно было бы задать с помощью аргументов шаблона [2пс1]:

Один из возможных способов - использовать шаблон для составления нового клосса из интерфейса для нужного контейнера и класса распределителя памяти, применяя технику размещения, описанную в [2nd, §6.7.2]:

template<class Т, class А> class Controlled container : public Container<T>, private A {

...

void some function() {

...

T* p = new(A::operator new(sizeof(T))) T; .. .

...



Здесь необходимо использовать шаблон, поскольку мы проектируем контейнер. Наследование от класса Container необходимо, чтобы Controlled container можно было использовать в качестве контейнера. Использование аргумента шаблона А позволяет применять различные распределители. Например:

class Shared : public Arena { /* ... */ } ;

class Fast allocator { /* ... */ };

class Persistent : public Arena { /* ... */ } ;

Controlled container<Process descriptor,Shared> ptbl;

Controlled container<Node,Fast allocator> tree;

Controlled container<Personnel record,Persistent> payroll;

Это обычный способ передать нетривиальную информацию о реализации производному классу. Преимущества приема - систематичность и возможность использовать встраивание. Правда, в данном случае нередко возникают довольно длинные имена. Но для них можно ввести синонимы с помощью typedef .

В компонентах Буча [Booch, 1993] такой способ композиции используется повсеместно.

15.8.2. Представление отношений порядка

Рассмотрим задачу сортировки. У нас есть шаблон контейнера, тип элемента и функция, сортирующая элементы в контейнере.

Мы не можем поместить критерий сортировки внутрь контейнера, поскольку обычно хранящиеся в нем элементы не должны зависеть от него. У нас также нет воз.можности поместить этот критерий и внутри типа элементов, поскольку их можно сортировать разными способами.

Итак, критерий сортировки не встраивается ни в контейнер, ни в тип элемента. Вместо этого он передается в виде операции, которую следует выполнить. Если в качестве примера взять строки, состоящие из слов - личных шведских имен, тогда какую схему упорядочения применить для сравнения? Для шведского языка обычно применяются две разные схемы. Но нет сомнения, что нельзя давать сведения о соглашениях, принятых для такой операции, ни общему типу строки, ни общему алгоритму сортировки.

Таким образом, любое общее решение включает выраженный в общих терминах алгоритм сортировки, который можно определить не для конкретного типа, а для конкретного использования данного типа. Давайте, например, обобщим стандартную библиотечную функцию strcmp () для работы со строками любого типа Т.

Сначала определяется шаблон класса с семантикой сравнения объекта типа т по умолчанию:

template<class Т> class CMP { public:

static int eq(T a, T b) { return a==b; } static int lt{T a, T b) { return a<b; }



1 ... 113 114 115 [ 116 ] 117 118 119 ... 144

© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки.
Яндекс.Метрика