Бьерн Страуструп. Язык программирования С++. Специальное издание (2011) (1004033), страница 198
Текст из файла (страница 198)
Мы здесь обсуждаем разрешение конфликтов имен лишь потому, что рассмотреннь)й прием с введением интерфейсных классов, имеющих функции с переадресацией вызовов (Гопнаг())пй Гипс()опз), сам по себе интересен и имеет множество иных применений. Его можно применять не только для изменения имен функций, но и для изменения типов их аргументов и возврата, для введения проверок на стадии выполнения и др.
Поскольку функции ССо((Ьоу( ((!ги(г() и Иг)г(п(!о(г(:(!га(гО переадресуют свой вызов виртуальным функциям, их нельзя оптимизировать встраиванием. Но если компилятор распознает их в качестве переадресующих функций, он сможет оптимизировать код, убрав их из цепочки вызовов. 25.6. Интерфейсные классы 913 1ог((не 1 = 1) 1<=10) (чь) ( в[1] = 7; Г.. ) ) гетр1аге<1пг 1оп, 1пг Ыяй> сгавв Йапее ( (пг га1; риййс: с1авв Еггог ( ); ~У класс исключений Канде(гпг 1) (Аввеге<Еггог> ((оп<=(ьЫ<ЫКЬ); га1 = 1; ) 77см.,024372 Йапйе орегагог= (Ыг 1) ( ге<ига *гйгв=Канде (1); ) орегагог 1пг ( ) ( гесагп га1; ) Е... )' гоЫ3'(Канде<2, 17>); гоЫ К (Канве<-10, 10> ); гоЫ й ((пг х) ( Капов<0, 2001> 1 = х; 77может сгенерировать Копке::Еггог (пг(1 =1) 1'(3) ' 2(17); е(-7); К(100); Е генерируется Копие::Еггог д генерируется Капле.':Еггог Шаблон Йапйе легко расширяется для работы с диапазонами произвольных скалярных типов (525.
[0[7[). Часто интерфейсный класс, контролирующий доступ к другому классу или выполняющий подгонку его интерфейса, называют оберткой (итаррег). Это не приводит к дополнительным по сравнению с предыдущим примером затратам. В то же время, вариант с шестое намного нагляднее, его легче и читать, и писать, так что он менее подвержен ошибкам. Интерфейсные классы обычно невелики и (по определению) мало что делают. Однако они становятся необходимы, когда нужно объединить программные компоненты, написанные согласно разным традициям.
Например, интерфейсные классы часто используются дяя предоставления С++-интерфейса к коду, написанному на других языках программирования, а также для изоляции пользовательского кода от деталей библиотеки (оставляя, тем самым, возможность замены библиотеки). Другое важное применение интерфейсных классов — предоставление проверяемых или ограничительных интерфейсов. Например, нередко требуется обеспечить ограничение целых значений заданным диапазоном.
Это можно гарантировать (во время выполнения программы) с помощью простого шаблона: 914 Глава 25. Роли классов 25.7. Дескрипторные классы (ЬапсПеб) Абстрактный класс помогает эффективно разделить интерфейс и его реализации. Однако если следовать 925.3, то получается неразрывная связь между интерфейсом, предоставляемым абстрактным типом, и его реализацией, обеспечиваемой конкретным типом.
Например, невозможно отвязать абстрактный итератор от одного источника (скажем, множества) и привязать его к другому (например, потоку) по мере исчерпания первого источника. Кроме того, если манипулировать объектами, реализующими абстрактный класс, не через указатели или ссылки, то теряются выгоды, предоставляемые виртуальными функциями. Пользовательский код становится зависимым от деталей класса реализации, ибо невозможно выделить память под эти объекты статически или в стеке, не зная точного размера объектов. Использование же указателей или ссылок означает, что бремя управления памятью ложится на пользователя. Распространенный способ решения этих проблем состоит в разделении одного объекта на два разных: дескрилп)ора (Ьалс((е), обеспечивающего пользовательский интерфейс, и предел) авлеиия (гергевеп !а!!оп), содержащего все или большую часть состояния объекта.
Соединяет их указательное поле в дескрипторе. В типичном случае класс дескриптора содержит чуть больше данных, чем один лишь указатель на представление, но не намного. Это означает, что расположение в памяти объектов дескрипторного класса остается неизменным, даже если представление изменяется, и что дескрипторные объекты достаточно малы, чтобы их можно было безболезненно передавать в функции по значению, так что отпадает нужда в указателях или ссылках. Кергезеп(а(1оп Напд!е Дескрипторы иллюстрируются классом Юптпй из 911.12; дескрипторный класс обеспечивает интерфейс, контроль за доступом и управления памятью для представления.
В данном случае и дескриптор, и представление задаются конкретными классами, но часто представлению соответствуют абстрактные классы. Рассмотрим абстрактный тип Яв! (множество) из 925.3. Как можно ввести для него дескриптор и какие выгоды это даст (и какой ценой)? Отталкиваясь от класса Яег, можно определить дескрипторный класс, просто перегрузив операцию ->: (егор!а!в<с!авв Т> с!ага Бе! Ьалйе ( Яег<Т>* гор; риЫ!с: Яег<Т>* орегагог->() (ге!игл серг) Яе! ватна(Хе!<у>* рр): гер(рр) () )' Это не меняет существенно то, как используются множества; просто всюду вместо Бега или Бег передаются Бе! йапг!!е. Например: 25.7.
Дескрипторные классы ())апс(!ез) 915 го!й 1(Бе< !<апй<е«п<> е) ( уог(о«е р = <->!<с<< () < р< р = я->пах< () ) !! ... ) ыо!й иеег() ( Яе< «алй<е«л<> <1(пе<гй!е< ае«!и>); Яе< <<апй1е«лг> г (пе<г Местг ее«!п<> (100) ) < <(е!); ,<(г) < ) Часто требуется, чтобы дескриптор не ограничивался лишь предоставлением доступа. Например, если бы класс Юе< и дескриптор Яе< !<апй!е проектировались совместно, то можно было бы легко обеспечить подсчет ссылок, включив счетчики в объекты Хе<. Но в обшем случае, нам не хотелось бы проектировать дескрипторы совместно с управляемыми имн объектами, чтобы не хранить совместно используемые данные в отдельных объектах.
Иными словами, помимо интрузивных дескрипторов хотелось бы иметь и неинтрузивные. Вот пример дескрипторного класса, удаляюшего объект по исчерпанию ссылок на него: <етр1а<е<с!а«Х> с!а<а Напй<е ( Х* сер< т<* рсоип« риЫ<с: Х* орега<ог-> () (ге<игл гер; ) Напйте(Х* рр): сер (рр), рсоип<(пел !л<(1) ) ( ) Напй<е(соп«Натйеь г): сер(г.гер), рсоип< (г.рсоит) ( (*рсоип<) ее; ) Налй<еь орега<ог= (солт Напй!еь г) ( <! (гер == г. гер ) ге<игл *<«!е; (!'(-- («рсоип<) == О) ( йе<е<е гер; йе1е<е рсоип« ) гер = г.гер; рсоип< = г.рсоип« ( "рсоип<) ее; ге<игл *Й!ю ) -Напй<е() ( «(-- (*рсоил<) == О) (йе1е<е гер; йе<е<е рсоип« ) ) Глава 25.
Роли классов 916 Такие дескрипторы можно свободно передавать: гоЫ у) (Напб(е <Хе!> ); Напб1е<Бег> у2 ( ) ( Напг(1е<уез> Ь (пегг ГЫз! зе!<1пг>) ~У... ге!игп Ь) ) гоЫХ() ( Напб1е<Бе(> ЬЬ=12 (); У)(ЬЬ); !! ... ) Множество, созданное в 12(), будет автоматически удалено на выходе из функции я(), если только уу() не создает ссылку на него (программнсту здесь этого даже знать не надо). Естественно, такое удобство не дается даром, но для большинства прикладных программ цена хранения и обслуживания счетчика ссылок вполне приемлема. Иногда нужно извлечь указатель из дескриптора и использовать его напрямую.
Например, если нужно передать указатель функции, которая ничего не знает о дескрипторе. Это неплохо работает, если, конечно, функция не уничтожит переданный ей объект. Также может быть полезной операция привязки дескриптора к новому представлению: гетр!аге<с1азз Х> с!азз Напгйе ( У ... Х* яе! сер() (гесигп гер; ) гоЫ ЬгтЫ(Х* РР) ( (!'(рр ( = гер) ( 1!'(-- (*рсоип!) == 0) ( бе1еге гер г *рсоипг = !) ) ейе рсоип! = пеп 1пз(1); р обновить рсоит д новый рсоип( гер =рр; ) Заметим, что наследование новых классов от Нап(Пе не особенно полезно.
Это конкретный тип без виртуальных функций. Идея состоит в том, чтобы иметь один дескрипторный класс для семейства классов, определяемых одним базовым клас- 25.7. Дескрипторные классы ()тапс((ез) 917 25.7.1. Операции в дескрипторных классах Перегрузка операции -> позволяет дескриптору получать управление и вь(полнять некоторую работу при каждом обращении к объекту. Например, можно собрать статистику о числе обращений к объекту через дескриптор: (етр!а(е <с(азя Т> с(азя ХЬапйе ( Т* гер( 1н( ио оТ ассетез( риЫ(с: Т* орега(ог-> () (по о7" ассеззез»»( ге(игп гер; ) !1... Дескрипторы, которым нужно выполнить работу н до, и после обращения, требуют более изощренного программирования.
Рассмотрим, к примеру, множество, для которого нужно выполнять блокировку при вставках или удалениях элементов. Существенно, что интерфейс класса представления должен быть повторен в дескрипторном классе: (етр1а(е<с(ат Т> с1ат Бе( соп(гоНег Яе(<Т>* (ер( Аоса 1осЬ( 11 ... риЫ1с: юЫ !утет(Т» р) (Ьосй ри х((осЬ); гер->!пзег((р) ( ) юЫгетоге(Т» р) (йоса р(гх(1ос1») ( гер->гетоге(р); ) т((я тетЬег(Т* р) (ге(игн гер->1з тетЬег(р); ) Тле(Ягя(() (Т* р = гер->Ягя((); ге(игп р? "р: Т() ( ) Тае( иех(() (Т* р = гер->пех(() ( ге(игп р? "р: ТО; ) ТЯгя(О (Йоса р(гх(1осЬ); Таир = »гер->Яя(О; ге(игп ипр( ) Тпех(() (Еоса р(гх((осЬ) ( Таир = *сер->пех((); ге(иги (тр; ) »? ...