Зачем нам на самом деле нужно Private или Protected inheritance в С++?

В С++ я не могу придумать случай, в котором я хотел бы наследовать private/protected из базовый класс:

class Base;
class Derived1 : private Base;
class Derived2 : protected Base;

Действительно ли это полезно?

Ответ 1

Это полезно, если вы хотите иметь доступ к некоторым членам базового класса, но не подвергая их в своем интерфейсе класса. Частное наследование также можно рассматривать как своего рода композицию: С++ 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, что вам не нужно, если ваша цель - простая композиция.

Ответ 2

Частный может быть полезен в нескольких обстоятельствах. Только одна из них - политика:

Является ли частная спецификация шаблона шаблона ответом на эту проблему дизайна?.

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

struct noncopyable {
    private:
    noncopyable(noncopyable const&);
    noncopyable & operator=(noncopyable const&);
};

class my_noncopyable_type : noncopyable {
    // ...
};

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

Ответ 3

Публичные модели наследования IS-A.
Непубличные модели наследования IS-IMPLEMENTED-IN-TERMS-OF.
Модели сдерживания HAS-A, что эквивалентно IS-IMPLEMENTED-IN-TERMS-OF.

Sutter по теме. Он объясняет, когда вы выбираете непубличное наследование поверх сдерживания для деталей реализации.

Ответ 4

Частное наследование в основном используется по неправильной причине. Люди используют его для IS-IMPLEMENTED-IN-TERMS-OF, как указано в более раннем ответе, но по моему опыту всегда более чисто, чтобы сохранить копию, а не наследовать от класса. Другой более ранний ответ, один о CBigArray, является прекрасным примером этого анти-шаблона.

Я понимаю, что могут быть случаи, когда has-a не работает из-за чрезмерного усердного использования "защищенного", но лучше исправить сломанный класс, чем сломать новый класс.

Ответ 5

Например, если вы хотите повторно использовать реализацию, но не интерфейс класса И переопределить его виртуальные функции.

Ответ 6

Я использовал как частную, так и защищенную наследование в какой-то момент.

Частное наследование полезно, когда вы хотите, чтобы что-то имело поведение базового класса, а затем можно переопределить эту функциональность, но вы не хотите, чтобы весь мир знал об этом и использовал его. Вы по-прежнему можете использовать интерфейс частного класса, если функция возвращает этот интерфейс. Это также полезно, когда вы можете заставить вещи регистрироваться для прослушивания обратных вызовов, поскольку они могут регистрироваться с использованием частного интерфейса.

Защищенное наследование особенно полезно, когда у вас есть базовый класс, который извлекает полезную функциональность из другого класса, но вы хотите, чтобы его производные классы могли его использовать.

Ответ 7

Я однажды применил эти структуры данных как классы:

  • Связанный список
  • Общий массив (аннотация)
  • Простой массив (наследуется от общего массива)
  • Большой массив (наследуется от общего массива)

Интерфейс большого массива сделает его похожим на массив, однако на самом деле это был связанный список простых массивов фиксированного размера. Поэтому я объявил следующее:

template <typename T>
class CBigArray : public IArray, private CLnkList {
    // ...