Бьерн Страуструп. Язык программирования С++. Специальное издание (2011) (1004033), страница 77
Текст из файла (страница 77)
Например: Глава )2. Наследование классов Мапаеег: Етр!оуее: Язг пате уатИу пате 11 1ат дгоир !вне! Отсюда вытекает, что объекты типа Мапаяег представляют собой подтип Етр!оуее, ибо их можно использовать всюду, где допустимы Етр!оуее. Например, теперь мы можем составить список сотрудников, некоторые из которых являются менеджерами: гоЫ У(Маоадег т1, Етр1оуее е1) ( !(згсЕтргоуее*> е1(зг; е1!зг.ризй /гоп! ( ьт! ); е11зг.ризА гоп! ( ье1 ); (г... оо!а е (Мападег тт, Етр!оуее ее) ( Етргоуее" ре = ьттг Мападег* рт = ьее! Ройс каждый Мапаяег есть Етр!оуее Реп ос: не каждый Етр!оуее есть Мапаеег Р катастрофа: у обьекта ее нету поля !еке! рт->геке! = 1! рт=зои!с сазг<Мападег*> (ре); ргрубая сила работает, тк реуказует тт (Мападе~) рт->!вне! = 1; У о!с рт указывает на тт, у которого есть !сне! ) Другими словами, с объектом производного класса можно обращаться как с объектом базового класса, если манипулировать им через указатели или ссылки.
Обратное же неверно. Лрименение операций приведения згаис саз! и Мулат!с сазг обсуждается в з15.4.2. Использование класса в качестве базового эквивалентно объявлению (неименованного) объекта этого класса. Следовательно, чтобы класс можно было использовать в качестве базового, он должен быть определен (З5.7): Так как объект типа Мапаяег является в то же время и Етр!оуее, то можно использовать Мапаиег* и как Етр1оуее*.
Обратное же необязательно верно — не каждый сотрудник является менеджером, так что Етр!оуее* нельзя использовать вместо Мапаиег*. В общем, если класс Рег!вел является открытым наследником базового класса (з15.3) Вазе, то переменную типа Рег!гЫ* можно присвоить переменной типа Вазе* без необходимости в явном приведении типа. Обратное же присваивание требует явного преобразования типов.
Например: 12.2. Производные классы 375 //лишь объявление (не определение) с1алл Ер(оуее; с(ат Мапалег: риЫ!с Етр1оуее У еггог: Етр(оуее не определен ( И... )' 12.2.1. Функции-члены с!ат Етр(оуее ( лгг!ла/)гл! пате, /ат!1у пате; слог тйЫ1е !пй!а1; /У ... риддс: ноЫрпп! () сопи(г и!г!па (иИ пате() сопи (ге!иглу)гл! пате+ ' ' +т!гЫ!е т1йа1+ ' ' ъ/атпу лате; ) // ... ): с1азл Мапааег: риЫЫ Етр1оуее ( // ... риЫгс: поЫ рил! ( ) солт! И... )' Любой член производного класса может обращаться к любому открытому (а также защищенному — см.
515.3) члену базового класса, как будто бы последние были непосредственно объявлены в производном классе. Например: гоЫ Мапааег:: рг!пг ( ) соля! ( соиг« "поте Ь" «7иИ пате () « ' ~п ' г // ... ) Закрытые же члены базового класса напрямую не доступны в производном классе: гоЫ Мапаяег:;рядн!() сопи ( сош« "пате Ы" «/атпу пате « ' ~п' ! У... ) У еггог! Эта вторая версия Мапаяег::рг!лг() компилироваться не будет. Члены производного класса не имеют специального разрешения на доступ к закрытым членам Столь простые структуры данных, как представленные выше Етр!оуее и Малаяег, не слишком интересны и не слишком полезны. Нам нужно представить информацию с помощью надлежащего типа, предоставляюгцего все необходимые операции, и при этом не связываться с деталями конкретных представлений.
Например: 376 Глава 12. Наследование классов базового класса, поэтому к гат11у лате нельзя обращаться в теле функции Малаяег::рг1пГО . Для некоторых это является сюрпризом, но рассмотрим альтернативу: функция-член производного класса может иметь прямой доступ к закрытым членам базового класса. Концепция закрытых членов при этом стала бы бессмысленной, поскольку для доступа к закрытой части класса программист должен был бы всего лишь написать производный от него класс. Кроме того, чтобы отыскать все случаи использования закрытой части класса, недостаточно будет в таком случае просмотреть лишь функции-члены класса и его дружественные функции.
Теперь нужно будет просматривать каждый исходный файл проекта на предмет обнаружения производных от исходного классов, тщательного изучения всех их функций-членов, а затем нужно будет выявить классы, производные от производных и так далее, и тому подобное. Это в самом лучшем случае утомительно, а часто и просто нереально. В базовых классах, где это возможно, нужно использовать ключевое слово ргогесгеб вместо рг(ваге. Защищенные члены (рго(ес(еб) в отношении функций производных классов ведут себя как открытые, а в остальных случаях — как закрытые 615.3).
Как правило, самым простым решением является использование в производном классе лишь открытых членов базового класса. Например: чоЫ Мападег:: рг!пг ( ) солт ( Етр1оуее:: ринг (); У вывести общую информацию У Гхарактерную для любого сотрудника) соил«1ече1; ~7 вывести информацию, специфичную для типа Малаяег ) Обратите внимание на то, что нужно использовать операцию::„поскольку функция рплг() переопределяется (гебейпеб) в классе Маладег. Очень опрометчиво написать следующее: чо1а Мапаеег::ргглг() сопи ( ~У печатаем специфичную для типа Малаеег информацию: рг(лг() г гг' оорл( ибо при этом порождается неожиданная рекурсия.
12.2.2. Конструкторы и деструкторы Некоторые производные классы нуждаются в конструкторах. Если в базовом классе определены конструкторы, то их тоже нужно вызывать. Конструкторы по умолчанию могут вызываться неявно. Остальные же типы конструкторов базового класса должны вызываться явно. Например: с1авл Етр1оуее ( лггулд1)гвг пате, уатйУ лате; ллогг аераггтелг г ) 2.2. Производные классы риЫзс: Етр1оуее (сопл! зи(ща п, тз И) / /У.,. ): с!азз Мапааег: рибдс Етр(оуее ( Изз<Етр!оуее*> Лгоирз //подчиненные з!зогз!еве1/ У...
риЫ(с: Мападег(сопл! з!г/пав и, зп! а', 1пг 1Ы) з //... )з Аргументы для конструктора базового класса указываются в аргументах конструктора производного класса. В этом отношении, базовый класс функционирует в точности как член производного класса (510.4.6). Например: Етр(оуее:: Етр(оуее (сопт згг!пав и, т! И) : уатИу пате(п), з(ераг!тенг(д) //инициализация членов класса Етр1оуее ( //...
) Мапааег::Мапаеег(сопл! з1мпдь и, (изб, 1(1 ИИ) : Етр!оуее (и, з!), У инициализация членов базового класса !вне!(ИИ) // инициализация членов класса Малаяег ( И... ) Конструктор производного класса может задавать инициализаторы только для своих членов и непосредственных базовых классов (он не может явным образом инициализировать члены базового класса). Например: Мападег::Мападег(сопл! з!г1пдь и, 1пзз(, зп! 1в1) : уатйу пазпе(п), // езтог:/ат11у пате не объявлен в Манаяег з(ераггтеп! (з(), У еггог: г!враг(теп( не объявлен в Малаяег 1ече! ( 1г!) ( // ... ) Здесь содержатся три ошибки; не удается вызвать конструктор базового класса Етр!ззуее, а также дважды осуществляется ошибочная попытка прямой инициализации членов базового класса. Объекты классов конструируются снизу-вверх: сначала базовый класс, затем инициализируются члены, и только потом остальная инициализация производного класса.
Уничтожение объектов выполняется в обратном порядке. Конструирование членов (указанных в списке инициализации конструктора) выполняется строго в порядке появления их объявлений в определении класса, уничтожение же производится точно в обратном порядке. См, также 510.4.6, З15.2.4.1 и й15.4.3. ЗТВ Глава ) 2. Наследование классов 12.2.3. Копирование Копирование классовых объектов определяется копирующим конструктором и операцией присваивания (Ь!0.4.4.1). Рассмотрим пример: с!ат Етр(оуее ( У ...
Етр!оуееь оретагог= (сопи Етр!оуесь ) Етр1оуее ( сопи Етр!оуееь ); то!д Г (сопя! Мападегь т) ( Етр1оуее е = т! е=т! ) У конструируем е из Етр!оуее-части т У присваиваем Етр!оуее-чость т объекту е Поскольку копирующие функции класса Етр!оуее ничего не знают о классе Малаяег, то копируется лишь часть объекта типа Мапаяег — та, что достается ему от базового класса Етр(оуее. Этот эффект часто называют срезкой (вйс!пй), и он может оказаться причиной недоразумений и ошибок.
Одной из причин передачи указателей на объекты классовых иерархий наследования является желание избежать срезки. Другими причинами служат желание обеспечить полиморфное поведение Я2.5.4, й)2.2.6) и достичь эффективности. Помните, что если вы не программируете явно операцию присваивания, то компилятор предоставляет собственный вариант этой операции (Ь! 1.7). Это подразумевает, что операции присваивания нс наследуются.
Конструкторы тоже никогда не наследуются. 12.2.4. Иерархии кпассов Производный класс, в свою очередь, тоже может быть базовым классом. Например: с!ат Етр(оуее ( !*... *I ) ! с!авв Мападег: риЫ(с Етр!Ьуее( l*... *I ) ! с!авв !т!тес!ог: риЬИс Маиааег( /*... *! ) ! с1ат Тетротагу( l*...*I ) ! с1ат 5естегату: риЫ!с Етр1оуее( l*... */ ); с1авя Твес: риЬИс Тетротату, риЬИс Бестегагу( l*... *I ) ! с1ат Сопвивапг: риЫтс Тетрогагу, риЬИс Мападег( l*...