Множественное наследование
Часть раздела Объектно-ориентированное программирование путешествия по C++ на Coddy — урок 53 из 104.
C++ позволяет классу наследоваться от нескольких базовых классов одновременно. Это называется множественным наследованием и позволяет комбинировать возможности из различных иерархий классов.
Чтобы наследоваться от нескольких классов, перечислите их через запятую:
class Printer {
public:
void print() {
std::cout << "Printing document" << std::endl;
}
};
class Scanner {
public:
void scan() {
std::cout << "Scanning document" << std::endl;
}
};
class MultiFunctionDevice : public Printer, public Scanner {
public:
void copy() {
scan();
print();
}
};Класс MultiFunctionDevice теперь имеет доступ к методам как из Printer, так и из Scanner:
MultiFunctionDevice mfd;
mfd.print(); // Из Printer
mfd.scan(); // Из Scanner
mfd.copy(); // Его собственный методКонструкторы вызываются в том порядке, в котором базовые классы перечислены в объявлении наследования (слева направо), а деструкторы — в обратном порядке. Вы можете передавать аргументы каждому базовому конструктору в списке инициализации:
class MultiFunctionDevice : public Printer, public Scanner {
public:
MultiFunctionDevice() : Printer(), Scanner() {}
};Потенциальная проблема возникает, когда оба базовых класса имеют член с одинаковым именем. Это создает неоднозначность, и вы должны использовать оператор разрешения области видимости, чтобы указать, какой из них вы имеете в виду:
class A { public: void show() {} };
class B { public: void show() {} };
class C : public A, public B {};
C obj;
obj.A::show(); // Вызов версии класса A
obj.B::show(); // Вызов версии класса BМножественное наследование — это мощный инструмент, но оно может привести к сложным ситуациям, особенно когда базовые классы имеют общего предка. Мы изучим эту проблему в следующем уроке.
Задание
ЛегкоДавайте создадим систему устройств для умного дома, которая объединяет возможности нескольких базовых классов с использованием множественного наследования. Вы создадите устройства, которые могут как подключаться к WiFi, так и управляться голосом, а затем объедините эти функции в единую умную колонку.
Вы организуете свой код в трех файлах:
WiFiDevice.h: Определите классWiFiDevice, который представляет любое устройство, способное подключаться к сети:- Защищенный (protected) член
std::string networkName - Конструктор, который принимает имя сети и сохраняет его
- Публичный метод
connect(), который выводит:Connecting to WiFi: <networkName> - Публичный метод
status(), который выводит:WiFi Status: Connected
- Защищенный (protected) член
VoiceControl.h: Определите классVoiceControl, который представляет любое устройство с голосовыми возможностями:- Защищенный (protected) член
std::string assistantName - Конструктор, который принимает имя ассистента и сохраняет его
- Публичный метод
listen(), который выводит:<assistantName> is listening... - Публичный метод
status(), который выводит:Voice Status: Active
- Защищенный (protected) член
main.cpp: Считайте три входных значения (каждое на отдельной строке):- Имя устройства (строка)
- Имя WiFi сети (строка)
- Имя голосового ассистента (строка)
Определите класс
SmartSpeaker, который публично наследуется как отWiFiDevice, так и отVoiceControl:- Приватный член
std::string deviceName - Конструктор, который принимает все три параметра, передает имя сети в
WiFiDeviceи имя ассистента вVoiceControl, используя список инициализации, и сохраняет имя устройства - Метод
powerOn(), который выводит:<deviceName> powering on..., затем вызываетconnect()иlisten() - Метод
showAllStatus(), который выводит имя устройства, а затем использует оператор разрешения области видимости для вызова обоих методовstatus(), чтобы разрешить неоднозначность
Создайте объект
SmartSpeakerс входными значениями. ВызовитеpowerOn(), выведите пустую строку, а затем вызовитеshowAllStatus().
Например, при входных данных Echo, HomeNetwork и Alexa:
Echo powering on...
Connecting to WiFi: HomeNetwork
Alexa is listening...
Echo Status:
WiFi Status: Connected
Voice Status: ActiveМетод showAllStatus() должен вывести имя устройства, за которым следует " Status:" на одной строке, а затем вызвать метод status() каждого базового класса, используя WiFiDevice::status() и VoiceControl::status(), чтобы избежать неоднозначности, так как оба базовых класса имеют метод с одинаковым именем.
Шпаргалка
C++ поддерживает множественное наследование, позволяя классу наследоваться от нескольких базовых классов одновременно:
class DerivedClass : public BaseClass1, public BaseClass2 {
// Тело класса
};Производный класс имеет доступ к членам всех базовых классов:
class Printer {
public:
void print() {
std::cout << "Printing document" << std::endl;
}
};
class Scanner {
public:
void scan() {
std::cout << "Scanning document" << std::endl;
}
};
class MultiFunctionDevice : public Printer, public Scanner {
public:
void copy() {
scan(); // Из Scanner
print(); // Из Printer
}
};Конструкторы базовых классов вызываются в том порядке, в котором они указаны в списке наследования (слева направо). Используйте список инициализации для передачи аргументов каждому базовому конструктору:
class MultiFunctionDevice : public Printer, public Scanner {
public:
MultiFunctionDevice() : Printer(), Scanner() {}
};Когда несколько базовых классов имеют члены с одинаковыми именами, используйте оператор разрешения области видимости для устранения неоднозначности:
class A { public: void show() {} };
class B { public: void show() {} };
class C : public A, public B {};
C obj;
obj.A::show(); // Вызов версии класса A
obj.B::show(); // Вызов версии класса BПопробуйте сами
#include <iostream>
#include <string>
#include "WiFiDevice.h"
#include "VoiceControl.h"
using namespace std;
// TODO: Определите класс SmartSpeaker, который публично наследуется от WiFiDevice и VoiceControl
// - Приватное поле: deviceName (string)
// - Конструктор: принимает deviceName, networkName, assistantName
// Используйте список инициализации для передачи networkName в WiFiDevice и assistantName в VoiceControl
// - Метод powerOn(): выводит "<deviceName> powering on...", затем вызывает connect() и listen()
// - Метод showAllStatus(): выводит "<deviceName> Status:", затем вызывает оба метода status()
// используя разрешение области видимости (WiFiDevice::status() и VoiceControl::status())
class SmartSpeaker {
// TODO: Реализуйте класс SmartSpeaker здесь
};
int main() {
string deviceName, networkName, assistantName;
getline(cin, deviceName);
getline(cin, networkName);
getline(cin, assistantName);
// TODO: Создайте объект SmartSpeaker с входными значениями
// TODO: Вызовите powerOn()
// TODO: Выведите пустую строку
// TODO: Вызовите showAllStatus()
return 0;
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыСборка и компиляция C++Заголовочные файлы и файлы исходного кодаПространства имен и область видимостиВведение в ООП на C++Классы и объектыУказатель 'this'Методы (функции-члены)Атрибуты (члены данных)Основы конструкторов и деструкторовИтоги — Простой калькулятор4Свойства классов
Члены экземпляра против статическихГеттеры и сеттерыКонстантные функции-членыКлючевое слово mutableСтатические методы и переменныеДружественные функции и классыИтоги — Менеджер банковских счетов7Наследование
Основы наследованияУровни доступа при наследованииПорядок вызова конструкторов и деструкторовПереопределение методовВиртуальные функции и VTableМножественное наследованиеВиртуальное наследованиеИтоги — Иерархия сотрудников10Обзор STL
Обзор и философия STLКонтейнеры STLИтераторыАлгоритмы STLФункторы и лямбда-выраженияИтоги: частота слов13Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonFactory и Abstract FactoryПаттерн BuilderПаттерн ObserverПаттерн Strategy2Управление памятью
Стек и кучаУказатели и ссылкиДинамическая память (new/delete)Умные указатели в C++RAII в C++Итоги — Менеджер динамического массива5Инкапсуляция
Спецификаторы доступа в C++Спецификаторы доступа: подробный разборСокрытие данныхStruct против ClassВложенные и внутренние классыИтоги — Система учета студентов8Полиморфизм
Полиморфизм компиляции и времени выполненияПерегрузка функцийВиртуальные функции: повторениеЧистые виртуальные функцииАбстрактные классыПроектирование интерфейсов на C++Dynamic Casting и RTTIИтоги: Калькулятор фигур11Продвинутые концепции ООП
Композиция против наследованияМиксины через CRTPИдиома PimplСтирание типовEnum Classes и строгая типизацияОбработка исключений в ООППользовательские иерархии исключений14Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикRAII как паттерн3Конструкторы и деструкторы
Конструктор по умолчаниюПараметризованный конструкторКонструктор копированияКонструктор перемещенияСписки инициализации конструктораДелегирующие конструкторыГлубокое погружение в деструкторыПравило трех / пяти / нуляПовторение — класс String6Перегрузка операторов
Введение в перегрузку операторовПерегрузка арифметических операторовПерегрузка операторов сравненияОператоры потоковПерегрузка оператора присваиванияПерегрузка операторов [] и ()Операторы преобразования типовИтоги — класс Matrix9Шаблоны
Шаблоны функцийШаблоны классовСпециализация шаблоновВариативные шаблоныОсновы SFINAE и Type TraitsИтоги — Обобщенный контейнер