В первой части серии мы рассмотрели основы наследования и полиморфизма в языке 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.
Мы рассмотрели, как разрабатывать классы, которые получаются в результате наследования от нескольких классов, имеющих общий базовый. Однако детали реализации механизма виртуального наследования могут отличаться даже между различными версиями одного и того же компилятора.