Наследование интерфейса в C ++ - PullRequest
16 голосов
/ 12 ноября 2009

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

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). Кто-нибудь знает, где моя вина?

Ответы [ 4 ]

21 голосов
/ 12 ноября 2009

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

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

9 голосов
/ 25 июня 2014

Виртуальное наследование, которое предложила Лаура, - это, конечно, решение проблемы. Но это не заканчивается наличием только одного InterfaceA. У него тоже есть «побочные эффекты», например. см. 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 также реализует interfaceC):

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
4 голосов
/ 12 ноября 2009

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

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

    void methodB(); 
}
2 голосов
/ 12 ноября 2009

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

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