Я пытаюсь разработать иерархию классов на C++, где у меня есть базовый интерфейс FooInterface с чистой виртуальной функцией foo() и другой интерфейс FooBarInterface, который должен расширяться FooInterface и добавлять дополнительную функцию bar(). Затем у меня есть класс Foo, который реализует foo() из FooInterface, и класс FooBar, который наследуется от Foo и FooBarInterface и реализует bar().
Моя цель — сделать так, чтобы функция func принимала std::shared_ptr к любому объекту, который реализует как foo(), так и bar().
#include <memory>
class FooInterface {
public:
virtual void foo() = 0;
};
class FooBarInterface : public FooInterface {
public:
virtual void bar() = 0;
};
class Foo : public FooInterface {
public:
void foo() override {};
};
class FooBar : public Foo, public FooBarInterface {
public:
void bar() override {};
};
void func(std::shared_ptr<FooBarInterface> fb) {}
int main() {
func(std::make_shared<FooBar>());
}
Примечание: виртуальный деструктор опущен.
Однако этот код не компилируется, вероятно, потому, что foo() в FooBarInterface не переопределяется в FooBar, хотя FooBar наследуется от Foo, который реализует foo().
Как мне изменить свой код? Или у вас есть какой-то шаблон проектирования, на который мне следует сослаться?
@user12002570 user12002570, но почему в этом случае FooBar не получает реализацию foo от Foo? (все три основных компилятора жалуются, что FooBar является абстрактным - см. демо). Я не увидел объяснений в дураке.
Открыт вновь. Да, буквальный ответ содержится в вопросе, указанном пользователем @user12002570. Но есть более тонкая проблема, которую следует решить; если бы FooInterface был виртуальной базой как Foo, так и FooBarInterface, код был бы действителен благодаря доминированию.
@PeteBecker Я действительно заметил это, пока «играл» с примером. Но почему FooBar абстрактно как есть (без виртуального наследования)? Почему он не наследует foo от Foo?
@wohlstad - в коде в вопросе есть две разные FooInterface::foo функции: одна проходит Foo и одна проходит FooBarInterface. Второе не переопределяется, поэтому FooBar по-прежнему абстрактно. Когда база FooInterface виртуальная, FooInterface::foo переопределяет foo в обоих промежуточных классах (именно это и означает «доминирование»).
@PeteBecker Теперь я понял. Я пропустил существование 2 foos. Возможно, вы захотите опубликовать ответ, потому что, насколько я понимаю, предложенный обман не решает эту проблему.
Вздох. Пожалуйста, игнорируйте ссылку «доминирование» в моем комментарии «Возобновлено». Страница, на которую он указывает, — ерунда.





Если вы хотите, чтобы FooBar::foo использовал реализацию Foo, вы вызываете его.
class FooBar : public Foo, public FooBarInterface {
public:
void foo() override { Foo::foo(); }
void bar() override {};
};
Ответ на самом деле не объясняет, почему FooBar является абстрактным как есть (без добавления foo). См. ответ ПитеБекера для объяснения (оно также отсутствовало в моем ответе, поэтому я удалил его).
Я запутался в Foo, Bar и Interfaces, поэтому собираюсь переписать иерархию классов, сохранив ту же структуру:
struct Base {
virtual void foo() = 0;
}
struct Intermediate1 : Base {
};
struct Intermediate2 : Base {
void foo();
}
struct Derived : Intermediate1, Intermediate2 {
};
Derived d; // error: can't create object of an abstract type
Объект Derived имеет два подобъекта типа Base, один проходит через Intermediate1, а другой проходит через Intermediate2. Таким образом, у него есть две разные функции foo: одна проходная Intermediate1 и одна проходная Intermediate2. Поскольку Intermediate1 не переопределяет Base::foo (а Derived тоже не переопределяет ее), это по-прежнему чисто виртуальная функция, поэтому Derived является абстрактным классом и не может быть создан экземпляр.
С другой стороны (и сколько людей представляют себе наследование «интерфейсов» (цитируется потому, что в C++ нет формального понятия «интерфейс»)) подобная иерархия может выглядеть так:
struct Base {
virtual void foo() = 0;
}
struct Intermediate1 : virtual Base {
};
struct Intermediate2 : virtual Base {
void foo();
}
struct Derived : Intermediate1, Intermediate2 {
};
Derived d; // okay, via "dominance"
Обратите внимание на добавленный virtual для классов, производных непосредственно от Base. Теперь объект типа Derived имеет только один подобъект типа Base, а правило доминирования гласит, что в DerivedIntermediate2::foo переопределяет Base::foo, хотя в Intermediate1 переопределения нет. Итак, Intermediate1 сам по себе является абстрактным классом, а Derived — нет, хотя он и не переопределяет Base::foo; переопределения через Intermediate2 достаточно.
Хорошее объяснение. +1. Я бы просто добавил примечание, что использование shared_ptr здесь не имеет особого значения, а только тот факт, что FooBar является абстрактным (поскольку проблема использования ptr была явно упомянута в вопросе).
Также обратите внимание, что виртуальное наследование — это проектное решение, а не решение по кодированию. Не делайте базы виртуальными только потому, что это «решает» насущную проблему; сделайте базу виртуальной, потому что это подходит для проектируемой иерархии. Пример Бьерна: если у вас есть класс списка Node в качестве основы (интрузивный список), вы должны написать struct X : Node ... и struct Y : Node, когда хотите создать список X и список Y. Если у вас есть производный класс, который одновременно является X и Y, вам нужны два Node класса, а не один, чтобы вы могли использовать свой список X и список Y.
Смотрите dupe: Нужно ли ВСЕ виртуальные функции реализовывать в производных классах?