Неабстрактные, но не содержащие состояния классы так же безопасны для множественного наследования, как и чистые абстрактные? - PullRequest
4 голосов
/ 04 апреля 2019

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

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

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

Но с учетом этого объяснения: если неоднозначность возникает только из форм «состояния» (например, членов данных, переменных статических функций), тогда класс не является неабстрактным без состояния (возможно, даже со всеми «конечными» методами) одинаково безопасно быть базовым классом в иерархии множественного наследования?

Какую возможную проблему я пропускаю?

PS: В случае, если ответ «Если нет виртуального метода, тогда вы могли бы использовать композицию в любом случае»: Помимо академического интереса, у меня есть случай, когда мне нужно свойство функций-членов, чтобы иметь возможность свободного затенения, глобальные функции в стиле C, поэтому я не могу получить к ним доступ через объект указатель на композицию.

1 Ответ

1 голос
/ 06 апреля 2019

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

Виртуальное наследование также интеллектуально обрабатывает переопределение:

struct B {
  virtual ~B()=default;
  virtual void f()/*=0*/;
};
struct X : virtual B {};
struct Y : virtual B {
  void f() override;
};
struct D : X,Y {};

B& b();
void g() {
  b().f();    // calls Y::f
}

Не имеет значения, является ли B::f чисто виртуальным или нет.

Так что отсутствие гражданства не является важной частью. Более того, если база не имеет состояния, наличие final членов предотвратит наиболее очевидный вариант использования заглушек по умолчанию. (Единственная другая возможность - зависеть от значения из this; в противном случае функции могут быть статическими.) Поэтому я бы не стал поощрять создание класса без состояния , поскольку это предназначен для виртуального наследования (а не просто потому, что минимизация состояния обычно является хорошей идеей).

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

...