Нужно ли множественное наследование?

У меня есть ситуация, как показано ниже:

class A {
virtual void f() { /*some default impl here*/}
virtual void g() { /*some default impl here*/}
};

class B : public A {
virtual void f() { /* do some specific stuff and call parent's f()*/}
virtual void g() { /* do some specific stuff and call parent's g()*/}
};

class C : public A {
virtual void f() { /* do some specific stuff and call parent's f()*/}
virtual void g() { /* do some specific stuff and call parent's g()*/}
};

class mixed /* I don't know how to derive*/ {
virtual void f() { /* call B::f()*/}
virtual void g() { /* call C::g()*/}
};

Я думаю о множественном наследовании здесь. Т.е. сделать mixed происходит от B а также C,
Но есть некоторые известные проблемы (например,
Алмазная проблема).

Другим решением может быть композиция.

Но какое правильное решение, пожалуйста, посоветуйте 🙂

Заранее спасибо!

3

Решение

некоторые известные проблемы (например, проблема Алмаза).

Во-первых: нет ромбовидного рисунка, если вы не создадите его явно.

 class mixed: public B, public C

Это сделает смешанное наследование от B и C. Каждый из них имеет свой собственный явный A (без ромба).

Поскольку и B, и C имеют виртуальные члены, производные от A, становится неоднозначно, какой из них следует называть, но вы решили это, имея явные определения всех виртуальных mixed (так что проблема решена).

  void mixed::f() { B::f();} // works fine.

Теперь даже если вы явно создаете бриллиант.

Примечание: ромбовидный рисунок не отображается нормально. Алмазный рисунок — это дизайнерское решение, которое вы должны эксплицитно make и вы используете его для решения определенных типов проблем (используя виртуальное наследование).

class B: public virtual A ...
class C: public virtual A ...
class mixed: public B, public C ...

У тебя все еще нет проблем. Так как mixed::f() использует только B ветвь (а затем А). В то время как mixed::g() использует только C ветвь (а затем А).

Даже если A имеет свое собственное состояние (хотя это, вероятно, плохая идея, обычно лучше использовать интерфейсы в качестве виртуального базового класса), то у нас нет проблем, потому что mixed::f() а также mixed::g() вызывать функцию только у одного ребенка (проблемы начинают возникать, если они вызывают обе стороны и состояние A мутирует обоими звонками.

Другим решением может быть композиция.

Это также будет работать.

 class mixed: public A
{
B    b;
C    c;
public:
virtual void f() override {b.f();}
virtual void g() override {c.g();}
....
};

Но каково правильное решение

Там нет правильного решения. Это будет во многом зависеть от деталей, которые вы не упомянули (например, детали A).

НО общий совет состоит в том, чтобы отдавать предпочтение композиции, а не наследованию, но это только общие рекомендации, которые всегда сводятся к решаемой проблеме.

4

Другие решения

Проблема в том, что каждый метод должен «делать вещи» перед вызовом родителя.

Одним из решений было бы иметь A а также B в качестве членов в mixed учебный класс. Тогда вы можете контролировать, что вам нужно делать с ними в mixed::f() а также mixed::g()

Если вам нужно, вы можете создать базовый класс base с чисто виртуальный функции f(), а также g(), mixed может наследовать от этого, и так мог A, B а также C, Вы намекаете на эту возможность, когда обсуждаете состав.

3

Я думаю, вы можете искать что-то вроде этого. (Извините, это большая куча кода, но это действительно просто.)

#include <iostream>

struct A
{
virtual void
f()
{
std::cout << __PRETTY_FUNCTION__ << '\n';
}

virtual void
g()
{
std::cout << __PRETTY_FUNCTION__ << '\n';
}

// Don't forget the virtual destructor.
virtual ~A() noexcept = default;
};

struct B : virtual A
{
virtual void
f() override
{
std::cout << __PRETTY_FUNCTION__ << '\n';
A::f();
}

virtual void
g() override
{
std::cout << __PRETTY_FUNCTION__ << '\n';
A::g();
}
};

struct C : virtual A
{
virtual void
f() override
{
std::cout << __PRETTY_FUNCTION__ << '\n';
A::f();
}

virtual void
g() override
{
std::cout << __PRETTY_FUNCTION__ << '\n';
A::g();
}
};

struct D : virtual B, virtual C
{
virtual void
f() override
{
std::cout << __PRETTY_FUNCTION__ << '\n';
B::f();
}

virtual void
g() override
{
std::cout << __PRETTY_FUNCTION__ << '\n';
C::g();
}
};

int
main()
{
D d {};
d.f();
std::cout << '\n';
d.g();
}

override это функция C ++ 11, позволяющая проверять компилятор, который вы на самом деле переопределяете. Использование это хорошая практика, но не обязательно, если ваш компилятор не поддерживает его. __PRETTY_FUNCTION__ является расширением GCC для получения строкового литерала, который называет подпись текущей функции. Стандарт C ++ имеет __func__ но это менее полезно здесь. Вы можете ввести строки самостоятельно, если ваш компилятор не имеет функции, сравнимой с __PRETTY_FUNCTION__,

Выход:

virtual void D::f()
virtual void B::f()
virtual void A::f()

virtual void D::g()
virtual void C::g()
virtual void A::g()

Это работает, но я не рассматриваю этот красивый код. Композиция, вероятно, будет лучшим решением здесь.

3

Вот альтернатива виртуальному наследованию: использование CRTP смешать в функциональности B а также C в M, разделяя общее A, без накладные расходы vtables.

#include <iostream>

struct A
{
int a;
};

template <typename T>
struct B
{
A *get_base_a() {return static_cast<T*>(this);}
void print_a() {std::cout << get_base_a()->a << '\n';}
};

template <typename T>
struct C
{
A *get_base_a() {return static_cast<T*>(this);}
void print_a() {std::cout << get_base_a()->a << '\n';}
};

struct M : A, B<M>, C<M>
{
};

int main()
{
M m;
m.a = 1;
m.B::print_a();
m.C::print_a();
return 0;
}

Обратите внимание, что вы не сможете пройти M* к функции, которая ожидает B* или C*,

1
По вопросам рекламы [email protected]