А. Александреску - Современное проектирование на C++ (1119444), страница 35
Текст из файла (страница 35)
Следовательно, объект р1 может стать ссылкой на ссылку на объект класса эт6:: зтг)п9, а это не допускается.' К счастью, решение этой проблемы существует. В главе 2 описан шаблонный класс туретга(тя<т>, определяющий группу типов, связанных с типом т. К ним относятся неконстантные типы (если тип т является константным), указатели (если тип т является указателем) и многие другие. Тип, который можно безопасно и эффективно передавать как параметр функции, называется Рагаветегтуре. В привеленной ниже таблице показана связь между типом, передаваемым классу туретга(тя, и внутренним определением типа Рагаветегтуре. туретга(тя<т>::Рагаветегтуре Тип ц, если ц — элементарный тип; в противном случае — сопят ц й Тип ц, если ц — элементарный тип; в противном случае — — сопят ц б ц б сопят ц й соп5т 0 0 Ь соп5т 0 Й Замена аргументов функции пересылки типами, указанными в правом столбце, всегда корректна, причем она не вызывает никаких дополнительных затрат, связанных с копированием.
// Внутри класса Рцпстог<я, ттлят> я орегатогД ( турепаве туретга1тя<Рагв1>::Рагаветегтуре р1; турепаве туретга(тя<Рагв2>::Рагаветегтуре р2; гетцгп ("эртвр! )(р1, р2); Еше приятнее то, что зти ссылки прекрасно работают в сочетании с подставляемыми функциями. Оптимизатор легче генерирует оптимальный код, поскольку для этого ему достаточно установить ссылки. 145 Глава 5, Обобщенные фунхторы ~ Эта проблема также возникает при работе со стандартными механизмами связывания. Бьяри Страуструп представил Комитету по Стандартизации отчет об этом дефекте.
Ои лреююжил исправить этот недостаток, разрешив ссылаться иа ссылки и обрабатывать результирующие ссььлки как простые. В момент написания книги этот отчет был доступен иа ТУеЬ-странице пттР:: //апцЫ 5. 6Кцц9. 6К/) тс1/5 с22/п921/6осз/св9 аст1че. Втв! ВХОБ. 6.13. Вторая практическая проблема: распределение динамической памяти Оценим затраты, связанные с созданием и копированием функторов. Мы должны реа- лизовать правильную семантику значений, ио зто связано с проблемой распределения ди- намической памяти. Каждый объект класса Гцпстог содержит интеллектуальный указатель на объект, созлаииый с помощью оператора пеш. Для создания копии обьекта класса гцпстог функция-член гцпстогтвр1:: с1опе выполняет глубокое копирование.
Зто особенно неприятно, если размер объектов очень важен. В большинстве случаев класс гцпстог используется с указателями иа функции и парами, состоящими из указате- лей на объекты и указателей на функции-члены. В типичных 32-разрядных системах эти обьекты занимают от 4 до 20 байт соответственно (4 байт для указателя на объект и !6 байт для указателя на функшцо-член'). При использовании связывания размер конкрет- ного функтора увеличивается примерно на величину связываемого аргумента (вследствие выравнивания блоков памяти его размер может еше ненамного вырасти).
В главе 4 описаны эффективные механизмы распределения памяти для небольших объектов. Класс рцпстогтвр1 и его наследники являются превосходными каилидата- ми на применение этих механизмов распределения памяти. Напомним, что один из способов применения механизма распрелеления памяти для небольших объектов за- ключается в создании класса, производного от шаблонного класса бва11аЬзест. Использовать этот класс очень легко. Однако иам нужно добавить в класс гцпстог шаблонный параметр, отображающий потоковую модель в механизме распределения па- мяти.
Зто ие сложно, поскольку бсльшую часть времени используется аргумент, заданный по умолчанию. В приведенном ниже коде изменения выделены полужирным шрифтом. тевр1ате < с1аьа и, с1азз ть, тевр1ате сс1ава т> с1ава тпгеаеппдмобе1 < аеряаьт тнкбдатиб > с1азз вцпстогтвр1: рцЫ з с бва110Ьтест<тЬгеабз пймобе1> рцЫт»: как и раньше ... ); Все это наделяет класс кцпстогтвр1 функциональными возможностями механизма распределения памяти. Аналогично в сам класс кцпстог добавляется третий шаблонный параметр. тевр1ате с с1а55 и, с1аэз Т<, тевр1ате <с1аая т> с)ааа тпгеаснпймобе1 - абпдццт тиябяатиа з Естественно было бы ожидать, что указатель на функцию-член занимает 4 байт, как и указатель нв обычную функцию.
Однако указатели на методы фактически представляют собой размеченные объединения (заяяед цп)оп). Они применяют множественное виртуальное наследование и виртуальные/невиртуальныс функции. Часть И. Компоненты с1азз яипссог ( рг1часе: // передаем параметр тйгеад(пймоде1 классу кипссогсмр1 зсд::аисо рсг<гипссогтмр1<я, тс, тйгеад)пдмоде1> рсмр1 )1 Для того чтобы использовать класс яипссог со стандартной потоковой моделью, третий шаблонный аргумент не нужен. Только если в приложении понадобятся функ- торы, поддерживающие несколько потоковых моделей, нужно явно указать параметр тйгеад)пймоде1. 5.14. Реализация операций 0пбо и Вело с помощью класса Рипс1ог В книге Савва е1 а1 (1995) рекомендуется реализовывать операцию индо с помощью дополнительной функции-члена ипехесисе класса соммапд.
Проблема заключается в том, что эту функцию невозможно выразить в обобщенном виде, поскольку отношение между некоей операцией и ее отменой (ипдо) нельзя предсказать. Решить эту задачу можно, если создать отдельный класс Соммапд для каждой операции, выполняемой в приложении. Однако подход, основанный на применении класса гипссог, ориентирован на использование одного класса, связываемого с разными объектами и вызовами функций-членов. Статья Ала Стивенса (А1 бсечепз) в журнале 1)г. 0оЬЬ|./оипш( (Яечепз, 1998) может оказать нам огромную помощь в изучении обобщенных реализаций операций ипсЬ и тедо. Стивенс создал обобщенную библиотеку операций ипдо/тедо, которую следует тщательно изучить, независимо от того, будете вы применять класс яипссог или нет.
Зто все, что нам следует знать о структурах данных. Основная идея операций щкЬ и гедо заключается в использовании стека отката (индо згаск) и стека повтора операций (тедо маей). Если пользователь выполнил некое действие, например, набрал букву на клавиатуре, в стек отката заталкивается новый функтор. Это означает, что функция-член росимепС:: ХпзегССпаг должна затолкнуть в стек отката действие, обратное по отношению к операции вставки символа (например, функцию-член посимепС::ое1етеСпаг).
Основная нагрузка переносится на функцию-член, которая действительно выполняет операцию отката, а в классе гипссог информация о том, как именно следует выполнять эту операцию, не хранится. При необходимости можно затолкнуть в стек повторения операции функтор, состоящий из класса посимепс и указателя на функцию росимепс::1пзегсспаг, связанных с фактическим символьным типом. Некоторые текстовые редакторы позволяют "повторный набор" (гесур)пд). После того как вы что-то набрали на клавиатуре и выбрали в меню пункт Веди, повторяется блок введенных символов.
Для этого прекрасно полходит связывание, реализованное в классе гипстог, позволяющее хранить вызов функции-члена росимепс: гдпзегссйаг для заданного символа, инкапсулируя такие вызовы в одном функторе. Кроме того, повторить нужно не только символ, набранный последним (это было бы не слишком впечатляющим достижением), но и всю последовательность символов, набранных после выполнения последней опера- 147 Глава 5. Обобшенныв функторы ции, не связанной с набором. Здесь в действие вступает сцепление, реализованное в классе ьцпстог. По мере того как пользователь вводит все новые и новые символы, создаются все новые и новые функторы.
Таким образом, возникает возможность выполнить последовательность нажатий клавиш как одну операцию. Функция-член Посцяепт::1пзегтСпаг, по существу, заталкивает функтор в стек отката. Когда пользователь выбирает пункт меню Ьпдо, этот функтор должен быть выполнен и затолкнут в стек повтора операций. Как видим, связывание аргументов и сцепление позволяют нам работать с функторами единообразно; вид вызова не имеет значения, поскольку он скрыт внутри функтора.
Это значительно облегчает задачу реализации операций отката и повтора операций. 5.15. Резюме Использовать хорошие библиотеки на языке С++ намного легче, чем создавать. С другой стороны, разрабатывать библиотеки очень интересно. Оглядываясь на детали описанных выше реализаций, можно извлечь несколько уроков', касающихся создания обобщенных программ. ° Создавайте гибкие шаблонные типы. Стремитесь на все смотреть с максимально общей точки зрения.
Классы яцпстогнапб1ег и мевгцпнапб1ег приобрели много преимушеств, благодаря тому, что в них используются шаблоны. Указатели на функции предоставляют большую свободу. По сравнению с их функциональными возможностями получающийся код имеет удивительно небольшой размер. Все эти выгоды достигаются благодаря использованию шаблонов и тому, что компилятору предоставлено право самому выводить типы, когда это возможно. ° Обобщенное программирование способствует созданию семантики первого класса (см. примечание в начале главы.
— Прим. ред.). Было бы крайне затруднительно оперировать исключительно указателями на класс ьопстогтвр1. Представьте себе, как в этих условиях реализуются связывание и сцепление. Изощренные технологии предназначены для достижения большей простоты. На основе всех этих шаблонов, наследования, связывания и управления памятью создана простая, легкая в применении и хорошо продуманная библиотека. В двух словах, класс гцпстог задерживает вызов функции, функтора или функции- члена. Он сохраняет вызываемый объект и предоставляет для его вызова оператор Ск Ниже приведено краткое описание этого класса.
5.16. Краткое описание класса Рипс$ог Класс гцпстог является шаблонным и позволяет выражать вызовы функций, имеющих до 15 аргументов. Первым шаблонным параметром является тип возвращаемого значения, вторым — спи~ок типов, содержащий типы параметров функции. Третий шаблонный параметр задает потоковую модель, которая используется механизмом распределения памяти, применяемым в классе гцпстог. Подробная информация о списках классов приведена в главе 3, о потоковой модели — в приложении, о механизме распределения памяти для небольших объектов — в главе 4.
° Объект класса яипстог можно инициализировать функцией, функтором, другим объектом класса яцпсгог или указателем на объект и указателем на метод, как показано в приведенном ниже примере. 148 Часть П. Компоненты чозд гипстзоп(1пт) зтгист 5овегипстог чозд орегатогО (Зпт); зтгист 5овес1азз ( чотд меаЬегеипст1оп(1пс); чо1д ехаар1еО ( // инициализируем класс гипстог функцией еипстог<чо1д, тУРеь15т 1(1пт)> свд1(еипст1оп); // инициализируем класс еипстог функтором 5овегипстог бп; еипстог<чо1д, тУРеь15т 1(1пт)> сад2(тп)~ // инициализируем класс гипстог указателем на объект // и указателем на функцию-член 5оаес1аза вуОЬ)ест; еипстог<чо1д, туРеь15т 1(1пт)> садЗ(аауОЬ)ест, 65оаес1азз::мевЬегеипст1оп); // инициализируем класс еипстог другим обьектом // класса гипстог (копирование) гипстог<чоИ, туРЕЬ15Т 1(1пт)> свд4(свдЗ); ) ° Объект класса гипстог можно инициализировать объектом класса зтд:: аито ртг< еипстогтар1<я, ть1зт».