Подтвердить что ты не робот

Наследовать от нескольких частичных реализаций абстрактного базового класса?

Возможно ли иметь несколько частичных реализаций абстрактного интерфейса, а затем собрать эти частичные реализации в один конкретный класс используя множественную наследование?

У меня есть следующий пример кода:

#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()
4b9b3361

Ответ 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().