Является виртуальным наследованием от чистых абстрактных классов (интерфейсов), необходимых

Почему в коде ниже компилятор жалуется, что PureAbstractBase является двусмысленным базовым классом MultiplyInheritedClass? Я понимаю, что у меня две копии PureAbstractBase в MultiplyInheritedClass и что FirstConreteClass и SecondConreteClass должны быть получены практически потому, что они представляют собой средний ряд алмаза (и это действительно исправляет проблему с помощью кода ниже). Но хотя у меня есть две копии интерфейса, почему код в MultiplyInheritedClass не просто переопределяет оба и недвусмысленно выбирает класс интерфейса, определенный в MultiplyInheritedClass?

#include <iostream>
using namespace std;

class PureAbstractBase {
  public:
    virtual void interface() = 0;
};

// I know that changing the following line to:
// class FirstConcreteClass : public virtual PureAbstractBase {
// fixes the problem with this hierarchy
class FirstConcreteClass : public PureAbstractBase {
  public:
    virtual void interface() { implementation(); }
  private:
    void implementation() { cout << "This is object FirstConcreteClass\n"; }
};

// I know that changing the following line to:
// class SecondConcreteClass : public virtual PureAbstractBase {
// fixes the problem with this hierarchy
class SecondConcreteClass : public PureAbstractBase {
  public:
    virtual void interface() { implementation(); }
  private:
    void implementation() { cout << "This is object SecondConcreteClass\n"; }
};

class MultiplyInheritedClass : public FirstConcreteClass,
                               public SecondConcreteClass {
  public:
    virtual void interface() { implementation(); }
  private:
    void implementation() { cout << "This is object MultiplyInheritedClass\n"; }
};

Кроме того, почему у меня нет проблем со следующей иерархией? В этом случае класс ConcreteHandler не имеет трех экземпляров AbstractTaggingInterface? Так почему же у него нет такой же проблемы, как в примере выше?

#include <iostream>
using namespace std;

class AbstractTaggingInterface {
  public:
    virtual void taggingInterface() = 0;
};

class FirstAbstractHandler : public AbstractTaggingInterface {
  public:
    virtual void taggingInterface() { cout << "FirstAbstractHandler\n"; }
    virtual void handleFirst() = 0;
};

class SecondAbstractHandler : public AbstractTaggingInterface {
  public:
    virtual void taggingInterface() { cout << "SecondAbstractHandler\n"; }
    virtual void handleSecond() = 0;
};

class ThirdAbstractHandler : public AbstractTaggingInterface {
  public:
    virtual void taggingInterface() { cout << "ThridAbstractHandler\n"; }
    virtual void handleThird() = 0;
};

class ConcreteHandler : public FirstAbstractHandler,
                        public SecondAbstractHandler,
                        public ThirdAbstractHandler {
  public:
    virtual void taggingInterface() = { cout << "ConcreteHandler\n"; }
    virtual void handleFirst() {}
    virtual void handleSecond() {}
    virtual void handleThird() {}
};

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

Ответ 1

Вам нужно виртуальное наследование для преодоления двусмысленности бриллианта:

class FirstConcreteClass  : public virtual PureAbstractBase { ... };
class SecondConcreteClass : public virtual PureAbstractBase { ... };

Долгосрочное объяснение: предположим, что у вас есть это:

// *** Example with errrors! *** //
struct A { virtual int foo(); };
struct B1 : public A { virtual int foo(); };
struct B2 : public A { virtual int foo(); };
struct C: public B1, public B2 { /* ... */ };  // ambiguous base class A!

int main() {
  A * px = new C;                              // error, ambiguous base!
  px->foo();                                   // error, ambiguous override!
}

Наследование виртуальной функции foo неоднозначно, потому что оно происходит тремя способами: от B1, от B2 и от A. Диаграмма наследования образует "алмаз":

   /-> B1 >-\
A->          ->C
   \-> B2 >-/

Сделав наследование виртуальным, struct B1 : public virtual A; и т.д., вы разрешаете любому базовому классу C* вызывать правильный член:

struct A { virtual int foo(); };
struct B1 : public virtual A { virtual int foo(); };
struct B2 : public virtual A { virtual int foo(); };
struct C: public B1, public B2 { virtual int foo(); };

