Б. Страуструп - Язык программирования С++. Специальное издание, 3-изд. Бином. 2004 (1160791), страница 198
Текст из файла (страница 198)
25.7. Вспомогательные классы Простым примером вспомогательного класса является Яппд из ~ 11.12. Вспомогательный класс обеспечивает интерфейс, контроль за доступом н управление памятью для представления. В данном случае и вспомогательный класс, и представление являются конкретными типами, хотя класс представления часто бывает абстрактным. Абстрактный тип обеспечивает эффективное разделение интерфейса и его реализаций.
Однако, если следовать б 25.3, получается неразрывная связь между интерфейсом, предоставляемым абстрактным типом, и его реализацией, обеспечиваемой конкретным типом. Например, невозможно «отвязатьь абстрактный итератор от одного источника — скажем, множества, и привязать к другому — например, потоку, когда первоначальный источник исчерпан. Более того, если нс манипулировать объектом, релизующим абстрактный класс, через указатели или ссылки, выгоды от виртуальных функций теряются. Пользовательская программа может стать зависимой от деталей классов реализации, поскольку абстрактный тип (в том числе, если это объект, принятьш в качестве аргумента, переданного по значению), если его размеры неизвестны, нельзя расположить статически в памяти или в стеке.
Использование указателей и ссылок подразумевает, что бремя распределения памяти ложится на пользователя. Другое ограничение подхода с применением абстрактных классов заключается в том, что объект класса имеет фиксированные размеры. Однако классы используются для представления понятий, требующих различное количество памяти для своей реатизации. Распространенным методом решения этих трудностей является разделение единого объекта на два: вспомогательного, обеспечивающего пользовательский интерфейс, и представления, содержащего все илн большую часть состояния объекта.
В качестве связи между вспомогательным классом и представлением, как правило, выступает указатель во вспомогательном классе. Часто вспомогательные классы имеют чуть больше данных, чем просто указатель па представление, но ненамного. Это подразумевает, что расположение в памяти объектов вспомогательного класса, как правило, неизменно, даже когда представление изменяется, и что вспомогательные классы достаточно малы для свободной пересылки между функциями, так что пользователю не нужно применять указатели и ссылки. 860 Глава 25.
Роли классов Рассмотрим абстрактный тнп множеств Зе1 из 8 25.3. Как ввести для него вспомогательный класс, какие выгоды это принесет, и какой ценой? Для класса множеств вспомогательный класс можно определить, просто перегрузив оператор ->: 1етр!а!е<с1авв Т> с!авв Зе1 Бап)!!е ( Зе)<Т>* гер, ри6!!с: Ве)<Т>" прего)ог — > () ( ге1игп гер; ) $е1 ))ап)1!е (Бе1<Т>*рр): гер(рр) () Это не очень существенно влияет на то, как используются множества; просто повсю- ду вместо Зе)й или Зе1* передаются Зе1 пап)((е. Например: оо!)!!(Бе1 Ьап)!!е<!п)> в) ( Уог (! и Р р = в- у)гв) (); р; р = в — пех1 ()) ( О.- ) оо!))ивег() ( Ве) Ьап)!!е<!п)>в!(петй'в) ве)<!и)>) Бе1 Иап))!е<)п)> о (пе)с ()ес)ог ве)<)п)> (100)); У(в)); У(с); Часто нам нужно, чтобы вспомогательный класс не только обеспечивал доступ. Например, если классы Яе1 и Яе1 Лала)(е проектируются вместе, легко ввести счетчик ссылок, включив соответствующие счетчики в калсдый класс множеств.
Вообще говоря, нам не нужно проектировать вспомогательный класс вместс с тем, чему он будет <помогать>, поэтому любую информацию, которая будет использоваться совместно со вспомогательным классом, нам прилется сохранять в отлельпом объекте. Иными словами, кроме интрузивных вспомогательных классов нам бы хотелось иметь и неинтрузивныс. Например, вот вспомогательный класс, который удаляет объект, когда исчезает его последний вспомогательный объект: 1етр!а1е<с!авв Х> с1авв Пап)!!е ( Х')ер; ш1" рсоип1; риЫ!с: Х' орега1ог-> () ( ге1игп гер; ) Нап)!!е (Х" рр): гер (рр), рсоип1 (пеи) ш1 (!)) () Нап)1!е (сопв) Нап)1!ей )), гер (г сер), рсоип1 (г рсоипй ( (рсоип))++, ) Напрей оре га 1о)= (сопв1 Нап)!!ей )) ( )) (гер == пгер) ге1игп *1)ив; )Т ( — ('рсоип)) == О) ( 861 25.7.
Вспомогательные классы с!е!е1е гер; с!е!е1е рсоил1; ) гер = леер; рсоил1 = г.рсоип1; ("рсоил1)<.+; ге1игл '1Ые; -Нале!!е () ((/ ( — ( рсоип1) == 0) ( 0е!е1е рсоип1; ) ) //-. Такие вспомогательные объекты можно свободно передавать. ооЫ/! (Нале!!е<5е1>) Напс!!е-5е1>Х2 () ( Нап<!!е<3е1> Ь (лет 7.ге1 ее1<ЫГ ); //- ге1игл Ь; ооЫя() ( НапИ!е<8е1> ЬЬ=/2 (); /! (ЬЬ); //... ' Здесь множество, созданное в/2 (), будет удалено на выходе из Ьс() (если толькоЯ () его не скопирует); программисту нет нужды заботиться об этом.
Естественно, за это удобство приходится платить, но для многих прикладных программ цена хранения и обслуживания счетчика использований приемлема. Иногда указатель на представление полезно извлечь из вспомогательного класса и воспользоваться нм напрямую — например, если нужно перелать объект в функцию, которая не знает о вспомогательном классе. Это хорошо работает, если вызываемая функция не разрушает переданный ей объект или сохраняет указатель на него для использования после возврата в вызвавшую функцию. Также может пригодиться операция привязывания вспомогательного класса к новому представлению: 1ет р!а1е<с!аее Х> с!аее Нале!!е ( Х' де1 сер ()( ге1игл гер,) поЫ Ь!лс! (Х' рр) (1 (рр! = гер) ( (/ ( — (*рсоилг) == О) ( с!е!е1е гер; *рсоип1 1; // ооловите рсоип1 Глава 25.
Роли классов 862 е1зе реоип1 = печи Ш1 (1); гер =рр; ) ) // наний реоип1 25.7.1. Операции во вспомогательных классах Перегрузка оператора ->дает возможность вспомогательному классу получать управле- ние н выполнять некоторую работу при каждом обращении к объекту. Например, можно собрать статистику о числе использований объекта через вспомогательный класс 1етр!а1е<с!а <я Т> е!азя Хбапд!е ( Т" гер; М1 по о/' ассеззез; риб!1с. Т' орега1ог — - () ( по о/ асееязез«+; ге!игл гер; ) О число обращении Вспомогательные классы, для которых требуется выполнение работы и до, и после.
обра- щения, требуют более изощренного программирования. Например, может понадобиться множество с блокировкой во время вставки илп удаления элемента. Здесь очень важно, чтобы интерфейс класса-представления был повторен во вспомогательном классе: 1етр!а!е<с!аяя Т> е!аяз Бе! еоп1говег ( Яе1<Т>" гер; Еоеб 1оеб; //- риЫ!е; оо!<! !пяег! (Т' р) ( Тоеб рггх (!оса; гер — >!изет! (р); ) оо!<( гетоие , 'Т' р) ( Тоеб р1гх (!оеб) гер — >гетоие (р); ) !п1!з тетбег(т'р)( ге1игп гер->!з течпбег(р); ) т' ие1 йгз1 () ( т' р = гер — >~те! (); ге!игл рз *р: т (); ) Т" аег пех1 () ( Т' р = гер — пех1 (), ге1игп рл *р: Т (1„) //ск З !4,4.! Отметим, что построение производных классов от вспомогательного класса Напг!!е не особенно полезно.
Это конкрепзый тип без виртуальных функций, Идея заключается в том, чтобы иметь один вспомогательный класс для семейства классов, определяемых базовым классом. Л вот создание производных классов от этого базового класса может оказаться мощным приемом. Он применим и к узловым классам, и к абстрактным типам, Как уже сказано, вспомогательный класс Напг!(е не связан с наследованием. Чтобы получить класс, действующий как истинный указатель, считающий число использований. Напг!!е нужно скомбинировать сР1гпз З 13631 (см. З 2510[21).
Вспомогательный класс, обеспечивающий интерфейс, почти идентичный классу, для которого оп является «вспомогательным», часто называют заместителе и (ргоху). Такой прием часто используется для классов, ссылающихся на объекты на удаленной машине. вбЗ 25.8. Прикладные среды разработки ТЯгег() (Тосй рсгх ()ос)с), Тетр ='гер — >!)гз! (); ге!ига стр; ) Тпехс() (Тосд рггх (!осд) Т!тр = *гер- пехс(), ге!игп !тр, ) О); Введение этих переадресующих функций утомительно (и поэтому здесь, в известном смысле, кроется источник ошибок), хотя оно нетрудно и не дорого с точки зрения выполнения программы. Отметим.
что только некоторые из функций яе! требуют блокировки. 11о своему опыту я могу сказать, что, как правило, класс, нуждающийся в выполнении пред-действий и пост-действий, требует этого лишь для нескольких функций-членов. В случае с блокировкой, блокировка всех операций — как это делается для мониторов в некоторых системах — ведет к излишним блокировкам и иногда приводит к значительной потере параллельности. Одно из преимуществ тщательного определения всех операций вспомогательного класса через перегрузку оператора -> заключается в том, что можно создавать классы, производные от Зе! соп!го!!ег. К сожалению, некоторые выгоды вспомогательных классов становятся сомнительными, если в производный класс добавляются члены данных.
В частности. объем совместно используемого кола (во «вспомогаемых. классах) уменьшается по сравнению с объемом кода в каждом вспомогательном классе. 25.8. Прикладные среды разработки Компоненты, построенные из классов разных видов, описаг!пых в э25.2-э25.7, поддерживают проектирование и повторное использование кода, предоставляя и строительные блоки, и способы их соединения; создатель прикладной программы проектирует единую среду разработки, в которую вк.лючаются этн общие строительные блоки, Лльтернативный, и иногда более амбициозный подход к поддержке проектирования и повторного использования — сначала предоставить общую среду разработки, в которую бы создатели прикладных программ вставляли специфичные для приложения фрагменты кода в качестве строительных блоков.
Такой подход часто называют прикладной средой разработки. Классы, образующие подобную среду, зачастую имеют такой жирный интерфейс, что вряд ли являются классами в обычном понимании. Опи приближаются к идеалу завершенных прикладных программ, если не считать того, что они ничего ие делают. Конкретные действия задаются прикладным программистом.