Почему частное наследование увеличивает вероятность того, что кто-то нарушит мой код по сравнению с композицией?

Автор этой статьи утверждает, что

"Обычно вы не хотите иметь доступ к внутренним компонентам слишком многих других классов, и частное наследование дает вам некоторую дополнительную силу (и ответственность). Но частное наследование не является злом; его просто дороже поддерживать, так как это увеличивает вероятность того, что кто-то изменит что-то, что нарушит ваш код ".

Предположим, что следующий код Car наследуется от Engine в частном порядке:

#include <iostream>
using namespace std;

class Engine
{
    int numCylinders;
    public:
    class exception{};
    Engine(int i) { if( i < 4 ) throw exception(); numCylinders = i; }
    void start() { cout << "Engine started " << numCylinders << " cylinders" << endl; }
};

class Car : private Engine          
{    
    public:
    Car(int i) : Engine(i) {}
    using Engine::start;
};

int main()
{
    try
    {
        Car c(4);
        c.start();
    }
    catch( Engine::exception& )
    {
        cout << "A Car cannot have less than 4 cylinders" << endl;
    }
}

Мой вопрос: как Car может сломать этот код, настроив, например, его Engine с менее чем 4 цилиндрами, используя личное наследование и без защищенных членов в базовом классе?

4 ответа

Решение

Я думаю, что проблема не в том, что Car может взломать ваш код двигателя, а в том, что, изменив Engine, кто-то может взломать ваш код автомобиля. Наследование представляет собой гораздо более тесную связь, чем состав, поэтому изменения в Engine с большей вероятностью нарушат класс, который наследует от него, а не содержит его. В случае C++ более слабая связь достигается тем, что Car содержит указатель Engine или умный указатель.

Один способ, которым наследование вводит гораздо более тесную связь, чем членство, состоит в том, что пространство имен производного и базового класса смешано. Таким образом, значение имени в контексте производного класса зависит от того, какие имена вводит базовый класс, и существуют обычные эффекты переопределения / скрытия. Изменения в базовом классе могут повлиять на код в производном классе, который не обязательно может быть четко определен или который немедленно приведет к полезной диагностике. Напротив, если интерфейс объекта-члена изменяется, то больше всего может сломаться код, который на самом деле упоминает объект-член.

Я не вижу, чтобы Car мог установить Engine::numCylinders (по крайней мере, не без грязных трюков, таких как доступ к сырой памяти). В примере в статье используется защищенный метод, вы используете закрытого члена.

Кстати: статья начинается с "Используйте композицию, когда можете" - и у автомобиля есть двигатель, но это не двигатель. Когда A выводится из B, то это обычно выражает A является B.

Автор статьи, в предыдущем пункте, ссылается на некоторые "недостатки" использования частного наследования здесь:

  • Вариант простой композиции необходим, если вы хотите содержать несколько двигателей на автомобиль
  • Вариант частного наследования может вводить ненужное множественное наследование
  • Вариант частного наследования позволяет членам Car преобразовать автомобиль * в двигатель *
  • Вариант частного наследования обеспечивает доступ к защищенным членам базового класса.
  • Вариант частного наследования позволяет Car переопределять виртуальные функции Engine
  • Вариант частного наследования делает несколько проще (20 символов по сравнению с 28 символами) дать Car метод start(), который просто вызывает метод Engine () start()
Другие вопросы по тегам