Множественное наследование конкретных классов. Синтаксис, структура в памяти, особенности применения и реализации
С++, в отличие от многих других объектно-ориентированных языков, допускает множественное наследование от конкретных классов. Базовые классы следует перечислять через запятую. Класс, производный от двух и более конкретных классов, объединяет свойства всех базовых. Такие классы иногда называют “примесями” (mixin).
imagebutton.hpp
#ifndef _IMAGEBUTTON_HPP_
#define _IMAGEBUTTON_HPP_
//************************************************************************
#include "button.hpp"
#include "imagecontrol.hpp"
//************************************************************************
// Класс, представляющий кнопку с изображением
classImageButton
: publicButton, publicImageControl
{
//------------------------------------------------------------------------
public:
// Конструктор - передаются аргументы для двух базовых классов
ImageButton ( conststd::string & _text, conststd::string & _imageFilename );
// Переопределение метода отрисовки из базового класса Button
voiddraw ( int_x, int_y ) override;
//------------------------------------------------------------------------
};
//************************************************************************
#endif // _IMAGEBUTTON_HPP_
imagebutton.cpp
//************************************************************************
#include "imagebutton.hpp"
//************************************************************************
ImageButton::ImageButton ( conststd::string & _text,
conststd::string & _imageFilename )
// Вызов конструкторов двух базовых классов
: Button( _text )
, ImageControl( _imageFilename )
{
}
//************************************************************************
// Реализация переопределенного метода отрисовки кнопки
voidImageButton::draw ( int_x, int_y )
{
// Вычисляем размеры текста
intwidth, height;
calculateTextSize( width, height );
// Изображение выводится слева от текста, понадобится 3 отступа
width += getImageWidth() + 3 * PADDING;
// Высота кнопки - максимум из высоты изображения и текста + 2 отступа
if( getImageHeight() > height )
height = getImageHeight();
height += 2 * PADDING;
// Отрисовка фонового прямоугольника (вызов Button::drawRectangle)
drawRectangle( _x, _y, width, height );
// Отрисовка изображения (вызов ImageConrol::drawImage)
drawImage( _x + PADDING,_y + PADDING );
// Отрисовка текста на кнопке правее изображения (вызов Button::drawText)
drawText( _x + getImageWidth() + 2 * PADDING, _y + PADDING );
}
Структура размещения данных класса-примеси ImageButton в памяти предполагает, что в начале будут размещаться данные первого базового класса Button, затем второго ImageControl. Если бы ImageButton содержал собственные дополнительные поля, они бы размещались в объекте после полей обоих базовых классов:
Указатель или ссылка на объект класса ImageButton может быть преобразован к указателю/ссылке на объект любого из двух базовых классов:
ImageButton ib( “OK”, “ok.png” );
Button * pButton = & ib;
ImageControl * pControl = & ib;
Интересно, что, несмотря на манипулирование одним и тем же объектом ImageButton, абсолютные значения адресов в преобразованных указателях pButton и pControl совпадать не будут. Это вытекает из расположения полей базовых классов в памяти объекта. Поля первого базового класса находятся в начале объекта, и адрес pButton будет совпадать с адресом начала объекта. Но поля второго базового класса смещены от начала объекта на размер первого базового класса. Соответственно, этот адрес не совпадает с адресом начала объекта. Учитывая факт возможного смещения адресов в иерархии множественного наследования, следует всячески избегать попыток преобразования вниз по такой иерархии.