Передача параметров конструкторам базового класса



 

До сих пор ни один из предыдущих примеров не включал конструкторы, для которых требовалось бы передавать аргументы. В случаях, когда конструктор лишь производного класса требует передачи одного или нескольких аргументов, достаточно использовать стандартный синтаксис параметризованного конструктора. Но как передать аргументы конструктору базового класса? В этом случае необходимо использовать расширенную форму объявления конструктора производного класса, в которой предусмотрена возможность передачи аргументов одному или нескольким конструкторам базового класса. Вот как выглядит общий формат такого расширенного объявления.

 

 

Здесь элементы base1baseN означают имена базовых классов, наследуемых производным классом. Обратите внимание на то, что объявление конструктора производного класса отделяется от списка базовых классов двоеточием, а имена базовых классов разделяются запятыми (в случае наследования нескольких базовых классов).

Рассмотрим следующую простую программу.

 

 

Здесь конструктор класса derived объявляется с двумя параметрами, х и у . Однако конструктор derived() использует только параметр х , а параметр у передается конструктору base() . В общем случае конструктор производного класса должен объявлять параметры, которые принимает его класс, а также те, которые требуются базовому классу. Как показано в предыдущем примере, любые параметры, требуемые базовым классом, передаются ему в списке аргументов базового класса, указываемого после двоеточия.

Рассмотрим пример программы, в которой демонстрируется наследование нескольких базовых классов.

 

 

Важно понимать, что аргументы для конструктора базового класса передаются через аргументы, принимаемые конструктором производного класса. Поэтому, даже если конструктор производного класса не использует никаких аргументов, он, тем не менее, должен объявить один или несколько аргументов, если базовый класс принимает один или несколько аргументов. В этой ситуации аргументы, передаваемые производному классу, "транзитом" передаются базовому. Например, в следующей программе конструкторы base1() и base2() , в отличие от конструктора класса derived , принимают аргументы.

 

 

Конструктор производного класса может использовать любые (или все) параметры, которые им объявлены для приема, независимо от того, передаются ли они (один или несколько) базовому классу. Другими словами, тот факт, что некоторый аргумент передается базовому классу, не мешает его использованию и самим производным классом. Например, этот фрагмент кода совершенно допустим.

 

 

При передаче аргументов конструкторам базового класса следует иметь в виду, что передаваемый аргумент может содержать любое (действительное на момент передачи) выражение, включающее вызовы функций и переменные. Это возможно благодаря тому, что C++ позволяет выполнять динамическую инициализацию данных.

 

Предоставление доступа

 

Когда базовый класс наследуется закрытым способом (как private‑класс), все его члены (открытые, защищенные и закрытые) становятся private‑членами производного класса. Но при определенных обстоятельствах один или несколько унаследованных членов необходимо вернуть к их исходной спецификации доступа. Например, несмотря на то, что базовый класс наследуется как private ‑класс, определенным его public ‑членам нужно предоставить public ‑статус в производном классе. Это можно сделать двумя способами. Во‑первых, в производном классе можно использовать объявление using (этот способ рекомендован стандартом C++ для использования в новом коде). Но мы отложили рассмотрение директивы using до темы пространств имен. (Основное назначение директивы using – обеспечить поддержку пространств имен.) Во‑вторых, можно настроить доступ к унаследованному члену с помощью объявлений доступа. Объявления доступа все еще поддерживаются стандартом C++, но в последнее время активизировались возражения против их применения, а это значит, что их не следует использовать в новом коде. Поскольку они все еще используются в С++‑коде, мы уделим внимание этой теме.

Объявление доступа имеет такой формат:

 

 

Объявление доступа восстанавливает уровень доступа унаследованного члена, в результате чего он получает тот уровень доступа, который был у него в базовом классе.

Объявление доступа помещается в производном классе под соответствующим спецификатором доступа. Обратите внимание на то, что объявления типа в этом случае указывать не требуется.

Чтобы понять, как работает объявление доступа, рассмотрим сначала этот короткий фрагмент кода.

 

 

Поскольку класс base наследуется классом derived закрытым способом, его public ‑переменная j становится private ‑переменной класса derived . Однако включение этого объявления доступа

 

 

в классе derived под спецификатором public восстанавливает public ‑статус члена j .

Объявление доступа можно использовать для восстановления прав доступа public ‑ и protected ‑членов. Однако для изменения (повышения или понижения) статуса доступа его использовать нельзя. Например, член, объявленный закрытым в базовом классе, нельзя сделать открытым в производном. (Разрешение подобных вещей разрушило бы инкапсуляцию!)

Использование объявления доступа иллюстрируется в следующей программе.

 

 

