Представьте, что преобразование объекта производного класса к базовому доступно внутри самого класса, но запрещено вызывающему коду. Как это объясняется частным наследованием?
При частном наследовании базовый класс становится недоступным как публичный интерфейс производного класса. Поэтому преобразование из производного типа в базовый разрешено членам и друзьям производного класса, но запрещено обычному внешнему коду.
Это ограничение относится к доступности преобразования, а не обязательно к расположению базовой части объекта или к существованию наследования.
C++ различает наследование как средство повторного использования реализации и наследование как отношение публичного подтипа. Публичное наследование выражает отношение «является», тогда как частное наследование позволяет встроить реализацию базового класса, не раскрывая её интерфейс через внешний тип.
Такой подход появился как часть модели управления доступом C++: разработчик может использовать готовую реализацию базового класса, но не обязан объявлять производный класс взаимозаменяемым с ним для клиентов.
Если преобразование к базовому классу случайно доступно внешнему коду, клиент может начать зависеть от деталей реализации. Это усложняет замену базового класса, ограничивает изменение иерархии и может открыть методы, которые не должны быть частью публичного интерфейса.
При частном наследовании внешний вызов, требующий Base* или Base&, обычно не скомпилируется. Однако внутри производного класса такое преобразование допустимо, поэтому сам производный класс может использовать базовую часть объекта.
Доступность преобразования определяется спецификатором наследования:
public — преобразование к базовому классу доступно везде, где доступны сами типы;protected — преобразование доступно производному классу, его потомкам и друзьям;private — преобразование доступно членам и друзьям непосредственно производного класса.При частном наследовании публичные и защищённые члены базового класса получают в производном классе частный уровень доступа. Частные члены базового класса при этом не становятся доступными: они по-прежнему доступны только самому базовому классу и его друзьям.
В примере Engine может использовать Base и преобразовывать this к Base*, но функция use не имеет права выполнять такое преобразование. Это проверяется на этапе компиляции по контексту доступа.
Ограничение не является механизмом сокрытия объекта в памяти и не запрещает существование базовой подобъектной части. Если преобразование разрешено внутри Engine, указатель может быть скорректирован, например при множественном наследовании, но сам факт частного наследования не означает отдельного объекта или копирования.
Частное наследование следует отличать от композиции. Оно сохраняет доступ к защищённым членам и виртуальным функциям базового класса, позволяет переопределять виртуальные методы и участвовать в механизмах наследования. Композиция лучше выражает отношение «имеет» и обычно сильнее скрывает детали реализации.
Допустим, класс ConnectionPool использует реализацию очереди Queue, но клиенты пула не должны передавать ConnectionPool туда, где ожидается произвольная Queue.
Вариант с публичным наследованием прост, но раскрывает отношение подтипа: любой клиент получает возможность рассматривать пул как очередь. Это создаёт риск нарушения инвариантов пула, например прямого добавления элементов в очередь.
Композиция с полем Queue лучше скрывает реализацию, но может потребовать явной маршрутизации большого числа операций. Частное наследование сохраняет доступ к реализации и защищённым точкам расширения, одновременно запрещая внешнее преобразование к Queue.
Если базовый класс специально предназначен только для реализации, выбранное частное наследование оправдано. Если же клиентам действительно нужна взаимозаменяемость с базовым типом, следует использовать публичное наследование и явно принять соответствующий контракт.
Нет, одно лишь явное приведение не отменяет проверку доступа. Преобразование остаётся недоступным в контексте, где частная базовая часть недоступна. Выполнять его можно внутри разрешённого контекста, например в члене или друге производного класса; обходить ограничение через небезопасные приведения нельзя.
Нет. После частного наследования публичные и защищённые члены базового класса доступны членам производного класса, но их уровень доступа при наследовании становится частным для пользователей производного типа. Поэтому Engine может вызвать унаследованный публичный метод, а внешний код — нет, если метод не был явно повторно открыт через using.
Нет. Если вызов виртуальной функции выполняется из контекста, где доступна базовая часть, выбор реализации по динамическому типу сохраняется. Частное наследование ограничивает доступ к базовому интерфейсу и преобразованию, но не превращает виртуальную функцию в невиртуальную и не отменяет переопределение.