Что такое виртуальный базовый класс в C ++?

Я хочу знать, что такое « виртуальный базовый класс » и что это значит.

Позвольте мне показать пример:

class Foo
{
public:
    void DoSomething() { /* ... */ }
};

class Bar : public virtual Foo
{
public:
    void DoSpecific() { /* ... */ }
};
385
задан 14.05.2020, 14:23

8 ответов

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

Рассматривают следующий сценарий:

class A { public: void Foo() {} };
class B : public A {};
class C : public A {};
class D : public B, public C {};

вышеупомянутая иерархия классов приводит к "страшному ромбу", который похож на это:

  A
 / \
B   C
 \ /
  D

экземпляр D будет составлен из B, который включает A и C, который также включает A. Таким образом, у Вас есть два "экземпляра" (из-за отсутствия лучшего выражения) A.

, Когда у Вас есть этот сценарий, у Вас есть возможность неоднозначности. Что происходит, когда Вы делаете это:

D d;
d.Foo(); // is this B's Foo() or C's Foo() ??

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

class A { public: void Foo() {} };
class B : public virtual A {};
class C : public virtual A {};
class D : public B, public C {};

Это означает, что существует только один "экземпляр" включенного в иерархию. Следовательно

D d;
d.Foo(); // no longer ambiguous

Hope, которая помогает как мини-сводка. Для получения дополнительной информации имейте чтение это и это . Хороший пример также доступен здесь .

519
ответ дан 14.05.2020, 14:28
  • 1
    это означает, что мы должны всегда использовать виртуальный???????????? – Bohdan 14.05.2020, 14:29
  • 2
    @OJ. почему нет? Они веселы:) – Bohdan 14.05.2020, 14:30
  • 3
    @Bohdan не это не делает:) – OJ. 14.05.2020, 14:30

О расположении памяти

Как примечание стороны, проблема со Страшным Ромбом состоит в том, что базовый класс присутствует многократно. Таким образом с регулярным наследованием, Вы полагаете, что имеете:

  A
 / \
B   C
 \ /
  D

, Но в расположении памяти, Вы имеете:

A   A
|   |
B   C
 \ /
  D

Это объясняет почему, когда вызов D::foo(), у Вас есть проблема неоднозначности. Но реальный проблема возникает, когда Вы хотите использовать членскую переменную A. Например, скажем, мы имеем:

class A
{
    public :
       foo() ;
       int m_iValue ;
} ;

, Когда Вы попытаетесь получить доступ m_iValue от D, компилятор выступит, потому что в иерархии, он будет видеть два m_iValue, не один. И если Вы измените тот, скажем, B::m_iValue (который является A::m_iValue родитель [1 110]), [то 1111] не будет изменен (который является A::m_iValue родитель [1 113]).

Это - то, куда виртуальное наследование происходит удобное, как с ним, Вы возвратитесь к истинному ромбовидному расположению, с не только один foo() метод только, но также и один и только один m_iValue.

, Что могло пойти не так, как надо?

Вообразите:

  • A имеет некоторую основную характеристику.
  • B добавляет к нему, некоторый прохладный массив данных (например)
  • C добавляет к нему некоторую замечательную опцию как шаблон "наблюдатель" (например, на [1 119]).
  • D наследовался от [1 121] и C, и таким образом от [1 123].

С нормальным наследованием, изменяя m_iValue от [1 125] неоднозначно, и это должно быть разрешено. Даже если это, существует два m_iValues внутреннее D, таким образом, необходимо помнить, что и обновляют два одновременно.

С виртуальным наследованием, изменяя m_iValue от [1 129] в порядке... Но... Скажем, то, что Вы имеете D. Через C интерфейс, Вы присоединили наблюдателя. И через B интерфейс, Вы обновляете прохладный массив, который имеет побочный эффект прямого изменения m_iValue...

, Поскольку изменение [1 134] сделано непосредственно (не используя виртуальный метод доступа), наблюдателя, "слушающего" до [1 135], не позвонят, потому что код, реализовывая слушание находится в [1 136], и B не знает об этом...

Заключение

, Если у Вас есть ромб в Вашей иерархии, это означает, что у Вас есть 95%, чтобы сделать что-то не так с упомянутой иерархией.

