А. Александреску - Современное проектирование на C++ (1119444), страница 31
Текст из файла (страница 31)
Это значит, что приведенный ниже фрагмент программы является неправильным. // еунктор без аргументов сеюр1асе <сурепаюе аезц1стуре> с1аьа еипссог // еунктор с одним аргументом сеюр1асе <сурепаюе яезо1стуре, сурепаюе яагю1> с1а55 Ропстог ( Называть шаблонные классы именами ямпстог1, ямпстог2 и так далее может оказаться затруднительно. В главе 3 описаны списки типов, позволяющие работать с коллекциями типов. Типы параметров функтора также образуют коллекцию, поэтому списки типов подойдут нам идеально.
Определение шаблонного класса гцпссог с помощью списков типов приведено ниже. // еунктор, имеющий любое количество аргументов любого типа сеюр1асе <сурепаюе кези1снаюе, с1аьз тс(зт> с1аьа гипстог Возможная конкретизация этого класса выглядит следующим образом. // определяем функтор, получающий параметры типа 1пс и г(оиЫе // и возвращающий значение типа домЫе гцпстог<доиЫе, туяаьгзт 2((пт, доиЫе)> юугцпстог; Одним из достоинств этого подхода является возможность повторно использовать сущности, определенные списками типов, а не разрабатывать новые. 128 Часть й. Компоненты Как мы вскоре убедимся, списки типов хотя и полезны, но не решают всех проблем. Мы по-прежнему вынуждены создавать отдельную реализацию функтора для каждого конкретного количества аргументов, В дальнейшем для простоты ограничимся двумя аргументами.
Масштабировать программу для конкретного количества параметров (не больше 15) можно с помощью включения заголовочного файла ьипссог.и. Полиморфный класс Гипстогтв1, погруженный в класс гцпссог, имеет те же самые шаблонные параметры.з севр1асе <сурепаве к, с1азз тьззс> с1аьа Гипстогтвр1; Класс гипссогсвр! определяет полиморфный интерфейс, абстрагирующий вызов функции. Для каждого конкретного количества параметров определена явная специализация класса кипссогхвр1 (глава 2). В каждой специализации определена чисто виртуальная функция 0 для определенного количества и типов параметров. севр1асе <сурепаве я> с1ааа ьцпссогхвр1<я, ни11туре> ( риЫзс: ч1гсиа1 н орегасогОО = О; чбгсиа1 ьипссогхвр1* с1опеО сопят = О; ч(гсца1 -ьцпссогхвр! О )' севр!асе <сурепаве к, сурепаве РХ> с1азз гиоссогхвр1<я„ тчрбьхбт 1(р1)> ( риЫтс: чт гсиа1 я орегасогО (я1) = О; ч(гсиа1 ьипссогхвр1* с1опеО сопзс = О; ч(гсиа1 -ьипссогхвр1 О О севр1асе <сурепаве и, сурепаве р1, сурепаве р2> с1аьа Гипстогтвр1<а, тчрбЬХбт 2(р1, р2)> ( риб!зс: чтгсиа1 и орегасогО(я1, я2) = О; чтгсиа1 ьипссогтвр1* с1опеО сопзс = О„ чб гсца1 еипссогхвр! 0 () Классы ьипссогхвр! прелставляют собой частичные специализации исходного шаблонного класса кцпссогсвр1.
Их свойства подробно описаны в главе 2. В нашем случае частичная шаблонная специализация позволяет определять разные версии класса гипссогхвр! в зависимости от количества элементов в списке типов. кроме оператора 0 в классе гцпссогхвр1 определены две вспомогательные функции-члены — С1опе и виртуальный деструктор. Функция с1опе предназначена для создания полиморфной копии объекта класса гцпссогхвр1. (Детали полиморфного Использование ключевых слов Сурепаве илн с1азз для определения шаблонных параметров прнаолит к эквивалентным результатам. В книге по умолчанию при определении шаблонных параметроа, которые могутотноситься к элементарным типам (например 1пС), принято использовать ключевое слово сурепаве, а ключевое слово с1азз применяется лля шаблонных параметров, тип которых определяется пользователем.
129 Глава 5. Обобщенные функторы клонирования изложены в главе 8.) Виртуальный деструктор позволяет уничтожать объекты классов, производных от класса еипссогсвр1, применяя оператор де1есе к указателю на объект класса еипссогсер1. Важность этого деструктора подробно обсуждалась в главе 4. Классическая реализация класса еипссог приведена ниже. севр1асе <сурепаве й, с1авз тьзвс> с1азз еипссог ( риб)(с: еипссогО; еипссог(сопзс еипссогв)~ енистой орегасог=(сопзс еипссогй); ехр!зсзс еипссог(зсг)::аисо рсг<свр1> врсвр1); ргзмасе: О Определение тела функтора сурег(ет еипссогсвр1<я, тьзвс> свр1; всс)::аисо рсг<свр1> врсер1 ); В классе еипссог интеллектуальный указатель на класс еипссогсвр1<я, тсзвс>, представляющий собой соответствующий тип тела функтора, хранится в виде закрытого члена.
Для этого выбран стандартный интеллектуальный указатель зсг):: аисо рс г. Приведенный выше код иллюстрирует наличие у класса Еипстог определенных артефактов, демонстрирующих его семантику значений. К этим артефактам относятся конструктор по умолчанию, конструктор копирования и оператор присваивания. Явный деструктор не нужен, поскольку интеллектуальный указатель аисо рсг автоматически освобождает все ресурсы.
Кроме того, в классе еипссог определен "конструктор расширения", предоставляющий классу еипссогсер1 доступ к указателю аисо рсг. Конструктор расширения позволяет определять классы, производные от класса еипссогсвр1, и непосредственно инициализировать класс еипссог указателями на них. Почему аргументом конструктора расширения является итератор аисо рсг, а не обычный указатель? Создание объекта с помощью указателя ацсо ргс явно свидетельствует о том, что объект класса еипссогсвр1 принадлежит классу еипссог. Вызывая этот конструктор, пользователь класса еипссог должен явно указать тип аисо рсг. Если он сделал это, значит, понимает, о чем идет речь.4 4 Разумеется, это вовсе не обязательно.
Однако это все же лучше, чем молча выбирать один вариант (копирование или владение). Хорошие библиотеки на языке С++ отличаются одной интересной особенностью: когда может возникнуть неопрелсленность, они позволяют пользователю устранять сс с помощью явного кода. С другой стороны, существуют библиотеки, неправильно использующие свойства языка С++, опрсделенныс по умолчанию (особенно преобразования типов и владение указателями). Они предоставляют пользователям меньше возможностей для дополнительного программирования, но в качестве компенсации принимают сомнитсльныс прслположения и решения, облегчая работу пользователя.
130 Часть И. Компоненты 5.5. Реализация оператора пересылки Рнпс1ог::орега1огЦ В классе Ропссог необходим оператор О, который пересылал бы вызов оператору Ропссогсар1::орегасогО. Для этого можно было бы воспользоваться подходом, который мы применяли при разработке самого класса Ропссогсвр1, и создать труппу частичных шаблонных специализаций, каждая из которых соответствует конкретному количеству параметров. Однако этот подход здесь не годится. В классе РопсСог содержится довольно большое количество кола, и было бы неразумной тратой времени и памяти размножать его только для того, чтобы реализовать оператор О.
Однако сначала попробуем определить типы параметров. Тут нам на помощь прил т списки типов. севр1асе <сурепаае я, с1аьв ть1вс> с1аав Ропссог суредет тН зс Рагвс1зс; суреоет сурепаае турелсноп5сг1сс<тсззс, О, еврсутуре>:: яеьо1с Рага1; суредеГ сурепаве туреАСНопвтгзсС<тсзЗС, 1, ЕврСутуре>:." яеао1с Рагв2; как и раньше ); Класс туреАСНоп5сг1сс является шаблоном, облалаюшим доступом к типу по его позиции в списке типов. Если тип не найден, в качестве третьего шаблонного аргумента класса туреАсноп5сг)сс задается результаг (т.е. внутренний класс Туредсноп5сгзсс<...>::яезо1с).
В качестве третьего аргумента выбран класс еарсутуре, являющийся, как следует из его названия, пустым. (Подробное описание класса Турелтноп5тгзсС можно найти в главе 3, а описание класса еарсутуре — - в главе 2.) Итак, тип Рагва будет либо м-м элементом списка типов, либо типом еарсутуре, если количество элементов списка типов меньше, чем Ф.
Чтобы реализовать оператор О, воспользуемся интересным трюком. Определим все версии оператора Π— для любого количества параметров — внутри определения класса Ропссог. севр1асе <сурепаае я, с1ааа тс)ас> с1ааа Ропссог ( как и раньше роЫзс." я орегасогОО гесо гп ( зртвр1-) О ' я орегасогО(Рага1 р1) гесогп (*врсар1 )(Р1): я орегасогО(Рага1 р1, Рагв2 р2) ( гесогп (*ар1ар1 )(Р1 Р2)' Глава б. Обобщенные функторы В чем состоит трюк? Для данной конкретизации класса Рипссог правильной явля- ется только одна версия оператора О. Все остальные версии на этапе компиляции порождают сообшения об ошибках.