Виртуальные базовые классы, используемые в виртуальном наследовании, являются способом предотвратить несколько "экземпляров" данного класса, появляющегося в иерархии наследования при использовании множественного наследования.
Рассматривают следующий сценарий:
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, которая помогает как мини-сводка. Для получения дополнительной информации имейте чтение это и это . Хороший пример также доступен здесь .
Как примечание стороны, проблема со Страшным Ромбом состоит в том, что базовый класс присутствует многократно. Таким образом с регулярным наследованием, Вы полагаете, что имеете:
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%, чтобы сделать что-то не так с упомянутой иерархией.
А виртуальный базовый класс является классом, который нельзя инстанцировать: Вы не можете создать прямое дополнение из него.
я думаю, что Вы путаете две совсем других вещи. Виртуальное наследование не является тем же самым как абстрактным классом. Виртуальное наследование изменяет поведение вызовов функции; иногда это разрешает вызовы функции, которые иначе были бы неоднозначны, иногда это задерживает обработку вызова функции к классу, кроме которого ожидал бы в невиртуальном наследовании.
В дополнение к тому, что было уже сказано о нескольких и виртуальном наследовании (наследованиях), существует очень интересная статья о Журнале доктора Dobb: Множественное наследование, Продуманное Полезный
Виртуальные классы не то же как виртуальное наследование. Виртуальные классы, которых Вы не можете инстанцировать, виртуальное наследование, являются чем-то еще полностью.
Википедия описывает его лучше, чем я могу. http://en.wikipedia.org/wiki/Virtual_inheritance
Вы немного сбиваете с толку. Я' не знаю, перепутываете ли Вы некоторые понятия.
у Вас нет виртуального базового класса в Вашем OP. У Вас просто есть базовый класс.
Вы сделали виртуальное наследование. Это обычно используется во множественном наследовании так, чтобы несколько производных классов использовали членов базового класса, не воспроизводя их.
базовый класс А с чистой виртуальной функцией не быть инстанцированным. это требует синтаксиса, который достигает Paul. Это обычно используется так, чтобы производные классы определили те функции.
я не хочу объяснять больше об этом, потому что я не полностью получаю то, что Вы спрашиваете.
Я хотел бы добавить к добрым разъяснениям OJ.
Виртуальное наследование не происходит без цены. Как со всеми виртуальными вещами, Вы поражали производительность. Существует путь вокруг этого хита производительности, который возможно менее изящен.
Вместо того, чтобы повредить ромб путем получения фактически, можно добавить другой слой к ромбу, для получения чего-то вроде этого:
B
/ \
D11 D12
| |
D21 D22
\ /
DD
Ни один из классов не наследовался фактически, все наследовались публично. Классы D21 и D22 тогда скроют виртуальную функцию f (), который неоднозначен для DD, возможно, путем объявления частной функции. Они каждый определили бы функцию обертки, f1 () и f2 () соответственно, каждый звонящий локальный для класса (частный) f (), таким образом разрешив конфликты. DD класса называет f1 (), если это хочет D11:: f () и f2 (), если это хочет D12:: f (). При определении оберток, встроенных, Вы, вероятно, доберетесь о нуле наверху.
, Конечно, если можно изменить D11 и D12 тогда, можно сделать тот же прием в этих классах, но часто дело не в этом.
Это означает, что вызов к виртуальной функции будет переведен к "правильному" классу.
C++ FAQ, Облегченный FTW.
Короче говоря, это часто используется в сценариях множественного наследования, где "ромбовидная" иерархия формируется. Виртуальное наследование тогда повредит неоднозначность, созданную в нижнем классе, когда Вы вызовете функцию в том классе, и функция должна быть разрешена к любому классу D1 или D2 выше того нижнего класса. Посмотрите объект FAQ для схемы и деталей.
Это также используется в родственная делегация , мощная функция (хотя не для слабонервных). См. этот FAQ.
Также посмотрите Объект 40 в Эффективном C++ 3-й выпуск (43 в 2-м выпуске).