Зачем нам на самом деле нужно частное или защищенное наследование в C++?
В C++ я не могу вспомнить случай, в котором я хотел бы наследовать private/protected от базового класса:
class Base;
class Derived1 : private Base;
class Derived2 : protected Base;
Это действительно полезно?
8 ответов
Это полезно, когда вы хотите иметь доступ к некоторым членам базового класса, но не раскрывая их в интерфейсе вашего класса. Частное наследование также можно рассматривать как некую композицию: C++ faq-lite дает следующий пример для иллюстрации этого утверждения.
class Engine {
public:
Engine(int numCylinders);
void start(); // Starts this Engine
};
class Car {
public:
Car() : e_(8) { } // Initializes this Car with 8 cylinders
void start() { e_.start(); } // Start this Car by starting its Engine
private:
Engine e_; // Car has-a Engine
};
Чтобы получить ту же семантику, вы также можете написать класс автомобиля следующим образом:
class Car : private Engine { // Car has-a Engine
public:
Car() : Engine(8) { } // Initializes this Car with 8 cylinders
using Engine::start; // Start this Car by starting its Engine
};
Однако у этого способа есть несколько недостатков:
- ваши намерения гораздо менее ясны
- это может привести к злоупотреблению множественным наследованием
- он нарушает инкапсуляцию класса Engine, так как вы можете получить доступ к его защищенным членам
- вам разрешено переопределять виртуальные методы Engine, чего вы не хотите, если ваша цель - простая композиция
Частный может быть полезен в нескольких случаях. Только один из них является политикой:
Является ли частичная специализация шаблона класса ответом на эту проблему проектирования?,
Другой случай, когда это полезно, это запрет на копирование и присвоение:
struct noncopyable {
private:
noncopyable(noncopyable const&);
noncopyable & operator=(noncopyable const&);
};
class my_noncopyable_type : noncopyable {
// ...
};
Потому что мы не хотим, чтобы у пользователя был указатель типа noncopyable*
к нашему объекту, мы получаем в частном порядке. Это относится не только к некопируемым, но и ко многим другим таким классам (политики наиболее распространены).
Модели публичного наследования IS-A.
Модели непубличного наследования IS-IMPLEMENTED-IN-TERMS-OF.
Модели сдерживания HAS-A, что эквивалентно IS-IMPLEMENTED-IN-TERMS-OF.
Саттер по теме. Он объясняет, когда вы выберете непубличное наследование вместо содержания для деталей реализации.
Например, когда вы хотите повторно использовать реализацию, но не интерфейс класса AND, переопределите его виртуальные функции.
Частное наследство в основном используется по неправильной причине. Люди используют его для "реализовано в условиях", как указывалось в предыдущем ответе, но по моему опыту всегда лучше хранить копию, а не наследовать от класса. Другой более ранний ответ, о CBigArray, является прекрасным примером этого анти-паттерна.
Я понимаю, что могут быть случаи, когда has-a не работает из-за чрезмерного усердного использования "protected", но лучше исправить поврежденный класс, чем сломать новый класс.
Я использовал как частное, так и защищенное наследство в тот или иной момент.
Частное наследование полезно, когда вы хотите, чтобы что-то имело поведение базового класса, а затем иметь возможность переопределить эту функциональность, но вы не хотите, чтобы весь мир знал об этом и использовал его. Вы по-прежнему можете использовать интерфейс частного класса, если функция возвращает этот интерфейс. Это также полезно, когда вы можете зарегистрировать вещи для прослушивания обратных вызовов, поскольку они могут регистрировать себя с помощью частного интерфейса.
Защищенное наследование особенно полезно, когда у вас есть базовый класс, который извлекает полезную функциональность из другого класса, но вы хотите, чтобы только его производные классы могли использовать его.
Частное наследование, скорее всего, будет законной стратегией проектирования, когда вы имеете дело с двумя классами, не связанными is-a, где одному либо требуется доступ к защищенным членам другого, либо необходимо переопределить одну или несколько его виртуальных функций.
Со страницы Scott Meyers Effective C++ 3rd Edition в 191.
Однажды я реализовал эти структуры данных в виде классов:
- Связанный список
- Общий массив (аннотация)
- Простой массив (наследуется от универсального массива)
- Большой массив (наследуется от универсального массива)
Интерфейс большого массива сделает его похожим на массив, однако на самом деле это был связанный список простых массивов фиксированного размера. Итак, я объявил это так:
template <typename T>
class CBigArray : public IArray, private CLnkList {
// ...