Обратите внимание на то, как в этой программе используются объявления доступа для восстановления статуса public у членов j , seti() и geti() . В комментариях отмечены и другие ограничения, связанные со статусом доступа.

C++ обеспечивает возможность восстановления уровня доступа для унаследованных членов, чтобы программист мог успешно программировать такие специальные ситуации, когда большая часть наследуемого класса должна стать закрытой, а прежний public ‑или protected ‑статус нужно вернуть лишь нескольким членам. И все же к этому средству лучше прибегать только в крайних случаях.

 

Чтение С++‑графов наследования

 

Иногда иерархии С++‑классов изображаются графически, что облегчает их понимание. Но порой различные способы изображения графов наследования классов вводят новичков в заблуждение. Рассмотрим, например, ситуацию, в которой класс А наследуется классом В , который в свою очередь наследуется классом С . Используя стандартную С++‑систему обозначений, эту ситуацию можно отобразить так:

 

 

Как видите, стрелки на этом рисунке направлены вверх, а не вниз. Многие поначалу считают такое направление стрелок алогичным, но именно этот стиль принят большинством С++‑программистов. Согласно стилевой графике C++ стрелка должна указывать на базовый класс. Следовательно, стрелка означает "выведен из" , а не "порождает" . Рассмотрим другой пример. Можете ли вы описать словами значение следующего изображения?

 

 

Из этого графа следует, что класс Е выведен из обоих классов С и D . (Другими словами, класс Е имеет два базовых класса С и D .) При этом класс С выведен из класса А , а класс D – из класса В . Несмотря на то что направление стрелок может вас обескураживать на первых порах, все же лучше познакомиться с этим стилем графических обозначений, поскольку он широко используется в книгах, журналах и документации на компиляторы.

 

Виртуальные базовые классы

 

При наследовании нескольких базовых классов в С++‑программу может быть внесен элемент неопределенности. Рассмотрим эту некорректную программу.

 

 

Как отмечено в комментариях этой программы, оба класса derived1 и derived2 наследуют класс base . Но класс derived3 наследует как класс derived1 , так и класс derived2 . В результате в объекте типа derived3 присутствуют две копии класса base , поэтому, например, в таком выражении

 

 

не ясно, на какую именно копию члена i здесь дана ссылка: на член, унаследованный от класса derived1 или от класса derived2 ? Поскольку в объекте ob присутствуют обе копии класса base , то в нем существуют и два члена ob.is ! Потому‑то эта инструкция и является наследственно неоднозначной (существенно неопределенной).

Есть два способа исправить предыдущую программу. Первый состоит в применении оператора разрешения контекста (разрешения области видимости), с помощью которого можно "вручную" указать нужный член i . Например, следующая версия этой программы успешно скомпилируется и выполнится ожидаемым образом.

 

 

Виртуальное наследование базового класса гарантирует, что в любом производном классе будет присутствовать только одна его копия.

Применение оператора "::" позволяет программе "ручным способом" выбрать версию класса base (унаследованную классом derived1 ). Но после такого решения возникает интересный вопрос: а что, если в действительности нужна только одна копия класса base ? Можно ли каким‑то образом предотвратить включение двух копий в класс derived3 ? Ответ, как, наверное, вы догадались, положителен. Это решение достигается с помощью виртуальных базовых классов .

Если два (или больше) класса выведены из общего базового класса, мы можем предотвратить включение нескольких его копий в объекте, выведенном из этих классов, что реализуется путем объявления базового класса при его наследовании виртуальным. Для этого достаточно предварить имя наследуемого базового класса ключевым словом virtual.

Для иллюстрации этого процесса приведем еще одну версию предыдущей программы. На этот раз класс derived3 содержит только одну копию класса base .

 

 

Как видите, ключевое слово virtual предваряет остальную часть спецификации наследуемого класса. Теперь оба класса derived1 и derived2 наследуют класс base как виртуальный, и поэтому при любом множественном их наследовании в производный класс будет включена только одна его копия. Следовательно, в классе derived3 присутствует лишь одна копия класса base , а инструкция ob.i = 10 теперь совершенно допустима и не содержит никакой неоднозначности.

И еще. Даже если оба класса derived1 и derived2 задают класс base как virtual ‑класс, он по‑прежнему присутствует в объекте любого типа. Например, следующая последовательность инструкций вполне допустима.

 

 

Разница между обычным базовым и виртуальным классами становится очевидной только тогда, когда этот базовый класс наследуется более одного раза. Если базовый класс объявляется виртуальным, то только один его экземпляр будет включен в объект наследующего класса. В противном случае в этом объекте будет присутствовать несколько его копий.

 


Дата добавления: 2018-09-22; просмотров: 577; Мы поможем в написании вашей работы!

Поделиться с друзьями:






Мы поможем в написании ваших работ!