Множественное наследование + виртуальная беспорядок

У меня есть сценарий множественного наследования с алмазом, подобный этому:

    A
  /   \
 B     C
  \   /
    D

Общий родитель, A, определяет виртуальную функцию fn().
Возможно ли для B и C определить fn()?
Если да, то следующий вопрос: может ли D получить доступ к B и C fn() без значения? Я предполагаю, что для этого есть некоторый синтаксис.
И возможно ли, чтобы это сделать, не зная конкретно, кто такие B и C? B и C могут быть заменены некоторыми другими классами, и я хочу, чтобы код в D был общим.

Я пытаюсь сделать так, чтобы D как-то перечислил все экземпляры fn(), которые он имеет в своей родословной. Возможно ли это каким-то другим способом, что виртуальные функции?

Ответы

Ответ 1

Если вы не перезапишете fn снова в D, это невозможно. Поскольку в объекте D нет конечного переопределения: оба C и B переопределяют A::fn. У вас есть несколько вариантов:

  • Удалите либо C::fn, либо B::fn. Затем тот, который по-прежнему переопределяет A::fn, имеет конечный перехватчик.
  • Поместите конечный переопределитель в D. Затем он переопределяет A::fn, а также fn в C и B.

Например, следующие результаты при ошибке времени компиляции:

#include <iostream>

class A {
public:
    virtual void fn() { }
};

class B : public virtual A {
public:
    virtual void fn() { }
};

class C : public virtual A {
public:
    virtual void fn() { }
};

// does not override fn!!
class D : public B, public C {
public:
    virtual void doit() {
        B::fn();
        C::fn();
    }
};

int main(int argc, char **argv) {
  D d;
  d.doit();
  return 0;
}

Вы можете, однако, получить не виртуальные из A в C и B, но тогда у вас больше нет наследования бриллиантов. То есть каждый элемент данных в появляется дважды в B и C, потому что у вас есть два под-объекта базового класса в объекте D. Я бы рекомендовал вам пересмотреть этот дизайн. Попытайтесь устранить двойные объекты, подобные виртуальным наследованиям. Это часто вызывает такие конфликтующие ситуации.

Случай, очень похожий на тот, когда вы хотите переопределить определенную функцию. Представьте, что у вас есть виртуальная функция с тем же именем в B и C (теперь без общей базы A). А в D вы хотите переопределить каждую функцию, но каждый из них должен по-разному реагировать. В зависимости от того, вызываете ли вы функцию с помощью указателя B или указателя C, у вас другое поведение. Многократная наследование Часть III от Herb Sutter описывает хороший способ сделать это. Это может помочь вам принять решение о вашем дизайне.

Ответ 2

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

class A
{
public:
   virtual ~A() {}
   virtual void fn() = 0;
};

class B: public A
{
public:
   virtual ~B() {}
   virtual void fn(){ std::cout << "B::fn()" << std::endl; }
};

class C: public A
{
public:
   virtual ~C() {}
   virtual void fn(){ std::cout << "C::fn()" << std::endl; }
};

template <typename TypeB, typename TypeC>
class D: public TypeB, public TypeC
{
public:
   void Do()
   {
      static_cast<TypeB*>(this)->fn();
      static_cast<TypeC*>(this)->fn();
   }
};

typedef D<B, C> DInst;

DInst d;
d.Do();

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

Ответ 3

Уже есть несколько вопросов, которые касаются этого. Похоже, у нас заканчиваются вопросы, чтобы спросить. Возможно, окно поиска должно быть больше, чем кнопка Ask Question.

См

Ответ 4

Вы не можете перечислить определения fn() в родословной. С++ не хватает отражения. Единственный способ, который я могу себе представить, - это гигантская петля, проверяющая тип всех возможных предков. И это больно представить это.

Ответ 5

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

В несколько иной заметке, если вы собираетесь использовать MI таким образом (то есть, страшный бриллиант), тогда вы должны быть очень откровенным о том, какой виртуальный член вы хотите. Я не могу думать о хорошем случае, когда вы хотите выбрать семантику B::fn() over C::fn() без явного принятия решения при написании D. Вероятно, вы выбрали один из них (или даже оба), исходя из того, что делает индивидуальный метод. После того, как вы приняли решение, требование состоит в том, что унаследованные изменения не изменяют ожидания или семантический интерфейс.

Если вы действительно обеспокоены заменой нового класса, скажите E вместо say B, где E не сходит с B, а предлагает тот же интерфейс, тогда вы действительно должны использовать шаблон хотя я не уверен, почему там static_cast<> там...

struct A {
    virtual ~A() {}
    virtual void f() = 0;
};
struct B: A {
    virtual void f() { std::cout << "B::f()" << std::endl; }
};
struct C: A {
    virtual void f() { std::cout << "C::f()" << std::endl; }
};

template <typename Base1, typename Base2>
struct D: Base1, Base2 {
    void g() { Base1::f(); Base2::f(); }
};

int main() {
    D<B,C> d1;
    D<C,B> d2;
    d1.g();
    d2.g();
    return 0;
}

// Outputs:
//   B::f()
//   C::f()
//   C::f()
//   B::f()

отлично работает и кажется немного легче смотреть.

Ответ 6

Vividos уже ответил на основную часть сообщения. Даже если бы я использовал оператор области видимости вместо более громоздкого оператора static_cast < > +.

В зависимости от поставленной задачи, возможно, вы можете изменить соотношение наследования от D до B и C для меньшей композиции соединения (плюс, возможно, наследование от A). Это предполагает, что вам не нужно, чтобы D использовалось полиморфно как B или C, и что вам действительно не нужны B и C, которые используют один и тот же базовый экземпляр.

Если вы выберете композицию, вы можете получить B и C в качестве аргументов вашего конструктора в качестве ссылок/указателей типа A, что делает D полностью не осведомленным о типах B и C. В этот момент вы можете использовать контейнер для удерживайте как можно больше A объектов. Ваша собственная реализация fn() (если вы так решите) или любой другой метод.