C++, бриллиантовое наследование, где/когда нужно реализовать чистые виртуальные машины?

C++: у меня есть базовый класс A с чистой виртуальной функцией f(), а затем два класса B и C виртуально наследуются от A, и класс D, который наследуется как от B, так и от C (типичная ромбовидная структура):

   A f() = 0
 v/ \v
 B   C
  \ /
   D

Где и когда нужно реализовать f() = 0 в следующих случаях?

  1. И B, и C также имеют чисто виртуальные функции (-> должны ли абстрактные классы должны реализовывать унаследованные чистые виртуальные функции?)
  2. Только один из них (B XOR C) имеет чисто виртуальную функцию (-> другой все еще должен реализовывать f()?)
  3. Ни B, ни C не имеют собственных чистых виртуальных машин (-> возможный способ пропустить реализацию в B и C и «пропустить» ее в D?)
  4. В каком из трех вышеприведенных случаев D нужно реализовать f()? В каких случаях D необязательно может реализовать f()? В каких случаях D не может реализовать f()?

Есть ли какие-либо другие общие предложения для такого рода проблем?

Спасибо.


person blubberbernd    schedule 16.09.2011    source источник
comment
@Tux-D Что? Возможно, это возможный случай для реального проекта, над которым я работаю. Я все еще думаю о нескольких подходах.   -  person blubberbernd    schedule 16.09.2011
comment
Обычно я считаю наследование алмазов плохим замыслом. Конечно, это возможно, но обычно есть лучший способ. Множественное наследование лучше всего подходит для чисто виртуальных базовых классов.   -  person AJG85    schedule 16.09.2011
comment
Я согласен с AJG85. Если у вас ромбовидный образец наследования, вам следует переосмыслить свой дизайн.   -  person Rob K    schedule 16.09.2011
comment
Обычно я считаю ромбовидное наследование плохим дизайном. Почему? с чистыми виртуальными базовыми классами. Что это?   -  person curiousguy    schedule 01.11.2011


Ответы (4)


И B, и C также имеют чисто виртуальные функции (-> должны ли абстрактные классы реализовывать унаследованные чистые виртуальные функции?)

Да D ДОЛЖЕН реализовать ВСЕ унаследованные чисто виртуальные функции.
Если только класс не реализует все чисто виртуальные функции классов, от которых он производен сам класс действует как абстрактный класс.

Только один из них (B XOR C) имеет чисто виртуальную функцию (-> должен ли другой реализовывать f()?)

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

Ни B, ни C не имеют собственных чистых виртуальных машин (-> возможный способ пропустить реализацию в B и C и «пропустить» ее в D?)

D должен будет реализовать чистые виртуальные функции, которые он наследует через A->B и A-C. Обратите внимание, что в этом случае и B, и C будут абстрактными классами.

В каком из трех вышеприведенных случаев D нужно реализовать f()? В каких случаях D необязательно может реализовать f()? В каких случаях D не может реализовать f()?

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

Вывод:

  1. Класс должен реализовать ВСЕ чистые виртуальные функции, которые он наследует от ВСЕХ своих базовых классов, в противном случае класс станет абстрактным классом.
  2. Виртуальный атрибут наследуется. Если суперкласс объявляет виртуальную функцию, то переопределенная функция в производном классе также является виртуальной и передает виртуальный атрибут всем производным от него классам.

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

Следующее может быть хорошим чтением:

  1. Множественное наследование – часть I
  2. Множественное наследование — часть II
  3. Множественное наследование – часть III
