2016-06-22 3 views
4

Я немного новичок в более сложных функциях C++. Вчера я опубликовал следующий вопрос, и я узнал о виртуальном наследовании и страшном алмазе смерти.Confused on C++ Multiple Inheritance

Inheriting from both an interface and an implementation C++

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

Я хочу определить интерфейс для двух типов цифровых точек. Входная цифровая точка и цифровая точка выхода. Интерфейс должен быть тонким, только для доступа к информации. Конечно, подавляющее большинство свойств являются общими для обоих типов цифровых точек. Поэтому для меня это явный случай Наследования, а не композиции.

Мои мои определения интерфейса выглядеть примерно так:

// Interface Definitions 
class IDigitalPoint 
{ 
public: 
    virtual void CommonDigitalMethod1() = 0; 
}; 

class IDigitalInputPoint : virtual IDigitalPoint 
{ 
public: 
    virtual void DigitialInputMethod1() = 0; 
}; 

class IDigitalOutputPoint : virtual IDigitalPoint 
{ 
public: 
    virtual void DigitialOutputMethod1() = 0; 
}; 

И мои реализации выглядеть следующим образом:

// Implementation of IDigitalPoint 
class DigitalPoint : virtual public IDigitalPoint 
{ 
public: 
    void CommonDigitalMethod1(); 
    void ExtraCommonDigitalMethod2(); 
} 

// Implementation of IDigitalInputPoint 
class DigitalInputPoint : public DigitalPoint, public IDigitalInputPoint 
{ 
public: 
    void DigitialInputMethod1(); 
    void ExtraDigitialInputMethod2(); 
} 

// Implementation of IDigitalOutputPoint 
class DigitalOutputPoint : public DigitalPoint, public IDigitalOutputPoint 
{ 
public: 
    void DigitialOutputMethod1(); 
    void ExtraDigitialOutputMethod2(); 
} 

Так как я мог бы переформатировать эту структуру, чтобы избежать ИМ?

Заранее благодарим за помощь.

+0

Поскольку 'IDigitalPoint' всегда фактически унаследован, я не вижу проблемы. Теперь, если вы должны были наследовать от двух последних классов, поскольку оба они наследуют практически не от «DigitalPoint», что было бы проблемой. Если это то, о чем вы просите, вам нужно уточнить это. PS: изучение C++ из хорошей книги всегда лучше, чем задавать вопросы на stackoverflow.com –

+0

@Sam Вы правы. Множественное наследование не всегда плохое. Если OP хочет увидеть довольно хороший пример для хорошего множественного наследования, посмотрите главу политики в Alexandrescues Modern C++ Design. Но это только мое мнение. – Mehno

+3

MI очень легко использовать. Но, пользуясь хорошо, это очень полезно. Большинство заявлений, которые я видел о плохой ИИИ, исходят от людей, которые, как правило, злоупотребляют им, а затем обвиняют инструмент в возникающих проблемах, а не за человека, владеющего им. – Peter

ответ

7

«множественное наследование, как правило, является признаком плохого кода» - родители, которые являются чистыми интерфейсами, не учитываются в отношении этого правила.Ваши I* классы представляют собой чистые интерфейсы (содержат только чистые виртуальные функции), так что вы Digital*Point классы ОК в этом отношении

+0

И интерфейсы должны * не * определять какие-либо переменные-члены, правильно? По крайней мере, не IDigitalPoint? Кроме того, на стороне реализации мой план состоял в том, чтобы получить 2 конечных класса - от DigitalInputPoint & DigitalOutputPoint и создать объекты из этих классов. Затем я собирался повысить их в DigitalInputPoint и DigitalOuputPoint для обработки кода более высокого уровня. Будет ли это проблемой? В принципе, как только вы правильно обработали часть MI (предположив, что я сделал это с моим примером выше), могу ли я безопасно продолжать работать из объединенных версий MI, не беспокоясь о других проблемах? – ThermoX

+0

Я выведу другой класс из DigitalInputPoint и DigitalOutputPoint, вы получите проблему с алмазами, потому что это не чистые интерфейсы (я полагаю, что «DigitalPoint» имеет переменные-члены). Но, как уже упоминалось здесь, это не всегда проблема и некоторые хорошо известные библиотеки (например, stl), просто не забывайте о виртуальном наследовании. Трудно сказать, будете ли вы или не будете иметь других проблем. Пока я не вижу «пробки» в вашем дизайне, хотя он может заслуживать пересмотра с учетом полных требований. – mvidelgauz

+0

Хорошо ... Итак, поиск продолжается ... Спасибо за ваши входы, mvidelgauz ... – ThermoX

0

Это именно та ситуация, в которой стандартная библиотека делает виртуальным наследованием, в иерархии std::basic_iostream.

Таким образом, это может быть редкий случай, когда он действительно имеет смысл.

Однако это зависит только от точных деталей, которые вы удалили для ясности, поэтому невозможно точно сказать, существует ли лучшее решение.

Например, Почему является точкой входа, отличной от точки вывода? A DigitalPoint звучит как вещь со свойствами, которые могут быть смоделированы классом. A DigitalInputPoint, однако, просто звучит как ... DigitalPoint как-то связано с источником входного сигнала. Имеет ли он разные свойства? Разное поведение? Что они и почему?

0

Вы можете перейти по ссылке ниже, чтобы понять больше о множественном наследовании Avoid Multiple Inheritance

Кроме того, в вашем случае, множественное наследование имеет смысл !!. Вы можете использовать композицию, если хотите.

+0

