Почему частное наследование увеличивает вероятность того, что кто-то нарушит мой код по сравнению с композицией?
Автор этой статьи утверждает, что
"Обычно вы не хотите иметь доступ к внутренним компонентам слишком многих других классов, и частное наследование дает вам некоторую дополнительную силу (и ответственность). Но частное наследование не является злом; его просто дороже поддерживать, так как это увеличивает вероятность того, что кто-то изменит что-то, что нарушит ваш код ".
Предположим, что следующий код 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()