Виртуальное, но не множественное наследование для вызова конструктора деда

У меня такой код:

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 конструктор, но, возможно, это не так с виртуальным наследованием.

3

Решение

Лучший вариант, который я могу увидеть, если вы хотите придерживаться иерархии наследования, — это реализовать защищенные конструкторы, используя ссылку, которую они передадут 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, Чтобы решить эту проблему, выполните одно из следующих действий:

  • Если требуется только статический материал: используйте CRTP указать A / B в бетоне Common тип: Common<A> затем можно использовать A::..., но не имеет ничего общего с конкретным случаем A
  • Если требуется экземпляр: укажите указатель / ссылку в конструкторе Common (небольшие накладные расходы)
  • Соединение первых двух решений: использование CRTP, реализация функций-обёрток в A а также B которые вызывают функции в Common<A> а также Common<B> обеспечение this (который является A* или же B* через дополнительный параметр.
  • То же, что и выше, но класс Common также может быть не шаблонным (без CRTP), если вы перегружаете / шаблонируете эти функции в этом аргументе указателя («CRTP on functions», если вы хотите вызвать его так). Код говорит громче, чем слова. (Пример кода без ваших ссылок и ориентирован на «общий класс».)
4

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

Да, вы можете технически использовать виртуальное наследование для достижения цели предоставления ссылки в наиболее производный класс.

И да, это дизайнерский запах.

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

2

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