Я пытаюсь разработать иерархию классов на 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 foo
s. Возможно, вы захотите опубликовать ответ, потому что, насколько я понимаю, предложенный обман не решает эту проблему.
Вздох. Пожалуйста, игнорируйте ссылку «доминирование» в моем комментарии «Возобновлено». Страница, на которую он указывает, — ерунда.
Если вы хотите, чтобы 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
, а правило доминирования гласит, что в Derived
Intermediate2::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: Нужно ли ВСЕ виртуальные функции реализовывать в производных классах?