Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Грабли 2: Виртуальное наследование

Anna | 25.06.2014 | нет комментариев

Статья о том, как множественное наследование все усложняет. Как виртуальное наследование, на 1-й взор, реализовано незакономерно. Как на 2-й взор логика возникает, но ярус трудности и запутанности продолжает расти. В всеобщем, чем труднее задача, тем больше примитивные необходимо подбирать инструменты.

Все основано на реальных событиях, но примеры были максимально упрощены, Дабы в них осталась лишь суть задачи.

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

  1. Renderable: содержит знак видимости и способ рисования
  2. Updatable: содержит знак активности и способ обновления состояния
  3. VisualActivity = Renderable Updatable

Добавлю еще два неестественных класса, Дабы продемонстрировать случившиеся трудности

  1. JustVisible: легко видный объект
  2. JustVisiblePlusVisualActivity: JustVisible с обновляемым состоянием

Получается дальнейшая картина

Сразу же видна задача — финальный класс наследует Renderable двукратно: как родитель JustVisible и VisualActivity. Это не дает типично трудиться со списками отображаемых объектов

	JustVisiblePlusUpdate* object = new JustVisiblePlusUpdate;
	std::vector<Renderable*> vector_visible;
	vector_visible.push_back(object);

Получается неоднозначность (ambiguous conversions) — компилятор не может осознать, об унаследованном по какой ветке Renderable идет речь. Ему дозволено подмогнуть, уточнив направление путем очевидного приведения типа к одному из промежуточных

	JustVisiblePlusUpdate* object = new JustVisiblePlusUpdate;
	std::vector<Renderable*> vector_visible;
	vector_visible.push_back(static_cast<VisualActivity*>(object));

Компиляция пройдет удачно, только вот оплошность останется. В нашем случае требовался один и тот же Renderable вне зависимости от того, каким образом он был унаследован. Дело в том, что в случае обыкновенного наследования в классе-потомке (JustVisiblePlusVisualActivity) содержится обособленный экземпляр родительского класса для всякой ветки.

Причем свойства всякого из них дозволено менять самостоятельно. Выражаясь на c , правдиво выражение

(&static_cast<VisualActivity*>(object)->mVisible) != (&static_cast<JustVisible*>(object)->mVisible)

Так что обыкновенное множественное наследование для задачи не подходило. А вот виртуальное выглядело той самой серебряной пулей, которая была нужна… Все что требовалось — унаследовать базовые классыRenderable и Updatable виртуально, а остальные — обыкновенным образом:

class VisualActivity : public virtual Updatable, public virtual Renderable
...
class JustVisible : public virtual Renderable
...
class JustVisiblePlusUpdate : public JustVisible, public VisualActivity

Все унаследованные виртуально классы представлены в потомке только один раз. И все бы работало, если бы базовые классы не имели конструкторов с параметрами. Но такие конструкторы существовали, и случился сюрприз. Всякий виртуально наследуемый класс имел как конструктор по умолчанию так и параметризованный

class Updatable
{
public:
	Updatable()
		: mActive(true)
	{
	}

	Updatable(bool active)
		: mActive(active)
	{
	}
    //....
};
class Renderable
{
public:
	Renderable()
		: mVisible(true)
	{
	}

	Renderable(bool visible)
		: mVisible(visible)
	{
	}
    //....
};

Классы-потомки содержали только конструкторы с параметрами

class VisualActivity : public virtual Updatable, public virtual Renderable
{
public:
	VisualActivity(bool visible, bool active)
		: Renderable(visible)
, Updatable(active)
	{
	}
    //....
};
class JustVisible : public virtual Renderable
{
public:
	JustVisible(bool visible)
		: Renderable(visible)
	{
	}
    //....
};
class JustVisiblePlusUpdate : public JustVisible, public VisualActivity
{
public:
	JustVisiblePlusUpdate(bool visible, bool active)
		: JustVisible(visible)
		, VisualActivity(visible, active)
	{
	}
    //....
};

И все равно при создании объекта

	JustVisiblePlusUpdate* object = new JustVisiblePlusUpdate(false, false);

вызывался конструктор Renderable по умолчанию! На 1-й взор, это казалось чем-то диким. Но разглядим подробнее, откуда взялось предположение, что приведенный код должен приводить к вызову конструктораRenderable::Renderable(bool visible) взамен Renderable::Renderable().

Породило загвоздку допущение, что Renderable Удивительным образом разделится между JustVisible,VisualActivity и JustVisiblePlusUpdate. Но «чуду» не суждено было случиться. чай тогда дозволено было бы написать что-то типа

class JustVisiblePlusUpdate : public JustVisible, public VisualActivity
{
public:
	JustVisiblePlusUpdate(bool active)
		: JustVisible(true)
		, VisualActivity(false, active)
	{
	}
    //....
};

осведомивши компилятору двойственную информацию, когда единовременно требовалось бы проектированиеRenderable с параметрами true и false. Открывать вероятность для сходственных парадоксов никто не захотел, соответственно и механизм работает иным образом. Класс Renderable в нашем случае огромнее не является частью ни JustVisible, ни VisualActivity, а принадлежит непринужденно JustVisiblePlusUpdate.

Это поясняет, отчего вызывался конструктор по умолчанию — конструкторы виртуальных классов обязаны вызываться финальными преемниками, т.е. рабочим вариантом было бы что-то типа

class JustVisiblePlusUpdate : public JustVisible, public VisualActivity
{
public:
	JustVisiblePlusUpdate(bool visible, bool active)
		: JustVisible(visible)
		, VisualActivity(visible, active)
		, Renderable(visible)
		, Updatable(active)
	{
	}
    //....
};

При виртуальном наследовании доводится, помимо конструкторов непосредственных родителей, очевидно вызывать конструкторы всех виртуально унаследованных классов. Это не дюже видимо и с легкостью может быть упущено в нетривиальном плане. Так что ненужный раз подтвердилась правда: не огромнее одного открытого наследования для всякого класса. Оно того не стоит. В нашем случае было принято решение отказаться от распределения на Renderable и Updatable, ограничившись одним базовым VisualActivity. Это добавило некоторую избыточность, но круто упростило всеобщую архитектуру — отслеживать и поддерживать все виртуальные и обыкновенные случаи наследования было слишком затратно.

 

Источник: programmingmaster.ru

 

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB