У меня есть ситуация, как показано ниже:
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
,
Но есть некоторые известные проблемы (например,
Алмазная проблема).
Другим решением может быть композиция.
Но какое правильное решение, пожалуйста, посоветуйте 🙂
Заранее спасибо!
некоторые известные проблемы (например, проблема Алмаза).
Во-первых: нет ромбовидного рисунка, если вы не создадите его явно.
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).
НО общий совет состоит в том, чтобы отдавать предпочтение композиции, а не наследованию, но это только общие рекомендации, которые всегда сводятся к решаемой проблеме.
Проблема в том, что каждый метод должен «делать вещи» перед вызовом родителя.
Одним из решений было бы иметь A
а также B
в качестве членов в mixed
учебный класс. Тогда вы можете контролировать, что вам нужно делать с ними в mixed::f()
а также mixed::g()
Если вам нужно, вы можете создать базовый класс base
с чисто виртуальный функции f()
, а также g()
, mixed
может наследовать от этого, и так мог A
, B
а также C
, Вы намекаете на эту возможность, когда обсуждаете состав.
Я думаю, вы можете искать что-то вроде этого. (Извините, это большая куча кода, но это действительно просто.)
#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()
Это работает, но я не рассматриваю этот красивый код. Композиция, вероятно, будет лучшим решением здесь.
Вот альтернатива виртуальному наследованию: использование 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*
,