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

  Юрий  | 

  |

  Обновл. 27 Сен 2021  | 

 79596

 ǀ   7 

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

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

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

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

Всё просто.

Примечание: public = «открытый», private = «закрытый», 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 (292 оценок, среднее: 4,92 из 5)
Загрузка...

Комментариев: 7

  1. Ровшан:

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

  2. Наталья:

    Очень редко можно встретить понятное с первого раза объяснение урока. У Вас все понятно и просто, а значит, применимо на практике. Спасибо

  3. concord:

    Отличное объяснение, как и большинство уроков на вашем сайте. Прошу вас продолжайте в таком же стиле. У вас определенно есть методический талант. Всем рекомендую!

    1. Фото аватара Юрий:

      Спасибо, очень приятно 🙂

  4. Dmitro:

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

    1. Фото аватара Юрий:

      Спасибо! Приятно, что Равесли вас не разочаровывает 🙂

  5. kmish:

    Спасибо. Ничего запутанного, все понятно и логически интуитивно.

Добавить комментарий для Dmitro Отменить ответ

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