239
ответ дан 14.05.2020, 14:24
  • 1
    @Chris Dodd: Не точно. То, что происходит с m_iValue, произошло бы с любым символом (, например, определение типа, членская переменная, функция членства, бросок к базовому классу, и т.д. ). Это действительно является кратным проблема наследования, проблема, что пользователи должны знать, чтобы использовать множественное наследование правильно, вместо того, чтобы идти Java путь и завершить " Множественное наследование является 100%-м злом, let' s делают это с interfaces". – paercebal 14.05.2020, 14:25
  • 2
    Ваш ' что могло пойти wrong' происходит из-за прямого доступа к основному участнику, не из-за множественного наследования. Избавьтесь от ' B" и у Вас есть та же проблема. Основное правило: ' если не частный, это должен быть virtual' избегает проблемы. m_iValue не является виртуальным и для этого должен быть частным – Chris Dodd 14.05.2020, 14:25

А виртуальный базовый класс является классом, который нельзя инстанцировать: Вы не можете создать прямое дополнение из него.

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

9
ответ дан 14.05.2020, 14:24
  • 1
    Спасибо! Наконец четкий ответ. – NomenNescio 22.08.2012, 19:01

В дополнение к тому, что было уже сказано о нескольких и виртуальном наследовании (наследованиях), существует очень интересная статья о Журнале доктора Dobb: Множественное наследование, Продуманное Полезный

4
ответ дан 14.05.2020, 14:25

Виртуальные классы не то же как виртуальное наследование. Виртуальные классы, которых Вы не можете инстанцировать, виртуальное наследование, являются чем-то еще полностью.

Википедия описывает его лучше, чем я могу. http://en.wikipedia.org/wiki/Virtual_inheritance

0
ответ дан 14.05.2020, 14:26
  • 1
    Нет такой вещи как " виртуальный classes" в C++. Существуют однако " виртуальная основа classes" которые являются " virtual" относительно данного наследования. То, что Вы отсылаете, - то, что официально называют " краткий обзор classes". – Luc Hermitte 14.05.2020, 14:28

Вы немного сбиваете с толку. Я' не знаю, перепутываете ли Вы некоторые понятия.

у Вас нет виртуального базового класса в Вашем OP. У Вас просто есть базовый класс.

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

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

я не хочу объяснять больше об этом, потому что я не полностью получаю то, что Вы спрашиваете.

1
ответ дан 14.05.2020, 14:27
  • 1
    Спасибо. Отредактированный мой ответ. – Amiram Korach 22.08.2012, 18:20
  • 2
    " основа class" это используется в виртуальном наследовании, становится " виртуальная основа class" (в контексте того точного наследования). – Luc Hermitte 14.05.2020, 14:28

Я хотел бы добавить к добрым разъяснениям OJ.

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

Вместо того, чтобы повредить ромб путем получения фактически, можно добавить другой слой к ромбу, для получения чего-то вроде этого:

   B
  / \
D11 D12
 |   |
D21 D22
 \   /
  DD

Ни один из классов не наследовался фактически, все наследовались публично. Классы D21 и D22 тогда скроют виртуальную функцию f (), который неоднозначен для DD, возможно, путем объявления частной функции. Они каждый определили бы функцию обертки, f1 () и f2 () соответственно, каждый звонящий локальный для класса (частный) f (), таким образом разрешив конфликты. DD класса называет f1 (), если это хочет D11:: f () и f2 (), если это хочет D12:: f (). При определении оберток, встроенных, Вы, вероятно, доберетесь о нуле наверху.

, Конечно, если можно изменить D11 и D12 тогда, можно сделать тот же прием в этих классах, но часто дело не в этом.

6
ответ дан 14.05.2020, 14:28
  • 1
    Слова благодарности - поиск ответа в течение многих часов. – markp3rry 24.04.2014, 13:25

Это означает, что вызов к виртуальной функции будет переведен к "правильному" классу.

C++ FAQ, Облегченный FTW.

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

Это также используется в родственная делегация , мощная функция (хотя не для слабонервных). См. этот FAQ.

Также посмотрите Объект 40 в Эффективном C++ 3-й выпуск (43 в 2-м выпуске).

1
ответ дан 14.05.2020, 14:29

Теги

Похожие вопросы