Наследование. Закрытое/частное (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)
};
