Урок 157. Наследование и спецификаторы доступа. Protected

   | 

   | 

 Обновлено 2 Сен 2018  | 

 447

В предыдущих уроках этой главы мы говорили о том, как работает наследование в C++. Во всех наших примерах мы использовали открытое наследование.

В этом уроке мы рассмотрим детальнее этот тип наследования, а также два других (private и protected). Также поговорим о том, как эти типы наследований взаимодействуют со спецификаторами доступа для разрешения или ограничения доступа к членам.

Мы уже рассматривали спецификаторы доступа private и public, которые определяют, кто может иметь доступ к членам класса. В качестве напоминания: доступ к членам public открыт для всех. К членам private доступ имеют только члены того же класса, в котором находится член private. Это означает, что дочерние классы не могут напрямую обращаться к членам private родительского класса!

Всё просто.

Примечание: public – перевод «открытый», private – перевод «закрытый», protected – перевод «защищенный». Дабы избежать тавтологии, в статье используется как перевод, так и оригинал.

Спецификатор доступа protected

В C++ есть третий спецификатор доступа, о котором мы еще не говорили, так как он полезен только в контексте наследования. Спецификатор доступа protected открывает доступ к члену класса дружественным и дочерним классам. Доступ к члену protected вне класса — закрыт.

В примере выше вы можете видеть, что член m_protected класса Parent напрямую доступен дочернему классу Child, но доступ к нему для членов извне — закрыт.

Когда следует использовать спецификатор доступа protected?

К членам protected родительского класса доступ открыт для членов дочернего класса, а это означает, что если вы позже измените что-либо в члене protected (тип данных, значение и т.д.), то вам придется внести изменения как в родительский, так и во все дочерние классы.

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

Создание членов private дает вам лучшую инкапсуляцию и изолирует родительские классы от изменений, вызванных дочерними классами. Но цена этому — создание открытого или защищенного интерфейса (способа взаимодействия других объектов с классами и их членами, т.е. геттеры и сеттеры). Это дополнительная работа, которая не стоит того, если вы сами работаете со своими же классами (чужие классы не обращаются к вашему классу) и количество дочерних классов небольшое.



Типы наследований. Доступ к членам

Существует три типа наследований классов: public, private и protected.

Для определения типа наследования нужно просто указать нужное ключевое слово возле наследуемого класса:

Если вы сами не определили тип наследования, то в C++ по умолчанию будет выбран тип наследования private (аналогично членам класса, которые по умолчанию private, если не указано иначе).

Это дает нам 9 комбинаций: 3 спецификатора доступа (public, private и protected) и 3 типа наследования (public, private и protected).

Так в чем же разница между ними? Если вкратце, то при наследовании, спецификатор доступа члена родительского класса может быть изменен в дочернем классе (в зависимости от типа наследования). Другими словами, члены, которые были public или protected в родительском классе, могут стать private в дочернем классе.

Это может показаться немного запутанным, но всё не так уж плохо. Мы сейчас со всем этим разберемся, но перед этим вспомним следующие правила:

  Класс всегда имеет доступ к своим (не наследуемым) членам.

  Доступ к члену класса основывается на его спецификаторе доступа.

  Дочерний класс имеет доступ к унаследованным членам родительского класса на основе спецификатора доступа этих членов в родительском классе.

Наследование типа public

Открытое наследование является одним из наиболее используемых типов наследования. Очень редко вы увидите или будете использовать другие типы, поэтому основной упор следует уделить на понимание именно этого типа наследования. К счастью, открытое наследование является самым легким и простым из всех типов. Когда вы открыто наследуете родительский класс, то унаследованные члены public остаются public, унаследованные члены protected остаются protected, а унаследованные члены private остаются недоступными для дочернего класса. Ничего не меняется.

Спецификатор доступа в родительском классе Спецификатор доступа при наследовании типа public в дочернем классе
Public Public
Private Недоступен
Protected Protected

