Dyzzet|
C++ Data Science Алгоритмы Темы · Блог · YouTube
Полиморфная кухня
3. Множественное и виртуальное наследование

В первой части серии мы рассмотрели основы наследования и полиморфизма в языке C++, во второй рассмотрели механизм, благодаря которому всё это работает. Идём дальше: создадим более замысловатую иерархию.

Сегодня реализуем следующую иерархию. Базовым классом будет класс Cmyk, реализующий цветовую схему CMYK. Класс CmykGy будет содержать дополнительный серый, класс CmykLcLm — дополнительные светло-синий и светло-малиновый. Класс CmykGyLcLmLg внизу иерархии будет содержать все упомянутые дополнительные цвета (серый, светло-синий, светло-малиновый), а также светло-серый.

Соответствующий код очень простой.

class Cmyk
{
public:
    virtual ~Cmyk()
    {
    }
 
    virtual void print() const
    {
        std::cout << "CMYK." << std::endl;
    }
 
protected:
    unsigned char cyan;
    unsigned char magenta;
    unsigned char yellow;
    unsigned char black;
};
 
 
 
class CmykGy : public Cmyk
{
public:
    virtual void print() const
    {
        std::cout << "CMYK + gray." << std::endl;
    }
 
protected:
    unsigned char gray;
};
 
 
 
class CmykLcLm : public Cmyk
{
public:
    virtual void print() const
    {
        std::cout << "CMYK + light cyan + light magenta." << std::endl;
    }
 
protected:
    unsigned char lightCyan;
    unsigned char lightMagenta;
};
 
 
 
class CmykGyLcLmLg : public CmykGy, public CmykLcLm
{
public:
    virtual void print() const
    {
        std::cout << "CMYK + gray "
            << "+ light cyan + light magenta + light gray." << std::endl;
    }
 
protected:
    unsigned char lightGray;
};

Как было сказано, производный класс включает все поля, которые содержатся в базовом. Получается, что класс CmykGyLcLmLg содержит в себе поля класса Cmyk дважды! Это не только не соответствует нашим целям, но и расходует память. Такое явление называется проблемой ромбовидного наследования (diamond problem). Хотя заметим, что в какой-то другой ситуации такое дублирование может иметь смысл.

Слегка модифицируем объявления классов CmykGy и CmykLcLm (вы можете скачать получившийся исходный файл).

...
class CmykGy : public virtual Cmyk
{
   ...
...
class CmykLcLm : public virtual Cmyk
{
   ...

Схема принципиально меняется.

При виртуальном наследовании базовый подобъект (давайте назовём его так) хранится в единственном экземпляре (показано стрелками). Но перед этим помещается указатель на него. В случае классов, которые наследуются непосредственно от базового (CmykGy и CmykLcLm), всё более-менее наглядно.

Подробно рассмотрим, как формируется структура класса CmykGyLcLmLg.

  1. CmykGy — это Cmyk. Действительно, первое же поле — указатель на Cmyk (пусть и добавляется один уровень косвенности).
  2. Каждый производный класс включает в себя базовый (все базовые). Можно увидеть, что подобъекты CmykGy и CmykLcLm поочердно включены в производный объект. Сам базовый подобъект располагается далее, он доступен по любому из указателей.
  3. Производный класс может добавлять свои поля.

Заключение

Мы рассмотрели, как разрабатывать классы, которые получаются в результате наследования от нескольких классов, имеющих общий базовый. Однако детали реализации механизма виртуального наследования могут отличаться даже между различными версиями одного и того же компилятора.

Исходные файлы

cmyk.cpp

28 декабря 2016
C++ наследование и полиморфизм
Зарегистрируйтесь и войдите, чтобы оставлять комментарии и голосовать.

Полиморфная кухня
Наследование и полиморфизм в C++
Таблицы виртуальных функций
Множественное и виртуальное наследование
Приведение dynamic_cast и RTTI
C++11 и C++14
Умные указатели
Также может быть интересным
© MMXI—MMXXIII. RSS. Поддержать сайт
Светлая тема / тёмная тема