Как реорганизовать классовые иерархии, чтобы избежать проблемы с бриллиантами? - PullRequest
0 голосов
/ 03 ноября 2018

Предположим, у меня есть большая иерархия классов в виде одного корневого дерева с корнем class A, так что у каждого из его потомков есть своя реализация void f(...) и void g(...) с различными списками аргументов.

У меня есть другой класс class X: public A, который использует только базовый класс f(...) и g(...) в некоторых своих методах. Теперь я хочу расширить методы в X на все подтипы A: скажем, подтип A будет B, новый класс - class BX. Требование:

  1. (реализация) class BX следует использовать f(...) и g(...) из class B, а также все методы из class X.
  2. (интерфейс) class BX должен соответствовать интерфейсу class A.

Решением, которое я придумал, является template<typename B> class BX: public B, чтобы избежать проблемы с алмазом в 1.

Есть ли лучший способ добиться того, чего я хочу?

Ответы [ 2 ]

0 голосов
/ 03 ноября 2018
template<class D>
struct X {
  A* self(){ return static_cast<D*>(this); }
  A const* self() const{ return static_cast<D*>(this); }
  void foo(){
    self()->f( 1, 2, 3 );
  }
};

struct XA: A, X<XA> {};
struct XB: B, X<XB> {};

В пределах X<XB>, реализация XB, у вас есть доступ только к A.

Если вы хотите, чтобы пользователи видели только A, выставляйте XB s как A.

Обратите внимание, что это может быть совершенно бесполезно, поскольку для доступа к любым изменениям в XB / X<XB> необходимо знать, что ваш объект не является A, поскольку X<XB> ничего не изменяет в интерфейсе A .

0 голосов
/ 03 ноября 2018

По моему опыту, немало проблем с наследованием исчезнет, ​​если вы наследуете только интерфейсы (то есть только виртуальные) и используете композицию и делегирование для совместного использования реализаций, однако любопытно повторяющийся шаблон или CRTP, который вы использовали здесь это еще один распространенный способ обойти эту проблему.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...