У меня такой код:
class Ref {<undefined>};
Ref refObjectForA, refObjectForB;
class Base
{
public:
Base(const Ref & iRef) : _ref(iRef) {}
virtual ~Base() {}
const Ref & ref;
};
class A: public Base
{
public:
A() : Base(refObjectForA) {}
virtual ~A() {}
};
class B: public A
{
public:
B() : Base(refObjectForB) {} // won't compile: Base is not direct base of B
virtual ~B() {}
};
Поскольку атрибут является ссылкой, я думаю, что я могу установить его только в конструкторе, поэтому мне нужно вызвать Base
конструктор в B()
,
Я нашел два способа: предоставить «прямой» конструктор в A
(но это подразумевает добавление кода во все классы, которые может быть быть унаследованным):
A(const Ref& iRef): Base(iRef)
или используя виртуальное наследование:
class A: public virtual Base
Второй вариант позволяет более простой код в B
реализация, но мне интересно, если я неправильно использую виртуальное наследование в уродливой уловке или это допустимый вариант использования.
Одно из «неожиданных» поведений, которое я обнаружил, заключается в том, что невозможно static_cast
Base
указатель на B
указатель из-за виртуального наследования.
Кроме того, мне также интересно, почему это работает (я имею в виду, почему B().ref == refObjectForB
): Я думаю, что неявный вызов по умолчанию A()
конструктор в B()
переписал бы ref
атрибут после явного Base
конструктор, но, возможно, это не так с виртуальным наследованием.
Лучший вариант, который я могу увидеть, если вы хотите придерживаться иерархии наследования, — это реализовать защищенные конструкторы, используя ссылку, которую они передадут Base
учебный класс. Создание защищенного конструктора гарантирует, что (окончательный) экземпляр не может быть создан с использованием этого конструктора, поэтому он будет использоваться только в подклассах для инициализации суперклассов.
С некоторыми более или менее уродливыми и опасными макросами это легко написать:
#define REF_FORWARD_CTOR(ClassName, DirectSuperClassName) \
protected: ClassName(class Ref &r) : DirectSuperClassName(r) {} \
public:
class A : public Base
{
REF_FORWARD_CTOR(A, Base)
public:
A() : Base(refObjectForA) {} // normal ctor
};
class B : public A
{
REF_FORWARD_CTOR(B, A)
public:
B() : A(refObjectForB) {} // normal ctor
};
Альтернативный дизайн будет позволять A
а также B
оба происходят (напрямую) из Base
, Затем добавьте функциональные возможности, используя множественное наследование и «общие классы», возможно частные, в зависимости от того, для чего они предназначены:
class Base {
};
class Common {
// common stuff used by both A and B
};
class A : public Base, public Common {
// no further stuff here
};
class B : public Base, public Common {
// add more stuff, or put it in a common super-class again,
// if some classes want to inherit from B again
};
Проблема с этим дизайном заключается в том, что функциональность в Common
не может получить доступ к материалу в A
а также B
, Чтобы решить эту проблему, выполните одно из следующих действий:
A
/ B
в бетоне Common
тип: Common<A>
затем можно использовать A::...
, но не имеет ничего общего с конкретным случаем A
Common
(небольшие накладные расходы)A
а также B
которые вызывают функции в Common<A>
а также Common<B>
обеспечение this
(который является A*
или же B*
через дополнительный параметр.Common
также может быть не шаблонным (без CRTP), если вы перегружаете / шаблонируете эти функции в этом аргументе указателя («CRTP on functions», если вы хотите вызвать его так). Код говорит громче, чем слова. (Пример кода без ваших ссылок и ориентирован на «общий класс».)Да, вы можете технически использовать виртуальное наследование для достижения цели предоставления ссылки в наиболее производный класс.
И да, это дизайнерский запах.
Ваши классы не должны знать ничего, кроме их непосредственных основ (виртуальное наследование является исключением из правила, когда оно необходимо по другим причинам).