10 ответов:
виртуальные базовые классы, используемые в виртуальном наследовании, - это способ предотвращения появления нескольких "экземпляров" данного класса в иерархии наследования при использовании множественного наследования.
рассмотрим следующий сценарий:
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. Таким образом, у вас есть два "экземпляра" (для не хватает лучшего выражения) А.
когда у вас есть этот сценарий, у вас есть возможность двусмысленности. Что происходит, когда вы делаете это:
D d; d.Foo(); // is this B's Foo() or C's Foo() ??виртуальное наследование есть, чтобы решить эту проблему. Когда вы указываете virtual при наследовании ваших классов, вы говорите компилятору, что вам нужен только один экземпляр.
class A { public: void Foo() {} }; class B : public virtual A {}; class C : public virtual A {}; class D : public B, public C {};это означает, что существует только один "экземпляр" a, включенный в иерархию. Отсюда
D d; d.Foo(); // no longer ambiguousнадеюсь, что помогает как мини-резюме. Для получения дополнительной информации прочтите этой и этой. Хороший пример также доступен здесь.
о макете памяти
как Примечание стороны, проблема с ужасным Алмазом заключается в том, что базовый класс присутствует несколько раз. Таким образом, с регулярным наследованием вы считаете, что у вас есть:
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родительB),C::m_iValueне будет изменен (то естьA::m_iValueродительC).вот где виртуальное наследование пригодится, так как с ним вы вернетесь к истинному алмазному макету, а не только к одному
foo()только метод, но и один и только одинm_iValue.что может пойти не так?
представьте себе:
Aимеет некоторые основные функции.Bдобавляет к нему какой-то классный массив данных (например)Cдобавляет к нему некоторые интересные функции, такие как шаблон наблюдателя (например, наm_iValue).Dнаследует отBиC, и, таким образом, сA.С нормальным наследования, изменение
m_iValueСDнеоднозначно, и это должно быть решено. Даже если это так, есть дваm_iValuesвнутриD, так что вам лучше запомнить это и обновить два одновременно.с виртуальным наследованием, изменение
m_iValueСDОК... Но... Допустим, что у вас естьD. Через егоCинтерфейс, вы прикрепили наблюдателя. И через егоBинтерфейс, вы обновляете прохладный массив, который имеет побочный эффект непосредственного измененияm_iValue...смена
m_iValueделается напрямую (без использования метода виртуального доступа), наблюдатель "слушает" черезCне будет вызван, потому что код, реализующий прослушивание, находится вCиBоб этом не знает...вывод
если у вас есть алмаз в вашей иерархии, это означает, что у вас есть 95%, чтобы сделать что-то не так с указанной иерархией.
объяснение множественного наследования с виртуальными базами требует знания объектной модели C++. И объяснение темы четко лучше всего делать в статье, а не в поле для комментариев.
лучшим, читаемым объяснением, которое я нашел, что решило все мои сомнения по этому вопросу, была эта статья:http://www.phpcompiler.org/articles/virtualinheritance.html
вам действительно не нужно будет читать что-либо еще по этой теме (Если вы не являетесь автором компилятора) после прочтения этого...
виртуальный базовый класс-это класс, который нельзя инстанцировать нельзя создайте из него прямой объект.
Я думаю, что вы путаете две очень разные вещи. Виртуальное наследование-это не то же самое, что абстрактный класс. Виртуальное наследование изменяет поведение вызовов функций; иногда оно разрешает вызовы функций, которые в противном случае были бы неоднозначными, иногда оно переносит обработку вызовов функций в класс, отличный от ожидаемого в невиртуальном наследовании.
Я хотел бы добавить к добрым разъяснениям 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 Lite FTW.
короче говоря, он часто используется в сценариях множественного наследования, где формируется" Алмазная " иерархия. Виртуальное наследование затем нарушит двусмысленность, созданную в нижнем классе, когда вы вызываете функцию в этом классе, и функция должна быть разрешена либо в классе D1, либо в D2 выше этого нижнего класса. Смотрите FAQ item для диаграммы и деталей.
Он также используется в делегация сестра, мощная функция (хотя и не для слабонервных). Смотрите этой FAQ.
Также см. пункт 40 в действующем 3-м издании C++ (43 во 2-м издании).
Алмаз наследование запускаемый пример использования
в этом примере показано, как использовать виртуальный базовый класс в типичном сценарии: для решения проблемы наследования алмазов.
#include <cassert> class A { public: A(){} A(int i) : i(i) {} int i; virtual int f() = 0; virtual int g() = 0; virtual int h() = 0; }; class B : public virtual A { public: B(int j) : j(j) {} int j; virtual int f() { return this->i + this->j; } }; class C : public virtual A { public: C(int k) : k(k) {} int k; virtual int g() { return this->i + this->k; } }; class D : public B, public C { public: D(int i, int j, int k) : A(i), B(j), C(k) {} virtual int h() { return this->i + this->j + this->k; } }; int main() { D d = D(1, 2, 4); assert(d.f() == 3); assert(d.g() == 5); assert(d.h() == 7); }
виртуальные классы не то же, что и виртуальное наследование. Виртуальные классы вы не можете создать экземпляр, виртуальное наследование - это совсем другое.
Википедия описывает это лучше, чем я могу. http://en.wikipedia.org/wiki/Virtual_inheritance
Comments