person Alok Save    schedule 16.09.2011
comment
Просто я правильно понимаю: если A имеет чисто виртуальную функцию, а B и C оба не реализуют ее, то они также автоматически абстрактны? - person blubberbernd; 16.09.2011
comment
@Mahesh И если B и C реализуют чистую виртуальную функцию, то D все равно должен ее реализовать? Id не наследует одну из реализаций от B или C? - person blubberbernd; 16.09.2011
comment
@bluebernd: D должен иметь уникальное окончательное переопределение, независимо от того, как вы его реализуете. - person Kerrek SB; 16.09.2011
comment
Если у A есть чистый виртуал, D должен реализовать его, независимо от того, что делают B и C. Если B или C (или оба) не могут его реализовать, D будет абстрактным без его реализации. Если ее реализуют и B, и C, функция неоднозначна в D; D должен реализовать его, чтобы устранить двусмысленность. - person James Kanze; 16.09.2011
comment
Хороший ответ, пока мы не опустимся ниже черты. Виртуальное наследование — это такой же инструмент, как и любой другой, и ромбовидное наследование допустимо во многих случаях. Mixins, очевидно, но см. Barton & Nackman для других случаев. - person James Kanze; 16.09.2011
comment
@James: Вы действительно правы в этом, virtual Inheritance необходим в некоторых случаях, но чаще люди склонны использовать его, когда это на самом деле не требуется. Я должен был выразить это более лучшими словами. У меня было это мягкое чувство с тех пор, как я написал это в ответе. - person Alok Save; 16.09.2011

И B, и C также имеют чисто виртуальные функции (-> должны ли абстрактные классы реализовывать унаследованные чистые виртуальные функции?

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

Только один из них (B XOR C) имеет чисто виртуальную функцию (-> должен ли другой реализовывать f()?)

Поскольку C также является производным от A, он также должен реализовывать f().

Ни B, ни C не имеют собственных чистых виртуальных машин (-> возможный способ пропустить реализацию в B и C и «пропустить» ее в D?)

Вы можете сделать это. Но это предотвращает создание экземпляров только B или C, например -

A *obj = new B(); // Error
A *obj = new C(); // Error

В каком из трех вышеприведенных случаев D нужно реализовать f()? В каких случаях D необязательно может реализовать f()? В каких случаях D не может реализовать f()?

f() можно реализовать только в D, если вы хотите, чтобы все его родительские классы были абстрактными.

person Mahesh    schedule 16.09.2011
comment
Однако ОП никогда не говорит, действительно ли он хочет создать экземпляр B или C. - person Kerrek SB; 16.09.2011
comment
@Kerrek SB - это упомянутая проблема. - person Mahesh; 16.09.2011

Единственное требование состоит в том, что на уровне «листа» (наиболее производном) все имеет реализацию (и, возможно, только одну, иначе может возникнуть неоднозначность, если - в точке доступа к графу - есть несколько реализаций на том же «расстоянии». ".

Таким образом, D должен реализовать все, что еще не было реализовано, или все было реализовано более одного раза разными путями (для устранения неоднозначности). Если что-то - на уровне D - все еще остается нереализованным... D не может быть инициирован, и реализация требуется для и E, производного от D.

– Бриллианты вечны –

person Emilio Garavaglia    schedule 16.09.2011

  1. Нет, абстрактные классы (предположительно B и C) не должны реализовывать унаследованные чистые виртуальные объекты. Однако дочерний класс, такой как D, должен будет сделать это для создания экземпляра.

  2. Нет, снова B и C унаследуют чисто виртуальный метод, и его нужно только переопределить, чтобы сгенерировать окончательный конкретный класс.

  3. Да, это будет реализовано в D одним из двух способов. Если B и C наследуются виртуально, то D реализует это один раз. Если они не наследуются виртуально, тогда D потребуется переопределить обе версии B и C версии f, чтобы быть конкретными.

  4. Если D является абстрактным, ему никогда не нужно реализовывать f. Предполагая, что вы хотите, чтобы это было конкретно, во всех трех случаях вам нужно переопределить f в D. Только если и B, и C переопределяют f, D в этом нет необходимости.

Внимательно посмотрите на свой дизайн и удалите алмазное наследство. В большинстве случаев это будет лучшим способом предотвратить все эти проблемы.

person Mark B    schedule 16.09.2011