Это хорошее соглашение, чтобы виртуально наследовать от чисто виртуальных (интерфейсных) классов? - PullRequest
5 голосов
/ 04 марта 2012

Я часто использую чистые виртуальные классы (интерфейсы), чтобы уменьшить зависимости между реализациями разных классов в моем текущем проекте.Для меня нередко даже иметь иерархии, в которых у меня есть чисто виртуальные и не чистые виртуальные классы, которые расширяют другие чисто виртуальные классы.Вот пример такой ситуации:

class Engine
{ /* Declares pure virtual methods only */ }

class RunnableEngine : public virtual Engine
{ /* Defines some of the methods declared in Engine */ }

class RenderingEngine : public virtual Engine
{ /* Declares additional pure virtual methods only */ }

class SimpleOpenGLRenderingEngine : public RunnableEngine,
    public virtual RenderingEngine
{ /* Defines the methods declared in Engine and RenderingEngine (that are not
     already taken care of by RunnableEngine) */ }

И RunnableEngine, и RenderingEngine расширяют Engine практически так, что проблема алмазов не влияет на SimpleOpenGLRenderingEngine.

IЯ хочу занять превентивную позицию в отношении проблемы с алмазом вместо того, чтобы заниматься ею, когда она становится проблемой, особенно потому, что мне нравится писать код, который как можно проще использовать кому-то другому, и я не хочу, чтобы ему приходилось изменятьмои классы, чтобы они могли создавать определенные иерархии классов, например, если бы Боб хотел сделать это:

class BobsRenderingEngine : public virtual RenderingEngine
{ /* Declares additional pure virtual methods only */ }

class BobsOpenGLRenderingEngine : public SimpleOpenGLRenderingEngine,
    public BobsRenderingEngine
{ /* Defines the methods declared in BobsRenderingEngine */ }

Это было бы невозможно, если бы я не сделал SimpleOpenGLRenderingEngine extension RenderingEngine виртуально. Мне известно, что вероятность того, что Боб захочет это сделать, может быть очень низкой.

Итак, я начал использовать соглашение о виртуальном всегда расширении чисто виртуальных классов, чтобы их можно было многократно наследовать.не вызывает проблемы с алмазами.Может быть, это из-за того, что я пришел из Java и склонен использовать только одиночное наследование с не чистыми виртуальными классами.Я уверен, что в некоторых ситуациях это, вероятно, излишне, но есть ли недостатки в использовании этого соглашения?Может ли это вызвать проблемы с производительностью / функциональностью и т. Д.?Если нет, то я не вижу причины не использовать соглашение, даже если в конце концов оно может и не понадобиться.

Ответы [ 3 ]

2 голосов
/ 21 июля 2012

Краткий ответ: Да.Вы прибили его.

Длинный ответ:

Итак, я начал использовать соглашение о постоянном расширении чисто виртуальных классов

Примечание:правильный термин - абстрактные классы, а не «чисто виртуальные классы».

Я начал использовать соглашение о постоянном расширении [абстрактных] классов практически так, чтобы множественное наследование от нихне вызывает проблемы с алмазами.

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

(Недостатки Java вынуждают васчтобы помещать в виртуальные интерфейсы только чисто виртуальные функции, а не элементы данных, C ++ более гибок, как обычно.)

Может ли это вызвать проблемы с производительностью

Виртуальность имеет свою стоимость.

Профилируйте ваш код.

Может ли это вызвать проблемы с [] функциональностью и т. Д.?

Возможно (редко).

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

2 голосов
/ 04 марта 2012

Я бы сказал, что поощрение наследования - это плохая идея (и это то, что вы делаете, используя виртуальное наследование).Немногие вещи на самом деле хорошо представлены древовидной структурой, которая подразумевается наследованием.Кроме того, я считаю, что множественное наследование, как правило, нарушает правило единой ответственности.Я не знаю вашего точного варианта использования, и я согласен, что иногда у нас нет другого выбора.

Однако в C ++ у нас есть другой способ составления объектов, инкапсуляция.Я считаю, что приведенное ниже объявление намного проще для понимания и манипулирования:

class SimpleOpenGLRenderingEngine 
{
public:
    RunnableEngine& RunEngine() { return _runner; }
    RenderingEngine& Renderer() { return _renderer; }

    operator RunnableEngine&() { return _runner; }
    operator RenderingEngine&() { return _renderer; }

private:
    RunnableEngine _runner;
    RenderingEngine _renderer;
};

Это правда, что объект будет использовать больше памяти, чем при виртуальном наследовании, но я сомневаюсь, что сложные объекты создаются в огромных количествах.

Теперь предположим, что вы действительно хотите наследовать для какого-то внешнего ограничения.Виртуальным наследованием все еще трудно манипулировать: вам, вероятно, это удобно, но люди, происходящие из ваших классов, могут этого не делать, и, вероятно, не будут слишком много думать перед тем, как наследовать.Я думаю, что лучшим выбором будет использование частного наследования.

class RunnableEngine : private Engine
{
     Engine& GetEngine() { return *this; }
     operator Engine&() { return *this; }
};

// Similar implementation for RenderingEngine

class SimpleOpenGLRenderingEngine : public RunnableEngine, public RenderingEngine
{ };
1 голос
/ 21 июля 2012

Ну, это довольно просто. Вы нарушили принцип единственной ответственности (SPR), и это является причиной вашей проблемы. Ваш класс "Двигатель" - это класс Бога. Хорошо продуманная иерархия не вызывает этой проблемы.

...