Бьерн Страуструп. Язык программирования С++. Специальное издание (2011) (1004033), страница 79
Текст из файла (страница 79)
Функция Мииаяег::рггиг() как раз служит наглядным примером. Поскольку тип объекта фиксирован уже при вызове Маяаяег::рггиг(), нет нужды определять его снова для вызова Етр!оуее::рг!иг() . Стоит не забывать, что очевидной и традиционной реализацией механизма вызова виртуальных функций является косвенный вызов (в2.5.5), высокая эффективность которого не должна удерживать программиста от применения виртуальных функций там, где приемлем обычный функциональный вызов. 12.3. Абстрактные классы Многие классы схожи с классом Етр!оуее в том отношении, что они полезны и сами по себе, и как базовые для производных классов.
Для подобных классов описанные в предыдущем разделе методики вполне достаточны. Но не все классы вписываются в такую схему работы. Некоторые классы, вроде класса ойаре, представляют абстрактные концепции, для которых реальных объектов не существует. Класс овире имеет смысл и полезен исключительно как абстрактный базовый класс для порождения конкретных производных классов. Это наглядно видно из того факта, что невозможно разумным образом определить его виртуальные функции: с(алэ эйаре ( риЬНс: иииа1 «оЫ го!иге (Ыг) ( еггог ( "Зйаре:: го<иге" ); ),(г не элегантно Ыниа!»иЫйган() (еггог("Яйаре::йаят) ) ) У..
)г Создание таких «бесформенных» объектов хотя и допустимо, но лишено всякого смысла: 12 3 Абстрактные классы 385 БЬаре я; //глупо: "яцаре!еяя яйаре" Действительно, какой смысл в объекте, любая операция над которым порождает лишь сообщение об ошибке. Лучше объявить виртуальные функции класса Ягоре как чисто виртуальные (риге Г1Г(иа!) С ПОМОЩЬЮ ИНИЦИаЛИЗатОРа =(к // абстрактный класс // чисто виртуальная функция // чисто виртуальная функция // часто виртуальная функция Класс с одной или несколькими чисто виртуальными функциями является абстрактныи классом (аьятгас1 с!аяя), объекты которого создавать недопустимо: Ягоре я; Абстрактный класс может использоваться только как интерфейсный и базовый для других классов. Например: с1аяя Ро(п1( /* ... */ ) 1 // замещает 5Ьарегтогате // замещает 5Ьаре::й аю // замещает БЬаре;Ля с!олег( С1гс1е (Ро1п1 р, тг г); Чисто виртуальная функция, не определенная и в производном классе, остается чисто виртуальной, и поэтому такой производный класс также является абстрактным.
Это открывает нам возможности постепенного осуществления реализации классовых иерархий: // абстрактный класс с(аяя Ро1уяоп: риЫ!с Яворе ( риЫ1с: Ьоо1Ы с!ояеИ() (ге!ига ггиег) //замещает ЯЬаре Ля с!ояеа // ... йаи апь! го(аге не замещаются )1 Ро(уяоп Ь1 с1аяя Ьтеяи!аг ро1уяоп: риЫ!с Рогуаоп ( БЫ< Ро!пг'г 1рт с!вял Кларе ( риЫ!с: Ыггиа( юЫ гоготе (!п1) = О; и!ггиа! юЫОгат() = О; и!гяиа1 Ьоо( Ы с!овей () = От // ...
с1аяя СЬ с!е: риЫ!с Ягоре ( риЫ1с: юЫ го1а!е (гпг) ( ) го!г( г!гав () / Ьоог Ы с!овей () (ге!ига ггие; ) рпжаге: Рот! сепяегь !пя гайия/ )1 //еггог переменная абстрактного класса Жаре //еггог: обьект абстрактного класса Ро1уаоп Глава 12. Наследование классов Э86 рив/кс: гоЫйгаи () к гоЫ «осаке (1пс) У...
)к УУ замещает ЯЬорекйгик« УУ замещает ь/карен«осаке /««ели/агро!ухни ро/у (зопкеро/псе) к У о/з (ески есть подходящий конструккпор) Важным примером применения абстрактных классов является предоставление интерфейса без какой-либо реализации. Например, операционная система может скрывать детали реализации аппаратных драйверов за вот таким абстрактным интерфейсом: с!аьз сдагассе«йег/се риЫс: «йгсиа/ кпк орел (ии орс) =Ок «йгсиа/ тк с/озе (кпс орс) =Ок Ипиа/кпс геай(слог' р, кпс п) =Ок кй«сиа/ /пс гег/се (свив! сьаг* р, кпс п) =Ок кйгсиа/ кпс коей(ии...
) =Ок г/«сккп/ -СЬа«ассе« йетсе () ( ) ' УУ виртуичьнлкй деструктор )к Мы можем определить драйверы в форме классов, производных от Пик«ассег йенсе, после чего управлять ими через указанный стандартный интерфейс. Важность виртуальных деструкторов разъясняется в 512.4.2. Теперь, после представления абстрактных классов,мы знакомы со всем инструментарием, позволяющим писать модульные программы, использующие классы в качестве строительных блоков. 12.4.
Проектирование иерархий классов Рассмотрим несложную проектную задачу: ввод в программу целого значения через пользовательский интерфейс. Это можно сделать огромным количеством способов. Чтобы позиционировать нашу задачу в рамках этого множества, и чтобы проанализировать различные возможные проектные решения, начнем с определения программной модели ввода.
Оставим на потом детали, необходимые для ее конкретной реализации в рамках реальных пользовательских интерфейсов. Идея состоит в создании класса 1«а/ Ьох, который знает допустимый диапазон вводимых значений. Программа может запросить 1«а/ Ьох об этом значении, а также предупредить пользователя. Также программа может попросить 1«а/ Ьох сообшить, не вводил ли пользователь новых значений после того, как программа получила предыдущее значение ввода. Поскольку можно по-разному реализовывать эту общую идею, мы предполагаем, что будет множество конкретных разновидностей элементов ввода типа 1«а/ Ьох, таких как ползунки, диалоговые окна, элементы для голосового ввода и т.д. В общем, мы хотилк реализовать «виртуальную систему пользовательского ввода» для использования в разных приложениях.
Она будет демонстрировать часть 12.4. Проектирование иерархий классов 387 12.4.1. Традиционные иерархии классов Наше первое решение сводится к традиционной иерархии классов, типичной для языков б(шп!а, Бгпа!)(а!)( и старых версий С++. Класс 1«а! Ьох определяет базисный интерфейс для всех элементов пользовательского ввода и задает его реализацию, которую более специфические элементы могут переопределять. Кроме того, мы обьявляем здесь данные, необходимые для формирования любого элемента ввода: сйт 1«а! Ьох р«о1есзед: шз га1; (пз!ои, Ь(ея; Ьооl сяапяед! риЬПс: 1«а! Ьох(1п( П, !пг ЬЬ) (сяапаел( =!а!зев «а1 = 1ои = П; Ь(аЬ = ЬЬз «пгиа1 1пг аез гаlие ( ) ( слапаед = 1а(зе; ге!игл «а! ! ) Ими! юЫ лег «а!ив(шз з) (сяапеед=(гие; ю1=1; ) Иггиа( юЫ геле! га!ие (шг з) ( слапяеа=уаае; ва!=1; ) /У для приложения У для пользователей /У для приложения «1«гиа! юЫрготр!() () иниаl Ьоо! згаз сяапяеИ() сопл! (ге!ига саапяед) ) Представленная здесь реализация функций довольно небрежна и нацелена лишь на демонстрацию основных намерений.
В реальном коде осуществлялась бы хоть какая-то проверка введенных значений. Данный класс можно использовать следующим образом: гоЫ !п!егас! (1«а! Ьох* рЫ ( рЬ->рготрг(); П ... О (рЬ->паз сяапяед() ) ( 1Ы г'=рЬ->лез гагие (); // новое значение; что-нибудь делаем ) еде ( П старое значение подходит; делаем что-нибудь еи(е Ф оповестить пользователя функциональности, реализованной в настоящих системах пользовательского интерфейса.
Ее желательно реализовать в широком наборе операционных систем, так что нельзя забывать о переносимости кода. Естественно, наш подход не единственно возможный. Я выбрал его потому, что он достаточно общий и позволяет продемонстрировать широкий набор методов и технологий проектирования. Эти методы не только использовались для построения реальных систем пользовательского интерфейса, но они вообще выходят далеко за рамки интерфейсных систем.
звв Глава ) 2. Наследование классов чоЫзоте !сг() ( 1ча! Ьох* р1 = пен 1ча! з!Ыег (О, 5) з йиегасз(р1) з УУ1ча! з(Ыег послед>ет от 1чо( Ьох 1ча1 Ьох* р2 = пен 1ча! Йа! (1, 12) з ииегасг(р2); ) с1азз1ча! з!Ыег: риЫи 1ча1 Ьох ( УУ графические атрибуты, определяющие еид ползунка (з!Ыег! и т.д. риЫЫ: 1ча1 зрщег(гпс, гпг) з !пз уез ча1ие ( ) з чоЫ рготрг ( ); )з Члены данных класса 1ча! Ьох были обьявлены защищенными с целью предоставления прямого к ним доступа из производных классов.
Таким образом, 1ча1 з!!з!ег::дег ча1ие () может держать значение в 1ча1 Ьох:: ча1. Напоминаем, что зашишез)ные члены доступны самому классу и его производным классам, но не обычному клиентскому коду (515.3). В дополнение к 1ча1 з1сЫег мы определим и другие конкретные элементы ввода, реализующие специфические варианты общей концепции 1ча! Ьох.
Это может быть Большая часть кода написана в стиле, использующем доступ к элементу ввода через указатель типа 1ча1 Ьох* (см. функцию ииегасг() ). Поэтому приложению нет нужды знать обо всех потенциально возможных конкретных элементах ввода. Знание же об этих специфических элементах требуется лишь в строго ограниченных частях кода (небольшом количестве функций), имеющих дело с созданием объектов соответствующих типов. Это помогает в значительной степени изолировать пользователя от изменений в реализациях производных классов. Большая часть кода может не обращать внимания на существование множества различных интерфейсных элементов ввода.