Спасибо. Я прочитал эту ссылку вчера, и это на самом деле побудило меня написать этот вопрос. Очевидно, что моя проблема может быть решена. Не использовать MI, поскольку C# не позволяет этого. Мне просто интересно, как это сделать. И все еще исследуя ответы здесь. Спасибо за вашу ссылку. – ThermoX

0

Рассмотрим другой подход:

class DigitalPoint 
{ 
public: 
    void CommonDigitalMethod1(); 
    void ExtraCommonDigitalMethod2(); 
} 

// Implementation of IDigitalInputPoint 
class DigitalInputPoint 
{ 
public: 
    void CommonDigitalMethod1(); 
    void DigitialInputMethod1(); 
    void ExtraDigitialInputMethod2(); 
} 

// Implementation of IDigitalOutputPoint 
class DigitalOutputPoint 
{ 
public: 
    void CommonDigitalMethod1(); 
    void DigitialOutputMethod1(); 
    void ExtraDigitialOutputMethod2(); 
} 

Чтобы использовать так:

template <class T> 
void do_input_stuff(T &digitalInputPoint){ 
    digitalInputPoint.DigitialInputMethod1(); 
} 

Вы получаете более легкую реализацию с более четким дизайном и меньшим сцеплением с наиболее вероятной производительностью. Единственный Один недостаток заключается в том, что интерфейс неявно определяется использованием. Это можно смягчить, задокументировав то, что ожидает шаблон, и в конечном итоге вы сможете сделать это в концепциях, чтобы компилятор проверил его для вас.
Еще один недостаток в том, что у вас больше нет vector<IDigitalPoint*>.

+0

Это не работает, если вы хотите иметь что-то вроде контейнера указателей «DigitalPoint», где тип каждого элемента известен только во время выполнения. Вы отказываетесь от преимуществ динамического полиморфизма. – interjay

+0

@interjay Вы правы, я отредактировал это немного, чтобы это отразить. ОП не сказал, что это требование. По моему опыту это требование редко и только появляется как деталь реализации. – nwp

+0

@nwp Не нужно ли определять CommonDigitalMethod1() как для DigitalInputPoint, так и для DigitalOutputtPoint? Я мог бы использовать внутренний указатель на экземпляр DigitalPoint, а затем обернуть методы DigitalInputPoint :: CommonDigitalMethod1() и DigitalOutputPoint :: CommonDigitalMethod1() вокруг этого внутреннего указателя DigitalPoint. Кажется свернутым, правда? – ThermoX

0

Вы действительно уверены, что вам нужны 3 интерфейса?

class IDigitalPoint 
{ 
public: 
    virtual void CommonDigitalMethod1() = 0; 
}; 


enum class Direction : bool { Input, Output }; 

template <Direction direction> 
class DigitalPoint : public IDigitalPoint 
{ 
public: 
    void CommonDigitalMethod1() {} 
    void ExtraCommonDigitalMethod2() {} 

    virtual void DigitialMethod1() = 0; 
}; 

class DigitalInputPoint : public DigitalPoint<Direction::Input> 
{ 
public: 
    void DigitialInputMethod1() {} 
    void ExtraDigitialInputMethod2() {} 

    // This is like DigitialInputMethod1() 
    virtual void DigitialMethod1() override 
    {} 
}; 

class DigitalOutputPoint : public DigitalPoint<Direction::Output> 
{ 
public: 
    void DigitialOutputMethod1() {} 
    void ExtraDigitialOutputMethod2() {} 

    // This is like DigitialOutputMethod1() 
    virtual void DigitialMethod1() override 
    {} 
}; 
+0

Hi @slasla. Спасибо за ваш вклад. Однако DigitalInputPoint & DigitalOutputPoint может отличаться по количеству методов (и, фактически, в моей реальной проблеме). Кроме того, даже если оба метода имеют один метод, это не означает, что они будут подразумевать один и тот же процесс в обоих классах и, таким образом, оправдать общее имя. – ThermoX

1

(множественное) наследование и интерфейсы имеют тенденцию к ненужным осложнениям простых отношений.

Здесь нам нужны только простая конструкция и несколько отдельно стоящих функций:

namespace example { 
    struct Point { T x; T y; } 

    Point read_method(); 
    void write_method(const Point&) 
    void common_method(Point&); 
    void extra_common_method(Point&); 
} // example 

common_method может быть кандидатом для функции члена Point. extra_common_method, который не так распространен, может быть кандидатом для другого класса, инкапсулирующего Point.

0

Вы можете использовать композицию вместо наследования. Live Example

Если дочерние классы не используют функциональные возможности DigitalPoint, вы можете попробовать использовать CRTP. Это может сбить с толку, если вы не понимаете CRTP, но он работает как шарм, когда он подходит правильно. Live Example

+0

Спасибо, Андрей. Вы добавили несколько дополнительных слоев к моим знаниям ... Мне психически трудно отделить общие части цифровых точек от их конкретных версий. Очевидно, что цифровая точка ввода - это скорее более конкретная версия цифровой точки, в моем случае. Чтобы его свойства разделились на два отдельных, отключенные классы чувствуют себя странно. Но я согласен, ваш вариант позволит избежать множественных наследований. Мне просто интересно, что будут делать люди, в этом случае, если бы это было написано на C#? Предлагает ли C# другие варианты решения этих случаев, используя одиночные наследования? – ThermoX

+0

@ThermoX C# предлагает дженерики (не путать с шаблонами C++ !!!), которые могут помочь создать хороший дизайн для этой ситуации. Как точно - из-за объема этого вопроса и ограничения длины комментариев :) – mvidelgauz

+0

@mvidelgauz OOoookkkkk then ... – ThermoX