Почему нам действительно нужно частное или защищенное наследование в C++?


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

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

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

7 52

7 ответов:

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

чтобы получить ту же семантику, вы также можете написать класс car следующим образом:

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, так как вы можете получить доступ к его защищенным членам
  • вам разрешено переопределять виртуальные методы двигателя, что вам не нужно, если ваша цель-простая композиция

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

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

еще один случай, когда полезно запретить копирование и назначение:

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

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

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

публичные модели наследования-A.
Непубличные модели наследования реализуются в терминах.
Модели сдерживания HAS-A, что эквивалентно is-IMPLEMENTED-in-TERMS-OF.

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

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

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

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

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

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

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

Я когда-то реализовал эти структуры данных в виде классов:

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

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

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