необходимо ли виртуальное наследование от чистых абстрактных классов (интерфейсов) - PullRequest
13 голосов
/ 19 июня 2011

Почему в приведенном ниже коде компилятор жалуется, что 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() {}
};

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

Ответы [ 4 ]

5 голосов
/ 19 июня 2011

Ваш первый пример не удался, потому что компилятор не может устранить неоднозначность между тремя реализациями 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.
}

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

5 голосов
/ 19 июня 2011

Вам необходимо виртуальное наследование, чтобы преодолеть неоднозначность алмаза:

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's foo

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

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

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

1 голос
/ 19 июня 2011

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

PureAbstractBase* F;
F = new MultiplyInheritedClass();

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

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

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

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

, то все работает нормально, из-за отсутствия виртуальности.

Я работаю в Visual Studio 2008.

0 голосов
/ 23 января 2012

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

#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    

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

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