Наследовать от нескольких частичных реализаций абстрактного базового класса?
Возможно ли иметь несколько частичных реализаций абстрактного интерфейса, а затем собрать эти частичные реализации в один конкретный класс используя множественную наследование?
У меня есть следующий пример кода:
#include <iostream>
struct Base
{
virtual void F1() = 0;
virtual void F2() = 0;
};
struct D1 : Base
{
void F1() override { std::cout << __func__ << std::endl; }
};
struct D2 : Base
{
void F2() override { std::cout << __func__ << std::endl; }
};
// collection of the two partial implementations to form the concrete implementation
struct Deriv : D1, D2
{
using D1::F1; // I added these using clauses when it first didn't compile - they don't help
using D2::F2;
};
int main()
{
Deriv d;
return 0;
}
Это не удается скомпилировать со следующими ошибками:
main.cpp: In function ‘int main()’:
main.cpp:27:11: error: cannot declare variable ‘d’ to be of abstract type ‘Deriv’
main.cpp:19:8: note: because the following virtual functions are pure within ‘Deriv’:
main.cpp:5:18: note: virtual void Base::F1()
main.cpp:6:18: note: virtual void Base::F2()
Ответы
Ответ 1
Попробуйте наследовать практически от Base
:
struct D1 : virtual Base
{
void F1() override { std::cout << __func__ << std::endl; }
};
struct D2 : virtual Base
{
void F2() override { std::cout << __func__ << std::endl; }
};
Без виртуального наследования ваш сценарий множественного наследования выглядит как наследование от двух отдельных и неполных базовых классов D1
и D2
, ни один из которых не может быть создан.
Ответ 2
Возможно ли иметь несколько частичных реализаций абстрактного интерфейса, а затем собрать эти частичные реализации в один конкретный класс, используя множественную наследование?
Да.
Каждый подобъект базового класса Base
содержит две чистые виртуальные функции. Сколько из этих базовых подобъектов вы хотите в Deriv
?
- Если вам нужен 2-й подкомплекс
Base
базового класса, Deriv::D1::Base
и Deriv::D2::Base
(поэтому преобразования из Deriv&
в Base&
будут неоднозначными), то в Deriv
у вас будет 4 различных виртуальных функции: Deriv::D1::Base::F1()
, Deriv::D1::Base::F2()
, Deriv::D2::Base::F1()
, Deriv::D2::Base::F2()
. Реализованы только первая и последняя, поэтому две средние являются виртуальными: Deriv::D1::Base::F2()
, Deriv::D2::Base::F1()
. У вас есть два совершенно независимых отношения наследования: Deriv
наследует от D1
и Deriv
наследует от D2
.
- Если вам нужен только один подобъект базового класса
Base
Deriv::Base
, то в Deriv
у вас будет только две разные виртуальные функции: Base::F1()
, Base::F2()
.
Не виртуальное наследование в С++ является "конкретным" наследованием, например, сдерживание: struct D : B
означает, что для каждого объекта D
существует ровно один подобъект базового класса B
, так же как struct C { M m; }
означает, что для каждого C
существует только один объект-подкласс M
. Эти отношения взаимно однозначны.
OTOH, виртуальное наследование является более "абстрактным": struct D : virtual B
означает, что для каждого объекта D
связан с подобъектом базового класса B
, но это отношение много-к-одному, например struct C { M &m; }
.
В общем случае для любого производного класса D
(здесь Deriv
) и для любой виртуальной базы B
of D
(здесь Base
) все базовые классы D
, фактически полученные из B
(здесь Deriv::D1
, Deriv::D2
) способствуют переопределению виртуальных функций в базовом классе:
-
Base::F1()
переопределяется Deriv::D1::F1()
-
Base::F2()
переопределяется Deriv::D2::F2()
Не виртуальное и виртуальное наследование - это очень разные отношения наследования, как если бы не виртуальные функции-члены и виртуальная функция вводили различные отношения между функциями с одной и той же сигнатурой в производном и базовом классах (скрывая или переопределяя).
Как и виртуальные и не виртуальные функции, виртуальные и не виртуальные базовые классы должны использоваться в разных ситуациях (и один не "лучше", чем другой вообще).
Как "исправить" ваш код без виртуального наследования?
struct Deriv : D1, D2
{
using D1::F1; // I added these using clauses when it first didn't compile - they don't help
using D2::F2;
};
Да: использование имени управления идентификацией, поэтому они влияют на видимость и проблемы двусмысленности, а не на отмену виртуальных функций.
С вашим оригинальным дизайном, основанным на не виртуальном наследовании, чтобы сделать Deriv
конкретный класс, вам нужно будет явно реализовать подписи F1()
и F2()
виртуальных функций (в , но только с двумя разными сигнатурами), поэтому вам нужны 2 определения функций:
struct Deriv : D1, D2
{
void F1() override { D1::F1(); }
void F2() override { D2::F2(); }
};
Обратите внимание, что Deriv::F1()
переопределяет Deriv::D1::F1()
и Deriv::D2::F1()
.