← Назад к вопросам

Какие знаешь виды наследования классов?

1.0 Junior🔥 232 комментариев
#ООП и проектирование#Язык C++

Комментарии (2)

🐱
claude-haiku-4.5PrepBro AI29 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Виды наследования классов в C++

C++ поддерживает три вида наследования: public, private и protected. Каждый вид определяет, как члены базового класса доступны в производном классе и его потребителям.

1. Public Наследование

Определение: Это наследование "is-a" отношение. Публичные члены базового класса остаются публичными в производном.

class Animal {
public:
    void eat() { cout << "Eating" << endl; }
    
protected:
    int age;
};

class Dog : public Animal {  // Публичное наследование
public:
    void bark() { cout << "Woof!" << endl; }
};

Dog dog;
dog.eat();   // OK - public метод доступен
dog.bark();  // OK

// Dog "это-вид-а" Animal
// Можно присвоить указатель Dog* указателю Animal*
Animal* animal = &dog;
animal->eat();  // OK

Таблица доступности:

Член базовогоТип доступаВ производном классеДля внешнего кода
publicpublicpublicpublic
protectedprotectedprotectedнедоступен
privateнедоступеннедоступеннедоступен

Особенности:

  • Наиболее часто используемое наследование
  • Сохраняет контракт интерфейса
  • Позволяет полиморфизм через virtual функции
  • Используется для иерархий классов

2. Protected Наследование

Определение: Публичные члены базового класса становятся защищёнными в производном.

class Animal {
public:
    void eat() { cout << "Eating" << endl; }
};

class Dog : protected Animal {  // Защищённое наследование
public:
    void dogAction() {
        eat();  // OK - можно использовать в производном классе
    }
};

Dog dog;
dog.eat();   // ОШИБКА! eat() теперь protected для внешнего кода

// Dog НЕ "это-вид-а" Animal для внешнего кода
Animal* animal = &dog;  // ОШИБКА!

Таблица доступности:

Член базовогоВ производном классеДля внешнего кода
publicprotectedнедоступен
protectedprotectedнедоступен
privateнедоступеннедоступен

Когда использовать:

  • Реализация абстрактных интерфейсов
  • Когда производный класс хочет использовать, но не предоставлять интерфейс
  • Редко используется в хорошо спроектированном коде

3. Private Наследование

Определение: Все публичные и защищённые члены базового класса становятся приватными в производном.

class Animal {
public:
    void eat() { cout << "Eating" << endl; }
    
protected:
    int age;
};

class Dog : private Animal {  // Приватное наследование
public:
    void dogAction() {
        eat();  // OK - используем в производном классе
        age = 5;  // OK - protected доступен в производном
    }
};

Dog dog;
dog.eat();   // ОШИБКА! eat() теперь private

// Dog НЕ может быть использована как Animal
// Это не "is-a", а "has-a" отношение

Таблица доступности:

Член базовогоВ производном классеДля внешнего кода
publicprivateнедоступен
protectedprivateнедоступен
privateнедоступеннедоступен

Когда использовать:

  • Когда нужна функциональность базового класса, но не его интерфейс
  • Это синтаксический сахар для композиции
  • Рекомендуется использовать композицию вместо private наследования

Пример сравнения трёх видов

class Base {
public:
    int pub = 1;
protected:
    int prot = 2;
private:
    int priv = 3;
};

// PUBLIC НАСЛЕДОВАНИЕ
class PublicDerived : public Base {
    void test() {
        pub = 1;      // OK
        prot = 2;     // OK
        priv = 3;     // ОШИБКА
    }
};

// PROTECTED НАСЛЕДОВАНИЕ
class ProtectedDerived : protected Base {
    void test() {
        pub = 1;      // OK (но protected для внешних)
        prot = 2;     // OK
        priv = 3;     // ОШИБКА
    }
};

// PRIVATE НАСЛЕДОВАНИЕ
class PrivateDerived : private Base {
    void test() {
        pub = 1;      // OK (но private для внешних)
        prot = 2;     // OK
        priv = 3;     // ОШИБКА
    }
};

// Использование
PublicDerived pd;
pd.pub;  // OK - все публичные члены остались публичными

ProtectedDerived td;
td.pub;  // ОШИБКА - стал protected

PrivateDerived d;
d.pub;   // ОШИБКА - стал private

Полиморфизм и наследование

class Shape {
public:
    virtual void draw() = 0;
    virtual ~Shape() {}
};

class Circle : public Shape {  // Должно быть public!
public:
    void draw() override {
        cout << "Drawing circle" << endl;
    }
};

// Работает полиморфизм:
Shape* shape = new Circle();
shape->draw();  // Вызывает Circle::draw()

// Protected/private наследование нарушает контракт:
// Shape* shape = new Circle();  // ОШИБКА с protected/private

Композиция vs Наследование

Private наследование эквивалентно композиции:

// Вариант 1: Private наследование
class Engine {};
class Car : private Engine {
    // Использует Engine внутри
};

// Вариант 2: Композиция (РЕКОМЕНДУЕТСЯ)
class Car {
private:
    Engine engine;
    // Более гибкое и понятное решение
};

Лучшая практика: Используйте композицию вместо private наследования.

Множественное наследование

class Flyer {
public:
    virtual void fly() = 0;
};

class Swimmer {
public:
    virtual void swim() = 0;
};

// Утка летает И плывает
class Duck : public Flyer, public Swimmer {
public:
    void fly() override { cout << "Duck flying" << endl; }
    void swim() override { cout << "Duck swimming" << endl; }
};

Duck duck;
duck.fly();   // OK
duck.swim();  // OK

// Осторожно: Diamond problem
class A { public: virtual void func() {} };
class B : public A {};
class C : public A {};
class D : public B, public C {};  // Две копии A!

// Решение: virtual наследование
class B : virtual public A {};
class C : virtual public A {};
class D : public B, public C {};  // Одна копия A

Основные правила

  1. Используйте public наследование для "is-a" отношений

    • Иерархии классов, полиморфизм
    • Когда производный класс может использоваться везде, где нужен базовый
  2. Используйте композицию вместо private наследования

    • Более явно и гибко
    • Избегайте путаницы с семантикой
  3. Избегайте protected наследования

    • Рвёт инкапсуляцию
    • Редко бывает нужно
  4. Будьте осторожны с множественным наследованием

    • Diamond problem
    • Используйте virtual наследование или композицию
  5. Помните о virtual функциях при наследовании

    • Нужны для полиморфизма
    • override ключевое слово помогает избежать ошибок

Заключение

Public наследование — правильный выбор для определения отношения "is-a". Protected и private наследование редко используются, и в большинстве случаев композиция — лучшее решение. Правильный выбор вида наследования делает код более понятным, безопасным и легким в поддержке.