Перейти к основному содержимому

Наследование классов

Общий интерфейс связывает датчик и привод робота

Этот урок сложнее предыдущих. Наследование нужно не для экономии нескольких строк, а для описания отношения «один тип является разновидностью другого типа».

Коротко

Базовый класс описывает общую часть. Производный класс получает эту общую часть и добавляет своё поведение. При public-наследовании производный объект можно использовать как объект базового типа.

Простая идея

Если разные устройства можно обслуживать одной командой update, можно задать общий базовый класс.

#include <iostream>
using namespace std;

class Module {
public:
void reset() {
cout << "reset" << endl;
}
};

class Sensor : public Module {
public:
void read() {
cout << "read" << endl;
}
};

int main() {
Sensor s;
s.reset();
s.read();
return 0;
}

Sensor является разновидностью Module, поэтому объект s получил метод reset.

public-наследование

class Sensor : public Module {
};

Эта запись означает: Sensor открыто наследует Module. Проверочная фраза: «датчик является модулем». Если фраза звучит странно, наследование не подходит.

СвязьЧто выбрать
датчик является модулемнаследование возможно
робот содержит моторполе класса
мотор содержит мощностьполе класса
отчёт содержит числамассив или вектор

virtual и override

Иногда общий метод должен работать по-разному в разных производных классах.

#include <iostream>
using namespace std;

class Module {
public:
virtual void update() {
cout << "module" << endl;
}
};

class Sensor : public Module {
public:
void update() override {
cout << "sensor" << endl;
}
};

class Motor : public Module {
public:
void update() override {
cout << "motor" << endl;
}
};

void service(Module* m) {
m->update();
}

int main() {
Sensor s;
Motor m;

service(&s);
service(&m);
return 0;
}

Вывод:

sensor
motor

virtual разрешает выбрать метод по настоящему типу объекта. override просит компилятор проверить, что производный класс действительно заменяет метод базы.

Базовый класс и две производные реализации общего действия

Виртуальный деструктор

Если базовый класс используют через указатели, обычно добавляют виртуальный деструктор:

class Module {
public:
virtual ~Module() {
}

virtual void update() {
}
};

Это нужно для безопасного удаления производного объекта через указатель на базовый класс. В этом уроке мы не создаём такие объекты через new, но правило важно запомнить для будущих проектов.

Когда не нужно наследование

Плохая модель:

class Robot : public Motor {
};

Робот не является мотором. Робот содержит мотор. Лучше так:

class Robot {
private:
Motor left;
Motor right;
};

Если связь описывается словом «содержит», используйте поле, а не наследование.

Практика

  1. Создайте базовый класс Module с методом reset.
  2. Создайте производный класс Sensor.
  3. Проверьте, что объект Sensor может вызвать reset.
  4. Добавьте виртуальный метод update.
  5. Создайте два производных класса с разным update.
  6. Для пар «робот — мотор» и «датчик — модуль» выберите наследование или поле.

Проверьте себя

  1. Что такое базовый класс?
  2. Что такое производный класс?
  3. Как читается class Sensor : public Module?
  4. Что делает virtual?
  5. Зачем нужен override?
  6. Почему робот обычно содержит мотор, а не наследуется от него?
Ориентиры для самопроверки

Сначала ответьте без подсказки. Ответ можно считать полным, если вы:

  • формулируете основную мысль своими словами;
  • называете важные условия, ограничения или меры безопасности;
  • для схемы, кода или расчёта показываете ход решения и ожидаемый результат.

Если один из пунктов объяснить не получается, найдите соответствующую главу статьи, перечитайте её и повторите ответ.

Словарь статьи

ТерминЗначение
наследованиесоздание класса на основе другого класса
базовый классобщий класс
производный класскласс-наследник
public-наследованиеоткрытая связь «является»
virtualвыбор метода по настоящему типу объекта
overrideпроверка переопределения метода
композицияобъект содержит другие объекты как поля

Связанные темы

Источники