Виртуальное наследование
Часть раздела Объектно-ориентированное программирование путешествия по C++ на Coddy. Урок 54 из 104.
Проблема ромба возникает, когда класс наследует от двух классов, у которых есть общий базовый класс. Без специальной обработки производный класс в итоге получает две копии общего базового класса, что приводит к неоднозначности и напрасному расходу памяти.
class Animal {
public:
int age;
};
class Mammal : public Animal {};
class Bird : public Animal {};
class Bat : public Mammal, public Bird {};
Bat b;
b.age = 5; // Ошибка: неоднозначно — какой 'age'?Класс Bat содержит два отдельных подобъекта Animal: один через Mammal, а другой через Bird. Это создаёт ромбовидную диаграмму наследования, которая и дала задаче её название.
Виртуальное наследование решает эту проблему, гарантируя наличие только одной копии общего базового класса. Добавьте ключевое слово virtual при наследовании от общего базового класса:
class Animal {
public:
int age;
Animal(int a = 0) : age(a) {}
};
class Mammal : virtual public Animal {
public:
Mammal(int a = 0) : Animal(a) {}
};
class Bird : virtual public Animal {
public:
Bird(int a = 0) : Animal(a) {}
};
class Bat : public Mammal, public Bird {
public:
Bat(int a) : Animal(a), Mammal(a), Bird(a) {}
};
Bat b(5);
b.age = 10; // Работает! Существует только один 'age'Обратите внимание, что Bat должен напрямую инициализировать Animal в своём constructor. При виртуальном наследовании наиболее производный класс отвечает за конструирование виртуального базового класса, независимо от промежуточных классов.
Задание
ЛегкоДавайте создадим систему управления сотрудниками, демонстрирующую, как виртуальное наследование решает проблему ромба. Вы создадите иерархию, в которой TeamLead наследуется от Developer и Manager, которые оба используют общий базовый класс Employee.
Вы распределите код по четырём файлам:
Employee.h: Определите общий базовый классEmployeeсо следующими элементами:- Защищённые
std::string nameиint id - Конструктор, принимающий оба значения и выводящий:
Employee [<name>] hired with ID <id> - Публичный метод
getInfo(), выводящий:Employee: <name> (ID: <id>) - Виртуальный деструктор, выводящий:
Employee [<name>] record closed
- Защищённые
Developer.h: Определите классDeveloper, использующий virtual public inheritance отEmployee:- Защищённый член
std::string language - Конструктор, принимающий name, id и language: передаёт name и id в
Employee, сохраняет language и выводит:Developer [<name>] specializes in <language> - Публичный метод
code(), выводящий:<name> is coding in <language> - Деструктор, выводящий:
Developer [<name>] signed off
- Защищённый член
Manager.h: Определите классManager, использующий virtual public inheritance отEmployee:- Защищённый член
int teamSize - Конструктор, принимающий name, id и размер команды: передаёт name и id в
Employee, сохраняет размер команды и выводит:Manager [<name>] leads a team of <teamSize> - Публичный метод
manage(), выводящий:<name> is managing <teamSize> people - Деструктор, выводящий:
Manager [<name>] stepped down
- Защищённый член
main.cpp: Считайте четыре входных значения (каждое в отдельной строке):- Имя (строка)
- Идентификатор сотрудника (целое число)
- Язык программирования (строка)
- Размер команды (целое число)
Определите класс
TeamLead, который публично наследуется как отDeveloper, так и отManager:- Конструктор, принимающий все четыре параметра и непосредственно инициализирующий
Employee(виртуальный базовый класс), а затемDeveloperиManager - Конструктор должен выводить:
TeamLead [<name>] ready to lead and code! - Метод
showRole(), вызывающийgetInfo(),code()иmanage()в указанном порядке - Деструктор, выводящий:
TeamLead [<name>] promoted out
Создайте объект
TeamLeadвнутри области видимости блока, вызовитеshowRole(), а затем позвольте объекту выйти из области видимости. После блока выведите:Organization restructured!
Например, при входных данных Alice, 101, C++ и 5:
Employee [Alice] hired with ID 101
Developer [Alice] specializes in C++
Manager [Alice] leads a team of 5
TeamLead [Alice] ready to lead and code!
Employee: Alice (ID: 101)
Alice is coding in C++
Alice is managing 5 people
TeamLead [Alice] promoted out
Manager [Alice] stepped down
Developer [Alice] signed off
Employee [Alice] record closed
Organization restructured!Обратите внимание: вызываются только один конструктор Employee и один деструктор Employee: виртуальное наследование гарантирует существование только одной копии общего базового класса. TeamLead должен непосредственно инициализировать Employee, поскольку при виртуальном наследовании наиболее производный класс отвечает за создание виртуального базового класса.
Попробуйте сами
#include <iostream>
#include <string>
#include "Developer.h"
#include "Manager.h"
using namespace std;
// TODO: Определите класс TeamLead, который публично наследуется от Developer и Manager
// Помните: при виртуальном наследовании TeamLead должен напрямую инициализировать Employee (виртуальный базовый класс)
class TeamLead : public Developer, public Manager {
public:
// TODO: Реализуйте конструктор, который принимает name, id, language и teamSize
// Необходимо инициализировать: сначала Employee (виртуальный базовый класс), затем Developer, затем Manager
// Should print: TeamLead [<name>] ready to lead and code!
TeamLead(const std::string& name, int id, const std::string& language, int teamSize)
: Employee(name, id),
Developer(name, id, language),
Manager(name, id, teamSize) {
// TODO: Выведите сообщение конструктора
}
// TODO: Реализуйте метод showRole()
// Должен вызывать getInfo(), code() и manage() в этом порядке
void showRole() {
// TODO: Вызовите три метода
}
// TODO: Реализуйте деструктор
// Should print: TeamLead [<name>] promoted out
~TeamLead() {
// TODO: Выведите сообщение деструктора
}
};
int main() {
// Считайте входные данные
string name;
int id;
string language;
int teamSize;
getline(cin, name);
cin >> id;
cin.ignore();
getline(cin, language);
cin >> teamSize;
// TODO: Создайте объект TeamLead внутри блочной области видимости
// Вызовите showRole(), затем дайте ему выйти из области видимости
{
// TODO: Создайте TeamLead и вызовите showRole()
}
// Выведите финальное сообщение после блока
cout << "Organization restructured!" << endl;
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Итоги — Обобщенный контейнерПотренируйтесь самостоятельно: Онлайн-компилятор C++