Пример на практике:

Правило: Используйте открытое наследование, если у вас нет веской причины делать иначе.

Наследование типа private

При закрытом наследовании все члены родительского класса наследуются как закрытые. Это означает, что члены private остаются недоступными, а члены protected и public становятся private в дочернем классе.

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

Итого:

Спецификатор доступа в родительском классе Спецификатор доступа при наследовании типа private в дочернем классе
Public Private
Private Недоступен
Protected Private

Закрытое наследование может быть полезно, когда дочерний класс не имеет очевидной связи с родительским классом, но использует его в своей реализации. В таком случае мы не хотим, чтобы открытый интерфейс родительского класса был доступен через объекты дочернего класса (как это было бы, если бы мы использовали открытый тип наследования).

На практике наследование типа private используется редко.

Наследование типа protected

Этот тип наследования почти никогда не используется, за исключением особых случаев. С защищенным наследованием, члены public и protected становятся protected, а члены private остаются недоступными.

Поскольку этот тип наследования очень редко используется, то мы пропустим пример на практике и сразу резюмируем всё в таблице:

Спецификатор доступа в родительском классе Спецификатор доступа при наследовании типа protected в дочернем классе
Public Protected
Private Недоступен
Protected Protected

Финальный пример

Класс Parent может обращаться к своим членам беспрепятственно. Доступ к m_public открыт для всех. Дочерние классы могут обращаться как к m_public, так и к m_protected.

D2 может беспрепятственно обращаться к своим членам. D2 имеет доступ к членам m_public и m_protected класса Parent, но не к m_private. Поскольку D2 наследует класс Parent закрыто, то m_public и m_protected теперь становятся закрытыми при доступе через D2. Это означает, что другие объекты не смогут получить доступ к этим членам через использование объекта D2, а также любые другие классы, которые будут дочерними классу D2 не будут иметь доступ к этим членам.

D3 может беспрепятственно обращаться к своим членам. D3 имеет доступ к членам m_public2 и m_protected2 D2, но не к m_private2. Поскольку D3 наследует D2 открыто, то m_public2 и m_protected2 сохраняют свои спецификаторы доступа и остаются public и protected при доступе через D3. D3 не имеет доступа к m_private класса Parent. Он также не имеет доступа к m_protected или m_public класса Parent, оба из которых стали закрытыми, когда D2 унаследовал их.

Итого

Способ взаимодействия спецификаторов доступа, типов наследования и дочерних классов может вызывать путаницу. Чтобы это устранить, проясним всё еще раз:

  Во-первых, класс всегда имеет доступ к своим собственным, не унаследованным, членам (и дружественные ему классы также имеют доступ). Спецификаторы доступа влияют только на то, могут ли объекты вне класса и дочерние классы обращаться к этим членам.

  Во-вторых, когда дочерние классы наследуют члены родительских классов, то члены родительского класса могут изменять свои спецификаторы доступа в дочернем классе. Это никак не влияет на собственные (не наследуемые) члены дочерних классов (которые определены в дочернем классе и имеют свои собственные спецификаторы доступа). Это влияет только на то, могут ли объекты извне и классы дочерние нашим дочерним классам получить доступ к унаследованным членам родительского класса.

Общая таблица спецификаторов доступа и типов наследования:

Спецификатор доступа в родительском классе Спецификатор доступа при наследовании типа public в дочернем классе Спецификатор доступа при наследовании типа private в дочернем классе Спецификатор доступа при наследовании типа protected в дочернем классе
Public Public Private Protected
Private Недоступен Недоступен Недоступен
Protected Protected Private Protected

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

Оценить статью:

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (8 оценок, среднее: 4,88 из 5)
Загрузка...
Подписаться на обновления:

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

ПОДПИСЫВАЙТЕСЬ

НА КАНАЛ RAVESLI В TELEGRAM

@ravesli

ПОДПИСАТЬСЯ БЕСПЛАТНО