Мы должны также определить C::foo(), чтобы это имело смысл, так как иначе C не имел бы четко определенного члена foo.

Еще несколько деталей: предположим, что теперь у нас есть фактически фактически наследующий класс C, как указано выше. Мы можем получить доступ ко всем различным виртуальным членам по желанию:

int main() {
  A * pa = new C;
  pa->foo();      // the most derived one
  pa->A::foo();   // the original A foo

  B1 * pb1 = new C;
  pb1->foo();     // the most derived one
  pb1->A::foo();  // A foo
  pb1->B1::foo(); // B1 foo

  C * pc = new C;
  pc->foo();      // the most derived one
  pc->A::foo();   // A foo
  pc->B1::foo();  // B1 foo
  pc->B2::foo();  // B2 foo
  pc->C::foo();   // C foo, same as "pc->foo()"
}

 

Обновление. Как говорит Дэвид в комментарии, важным моментом здесь является то, что промежуточные классы B1 и B2 наследуют фактически так, что дальнейшие классы (в данном случае C) могут унаследовать от них, одновременно сохраняя наследование от A недвусмысленное. Извините за первоначальную ошибку и спасибо за исправление!

Ответ 2

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

Причина, по которой ваш пример работает без наследования virtual, заключается в том, что не существует противоречивой реализации общего базового класса. Другими словами:

class Base
{
public:
    void DoSomething() {
    std::cout << "TADA!";
    }
}

class One : public Base
{
    //...
}

class Two : public Base
{
    //...
}

class Mixed : public One, public Two
{
    //...
}

int main()
{
    Mixed abc;
    abc.DoSomething(); //Fails because the compiler doesn't know whether to call
                       // One::DoSomething or Two::DoSomething, because they both
                       // have implementations.

    //In response to comment:
    abc.One::DoSomething(); //Succeeds! You removed the ambiguity.
}

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

Ответ 3

Я пробовал оба кода вопроса, и они отлично работали при создании экземпляра объекта многоуровневого класса. Он не работал только с полиморфизмом, как это, например:

PureAbstractBase* F;
F = new MultiplyInheritedClass();

И причина ясна: он не знает, к какой копии базового класса Аннотация он должен быть связан (извините за плохие выражения, я понимаю идею, но не могу ее выразить). И так как вложение virtaully делает только одну копию в производном классе, тогда это прекрасно.

Также код Billy ONeal не совсем понятен, что мы должны помещать вместо комментариев?

Если мы разместим:

public:    
void DoSomething() 
{    std::cout << "TADA!";    }

он отлично работает, поскольку нет виртуальности.

Я работаю над Visual Studio 2008.

Ответ 4

Почему бы не сделать это так (предложено в запись блога Бенджамина Супника):

#include <iostream>

class PureAbstractBase {
public:
    virtual void interface() = 0;
};

class FirstConcreteClass : public PureAbstractBase {
public:
    virtual void interface() { implementation(); }
private:
    void implementation() { std::cout << "Fisrt" << std::endl; }
};

class SecondConcreteClass : public PureAbstractBase {
public:
    virtual void interface() { implementation(); }
private:
    void implementation() { std::cout << "Second" << std::endl; }
};

class MultiplyInheritedClass : public FirstConcreteClass,
                               public SecondConcreteClass 
{
public:
    virtual void interface() { implementation(); }
private:
    void implementation() { std::cout << "Multiple" << std::endl; }
};

int main() {
MultiplyInheritedClass mic;
mic.interface();

FirstConcreteClass *fc = &mic; //disambiguate to FirstConcreteClass 
PureAbstractBase *pab1 = fc;
pab1->interface();

SecondConcreteClass *sc = &mic; //disambiguate to SecondConcreteClass 
PureAbstractBase *pab2 = sc;
pab2->interface();
}

который дает:

Multiple
Multiple
Multiple    

Таким образом:

  • не задействованы виртуальные базы (вам они действительно нужны?)
  • вы можете вызвать функцию overriden через экземпляр MultiplyInheritedClass
  • неоднозначность удаляется двухэтапным преобразованием