Наследование интерфейса в С++

У меня есть следующая структура класса:

class InterfaceA
{ 
   virtual void methodA =0;
}

class ClassA : public InterfaceA
{
   void methodA();
}

class InterfaceB : public InterfaceA
{
   virtual void methodB =0;
}

class ClassAB : public ClassA, public InterfaceB
{ 
   void methodB(); 
}

Теперь следующий код не подлежит компиляции:

int main()
{
    InterfaceB* test = new ClassAB();
    test->methodA();
}

Компилятор говорит, что метод methodA() является виртуальным и не реализован. Я думал, что он реализован в ClassA (который реализует InterfaceA). Кто-нибудь знает, где моя вина?

Ответы

Ответ 1

Это потому, что у вас есть две копии InterfaceA. См. Это для более подробного объяснения: https://isocpp.org/wiki/faq/multiple-inheritance (ваша ситуация похожа на "страшный бриллиант" ).

Вам нужно добавить ключевое слово virtual, когда вы наследуете ClassA от InterfaceA. Вам также нужно добавить virtual, когда вы наследуете InterfaceB от InterfaceA.

Ответ 2

Виртуальное наследование, предложенное Лаурой, - это, конечно, решение проблемы. Но это не значит, что у вас есть только один интерфейс. У него также есть "побочные эффекты", например. см. https://isocpp.org/wiki/faq/multiple-inheritance#mi-delegate-to-sister. Но если привыкнуть к нему, это может пригодиться.

Если вам не нужны побочные эффекты, вы можете использовать шаблон:

struct InterfaceA
{ 
  virtual void methodA() = 0;
};

template<class IA>
struct ClassA : public IA //IA is expected to extend InterfaceA
{
  void methodA() { 5+1;}
};

struct InterfaceB : public InterfaceA
{
  virtual void methodB() = 0;
};

struct ClassAB 
  : public ClassA<InterfaceB>
{ 
  void methodB() {}
};

int main()
{
  InterfaceB* test = new ClassAB();
  test->methodA();
}

Итак, у нас есть ровно один родительский класс.

Но он выглядит более уродливым, когда есть более чем один "общий" класс (InterfaceA "разделяется", потому что он находится поверх "страшного алмаза", см. здесь https://isocpp.org/wiki/faq/multiple-inheritance, как опубликовано Лаурой). См. Пример (что будет, если ClassA также реализует интерфейс C):

struct InterfaceC
{
  virtual void methodC() = 0;
};

struct InterfaceD : public InterfaceC
{
  virtual void methodD() = 0;
};

template<class IA, class IC>
struct ClassA
  : public IA //IA is expected to extend InterfaceA
  , public IC //IC is expected to extend InterfaceC
{
  void methodA() { 5+1;}
  void methodC() { 1+2; }
};

struct InterfaceB : public InterfaceA
{
  virtual void methodB() = 0;
};

struct ClassAB
  : public ClassA<InterfaceB, InterfaceC> //we had to modify existing ClassAB!
{ 
  void methodB() {}
};

struct ClassBD //new class, which needs ClassA to implement InterfaceD partially
  : public ClassA<InterfaceB, InterfaceD>
{
  void methodB() {}
  void methodD() {}
};

Плохо, что вам нужно было изменить существующий ClassAB. Но вы можете написать:

template<class IA, class IC = interfaceC>
struct ClassA

Затем ClassAB остается неизменным:

struct ClassAB 
      : public ClassA<InterfaceB>

И у вас есть реализация по умолчанию для параметра шаблона IC.

Какой способ использовать для вас. Я предпочитаю шаблон, когда его просто понять. Трудно привыкнуть, что B:: incrementAndPrint() и C:: incrementAndPrint() будут печатать разные значения (не ваш пример), см. Это:

class A
{
public:
  void incrementAndPrint() { cout<<"A have "<<n<<endl; ++n; }

  A() : n(0) {}
private:
  int n;
};

class B
  : public virtual A
{};

class C
  : public virtual A
{};

class D
  : public B
  : public C
{
public:
  void printContents()
  {
    B::incrementAndPrint();
    C::incrementAndPrint();
  }
};

int main()
{
  D d;
  d.printContents();
}

И вывод:

A have 0
A have 1

Ответ 3

Эта проблема существует, потому что у С++ на самом деле нет интерфейсов, а только чистых виртуальных классов с множественным наследованием. Компилятор не знает, где найти реализацию methodA(), потому что он реализуется другим базовым классом ClassAB. Вы можете обойти это, реализовав methodA() в ClassAB(), чтобы вызвать базовую реализацию:

class ClassAB : public ClassA, public InterfaceB
{ 
    void methodA()
    {
        ClassA::methodA();
    }

    void methodB(); 
}

Ответ 4

У тебя ужасный бриллиант. ИнтерфейсB и ClassA должны фактически наследовать от InterfaceA В противном случае у ClassAB есть две копии MethodA, одна из которых по-прежнему является чистой виртуальной. Вы не должны создавать экземпляр этого класса. И даже если бы вы были - компилятор не смог бы решить, какой MethodA должен вызывать.