Наследование. Закрытое/частное (private) и защищенное (protected) наследование / FAQ C++

Добавлено 21 декабря 2020 в 05:27

Как выражается «закрытое/частное (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)
};

Теги

C++ / CppFAQprivateprotectedВысокоуровневые языки программированияНаследованиеЯзыки программирования

На сайте работает сервис комментирования DISQUS, который позволяет вам оставлять комментарии на множестве сайтов, имея лишь один аккаунт на Disqus.com.

В случае комментирования в качестве гостя (без регистрации на disqus.com) для публикации комментария требуется время на премодерацию.