Наследование интерфейса в С++
У меня есть следующая структура класса:
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 должен вызывать.