Уровни доступа при наследовании
Часть раздела Объектно-ориентированное программирование путешествия по C++ на Coddy — урок 49 из 104.
Выбранный вами тип наследования влияет на то, как осуществляется доступ к членам базового класса в производном классе. C++ предлагает три варианта: public, protected, и private наследование.
Вот как каждый тип преобразует доступ к членам класса:
| Член базового класса | публичное наследование | защищенное наследование | закрытое наследование |
|---|---|---|---|
| public | public | protected | private |
| protected | protected | protected | private |
| private | недоступен | недоступен | недоступен |
class Base {
public:
int pub;
protected:
int prot;
private:
int priv;
};
class PublicDerived : public Base {
// pub — public, prot — protected, priv недоступен
};
class ProtectedDerived : protected Base {
// pub — protected, prot — protected, priv недоступен
};
class PrivateDerived : private Base {
// pub — private, prot — private, priv недоступен
};Публичное наследование моделирует отношения типа «является» (is-a) и является наиболее распространенным. Dog является Animal, поэтому внешний код может обращаться с ним как с таковым.
Защищенное (protected) и закрытое (private) наследование моделируют отношения «реализовано посредством» (implemented-in-terms-of). Они скрывают интерфейс базового класса от внешнего кода, что полезно, когда вы хотите повторно использовать реализацию, не раскрывая отношения наследования. При закрытом наследовании даже последующие производные классы теряют доступ к членам исходного базового класса.
Задание
ЛегкоДавайте изучим, как различные типы наследования влияют на доступность членов класса, построив простую иерархию устройств. Вы создадите базовый класс Device и три производных класса, каждый из которых использует свой тип наследования, чтобы на практике увидеть, как публичное (public), защищенное (protected) и закрытое (private) наследование изменяют доступ к членам класса.
Вы организуете свой код в двух файлах:
Device.h: Определите базовый классDeviceс тремя членами на разных уровнях доступа:- Публичный (public)
std::string model - Защищенный (protected)
int serialNumber - Закрытый (private)
int internalCode(по умолчанию установлен в 999) - Публичный метод
showModel(), который выводит:Model: <model>
Затем создайте три производных класса, каждый из которых наследуется от
Device, используя разные типы наследования:PublicDevice, использующий публичное наследование (public inheritance) — добавьте методdisplaySerial(), который выводит:Serial: <serialNumber>ProtectedDevice, использующий защищенное наследование (protected inheritance) — добавьте методdisplaySerial(), который выводит:Serial: <serialNumber>PrivateDevice, использующий закрытое наследование (private inheritance) — добавьте методdisplaySerial(), который выводит:Serial: <serialNumber>
Каждый производный класс должен иметь конструктор, который принимает название модели и серийный номер, передавая модель в базовый класс и устанавливая серийный номер.
- Публичный (public)
main.cpp: Считайте два входных значения (каждое на отдельной строке):- Название модели (строка)
- Серийный номер (целое число)
Создайте по одному объекту каждого производного типа с одинаковыми входными значениями. Затем продемонстрируйте различия в доступе:
Для
PublicDevice:- Вызовите
showModel()напрямую у объекта (это работает, так как публичное наследование сохраняет его публичным) - Обратитесь к
modelнапрямую и выведите:Direct model access: <model> - Вызовите
displaySerial()
Для
ProtectedDeviceиPrivateDevice:- Вызовите только
displaySerial()(унаследованныеshowModel()иmodelбольше не доступны публично извне)
Формат вывода:
--- PublicDevice --- Model: <model> Direct model access: <model> Serial: <serial> --- ProtectedDevice --- Serial: <serial> --- PrivateDevice --- Serial: <serial>
Например, при входных данных Laptop и 12345:
--- PublicDevice ---
Model: Laptop
Direct model access: Laptop
Serial: 12345
--- ProtectedDevice ---
Serial: 12345
--- PrivateDevice ---
Serial: 12345Обратите внимание, что только PublicDevice разрешает внешний доступ к унаследованным публичным членам. Другие типы наследования скрывают интерфейс базового класса от внешнего кода, но производные классы все равно могут обращаться к защищенным членам внутри через свои собственные методы.
Шпаргалка
C++ предлагает три типа наследования, которые управляют тем, как члены базового класса доступны в производных классах:
| Член базового класса | public наследование | protected наследование | private наследование |
|---|---|---|---|
| public | public | protected | private |
| protected | protected | protected | private |
| private | недоступен | недоступен | недоступен |
class Base {
public:
int pub;
protected:
int prot;
private:
int priv;
};
class PublicDerived : public Base {
// pub является public, prot является protected, priv недоступен
};
class ProtectedDerived : protected Base {
// pub является protected, prot является protected, priv недоступен
};
class PrivateDerived : private Base {
// pub является private, prot является private, priv недоступен
};Публичное наследование (Public inheritance) моделирует отношение "является" (is-a) и встречается чаще всего. Внешний код может обращаться к публичным членам базового класса через производный класс.
Защищенное и закрытое наследование (Protected and private inheritance) моделируют отношение "реализовано посредством" (implemented-in-terms-of). Они скрывают интерфейс базового класса от внешнего кода. При закрытом наследовании даже последующие производные классы теряют доступ к исходным членам базового класса.
Попробуйте сами
#include <iostream>
#include <string>
#include "Device.h"
using namespace std;
int main() {
// Чтение входных данных
string modelName;
int serialNum;
getline(cin, modelName);
cin >> serialNum;
// TODO: Создайте объекты каждого производного типа с входными значениями
// PublicDevice pubDev(modelName, serialNum);
// ProtectedDevice protDev(modelName, serialNum);
// PrivateDevice privDev(modelName, serialNum);
// TODO: Продемонстрируйте доступ к PublicDevice
cout << "--- PublicDevice ---" << endl;
// - Вызовите showModel() напрямую у объекта
// - Обратитесь к model напрямую и выведите: Direct model access: <model>
// - Вызовите displaySerial()
// TODO: Продемонстрируйте доступ к ProtectedDevice
cout << "--- ProtectedDevice ---" << endl;
// - Вызовите только displaySerial() (showModel и model не являются публично доступными)
// TODO: Продемонстрируйте доступ к PrivateDevice
cout << "--- PrivateDevice ---" << endl;
// - Вызовите только displaySerial() (showModel и model не являются публично доступными)
return 0;
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыСборка и компиляция C++Заголовочные файлы и файлы исходного кодаПространства имен и область видимостиВведение в ООП на C++Классы и объектыУказатель 'this'Методы (функции-члены)Атрибуты (члены данных)Основы конструкторов и деструкторовИтоги — Простой калькулятор4Свойства классов
Члены экземпляра против статическихГеттеры и сеттерыКонстантные функции-членыКлючевое слово mutableСтатические методы и переменныеДружественные функции и классыИтоги — Менеджер банковских счетов7Наследование
Основы наследованияУровни доступа при наследованииПорядок вызова конструкторов и деструкторовПереопределение методовВиртуальные функции и VTableМножественное наследованиеВиртуальное наследованиеИтоги — Иерархия сотрудников10Обзор STL
Обзор и философия STLКонтейнеры STLИтераторыАлгоритмы STLФункторы и лямбда-выраженияИтоги: частота слов13Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonFactory и Abstract FactoryПаттерн BuilderПаттерн ObserverПаттерн Strategy2Управление памятью
Стек и кучаУказатели и ссылкиДинамическая память (new/delete)Умные указатели в C++RAII в C++Итоги — Менеджер динамического массива5Инкапсуляция
Спецификаторы доступа в C++Спецификаторы доступа: подробный разборСокрытие данныхStruct против ClassВложенные и внутренние классыИтоги — Система учета студентов8Полиморфизм
Полиморфизм компиляции и времени выполненияПерегрузка функцийВиртуальные функции: повторениеЧистые виртуальные функцииАбстрактные классыПроектирование интерфейсов на C++Dynamic Casting и RTTIИтоги: Калькулятор фигур11Продвинутые концепции ООП
Композиция против наследованияМиксины через CRTPИдиома PimplСтирание типовEnum Classes и строгая типизацияОбработка исключений в ООППользовательские иерархии исключений14Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикRAII как паттерн3Конструкторы и деструкторы
Конструктор по умолчаниюПараметризованный конструкторКонструктор копированияКонструктор перемещенияСписки инициализации конструктораДелегирующие конструкторыГлубокое погружение в деструкторыПравило трех / пяти / нуляПовторение — класс String6Перегрузка операторов
Введение в перегрузку операторовПерегрузка арифметических операторовПерегрузка операторов сравненияОператоры потоковПерегрузка оператора присваиванияПерегрузка операторов [] и ()Операторы преобразования типовИтоги — класс Matrix9Шаблоны
Шаблоны функцийШаблоны классовСпециализация шаблоновВариативные шаблоныОсновы SFINAE и Type TraitsИтоги — Обобщенный контейнер