Наследование. Закрытое/частное (private) и защищенное (protected) наследование / FAQ C++
Как выражается «закрытое/частное (private
) наследование»?
В этом случае используется : private
вместо : public
. Например,
class Foo : private Bar {
public:
// ...
};
Чем похожи «закрытое/частное наследование» и «композиция»?
Закрытое наследование – это синтаксический вариант композиции (она же агрегация и/или связь типа «имеет»).
Например, связь «Car
(автомобиль) имеет Engine
(двигатель)» можно выразить с помощью простой композиции:
class Engine {
public:
Engine(int numCylinders);
void start(); // запускает этот "двигатель"
};
class Car {
public:
Car() : e_(8) { } // Инициализирует автомобиль (Car) с 8 цилиндрами
void start() { e_.start(); } // Запускает автомобиль (Car), запуская его двигатель (Engine)
private:
Engine e_; // Автомобиль (Car) имеет двигатель (Engine)
};
Связь «Car
имеет Engine
» также можно выразить с помощью закрытого (частного) наследования:
class Car : private Engine { // Автомобиль (Car) имеет двигатель (Engine)
public:
Car() : Engine(8) { } // Инициализирует автомобиль (Car) с 8 цилиндрами
using Engine::start; // Запускает автомобиль (Car), запуская его двигатель (Engine)
};
Между этими двумя вариантами есть несколько общих черт:
- в обоих случаях в каждом объекте
Car
содержится ровно один объект-членEngine
; - ни в том, ни в другом случае пользователи (посторонние) не могут преобразовать
Car*
вEngine*
; - в обоих случаях класс
Car
имеет методstart()
, который вызывает методstart()
содержащегося в нем объектаEngine
.
Также есть несколько отличий:
- вариант простой композиции нужен, если вы хотите иметь в
Car
несколько объектовEngine
; - вариант с частным наследованием может ввести ненужное множественное наследование;
- вариант с частным наследованием позволяет членам
Car
преобразовыватьCar*
вEngine*
; - вариант с частным наследованием позволяет получить доступ к защищенным членам базового класса;
- вариант с частным наследованием позволяет
Car
переопределять виртуальные функцииEngine
; - вариант с частным наследованием немного упрощает (20 символов по сравнению с 28 символами) предоставление
Car
методаstart()
, который просто вызывает методstart()
классаEngine
.
Обратите внимание, что частное наследование обычно используется для получения доступа к защищенным членам базового класса, но обычно это краткосрочное решение (перевод: пластырь).
Что предпочесть: композицию или частное наследование?
Используйте композицию, когда можете, а частное наследование, когда вам нужно.
Обычно вы не хотите иметь доступ к внутренним компонентам слишком многих других классов, а частное наследование дает вам некоторые из этих дополнительных полномочий (и ответственностей). Но частное наследование – это не зло; просто его дороже поддерживать, так как оно увеличивает вероятность того, что кто-то изменит что-то, что нарушит ваш код.
Легальное, долгосрочное использование частного наследования – это когда вы хотите создать класс Fred
, который использует код в классе Wilma
, а код из класса Wilma
должен вызывать функции-члены из вашего нового класса Fred
. В этом случае Fred
вызывает невиртуальные функции в Wilma
, а Wilma
вызывает в себе (обычно чисто) виртуальные функции, которые перегружены классом Fred
. Сделать это с помощью композиции будет намного сложнее.
class Wilma {
protected:
void fredCallsWilma()
{
std::cout << "Wilma::fredCallsWilma()\n";
wilmaCallsFred();
}
virtual void wilmaCallsFred() = 0; // чисто виртуальная функция
};
class Fred : private Wilma {
public:
void barney()
{
std::cout << "Fred::barney()\n";
Wilma::fredCallsWilma();
}
protected:
virtual void wilmaCallsFred()
{
std::cout << "Fred::wilmaCallsFred()\n";
}
};
Должен ли я приводить указатель из производного класса с частным наследованием к его базовому классу?
Как правило, нет.
Об отношении к базовому классу из функции-члена или друга производного класса с частным наследованием известно, и восходящее преобразование из PrivatelyDer*
в Base*
(или PrivatelyDer&
в Base&
) безопасно; приведение типов не требуется и не рекомендуется.
Однако пользователям PrivatelyDer
следует избегать этого небезопасного преобразования, поскольку оно основано на закрытом/частном решении PrivatelyDer
и может быть изменено без предварительного уведомления.
Как защищенное (protected
) наследование связано с частным (private
) наследованием?
Сходства: оба позволяют переопределять виртуальные функции в частном/защищенном базовом классе, и ни одно из них не утверждает, что производный класс является разновидностью базового класса.
Различия: защищенное наследование позволяет производным классам производных классов знать о наследственной связи. Таким образом, ваши внуки действительно узнают о деталях вашей реализации. Это имеет как преимущества (позволяет производным классам защищенного производного класса использовать связь с защищенным базовым классом), так и затраты (защищенный производный класс не может изменить связи, не нарушив при этом последующие производные классы).
Защищенное наследование использует синтаксис : protected
.
class Car : protected Engine {
public:
// ...
};
Каковы правила доступа при частном и защищенном наследованиях?
Возьмем в качестве примера следующие классы:
class B { /*...*/ };
class D_priv : private B { /*...*/ };
class D_prot : protected B { /*...*/ };
class D_publ : public B { /*...*/ };
class UserClass { B b; /*...*/ };
Ни один из производных классов не может получить доступ к чему-либо, что является private
в B
. В D_priv
открытая и защищенная части B
являются частными. В D_prot
открытая и защищенная части B
являются защищенными. В D_publ
открытые части B
являются открытыми, а защищенные части B
– защищенными (D_publ
– это разновидность B
). Класс UserClass
может получить доступ только к открытым частям B
, что «изолирует» UserClass
от B
.
Чтобы сделать публичный член B
публичным в D_priv
или D_prot
, укажите имя члена с префиксом B::
. Например, чтобы сделать член B::f(int, float)
открытым в D_prot
, вы должны сказать:
class D_prot : protected B {
public:
using B::f; // Примечание: не using B::f(int,float)
};