Что такое виртуальный базовый класс В C++?
Я хочу знать, что это "виртуальный базовый класс" и что это означает.
позвольте мне показать пример:
class Foo
{
public:
void DoSomething() { /* ... */ }
};
class Bar : public virtual Foo
{
public:
void DoSpecific() { /* ... */ }
};
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()
, у вас есть проблема неоднозначности. Но ... --45-->реальные проблема возникает, когда вы хотите использовать переменную-член 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()
только метод, но и один и только один!--5-->.
что может пойти не так?
представьте себе:
-
A
имеет некоторые основные функции. -
B
добавляет к нему какой-то классный массив данных (например) -
C
добавляет к нему некоторые интересные функции, такие как шаблон наблюдателя (например, onm_iValue
). -
D
наследует отB
иC
, и, таким образом, сA
.
С нормальным наследования, изменение m_iValue
С D
неоднозначно, и это должно быть решено. Даже если и так, их два!--26--> внутри 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, то вы можете сделать тот же трюк внутри этих классов, но часто это не так.
в дополнение к тому, что уже было сказано о множественном и виртуальном наследовании, есть очень интересная статья о журнале доктора Добба:Множественное Наследование Считается Полезным
ты немного путаешь. Я не знаю, не путаете ли вы некоторые понятия.
У вас нет виртуального базового класса в вашем OP. У тебя просто базовый класс.
вы сделали виртуальное наследование. Это обычно используется в множественном наследовании, так что несколько производных классов используют члены базового класса без их воспроизведения.
базовый класс с чистой виртуальной функцией не создается. это требует синтаксиса, который получает Павел. Он обычно используется так, что производные классы должны определить эти функции.
Я не хочу больше объяснять об этом, потому что я не совсем понимаю, о чем вы спрашиваете.
Это означает, что вызов виртуальной функции будет перенаправлен в класс" right".
C++ FAQ Lite FTW.
короче говоря, он часто используется в сценариях множественного наследования, где формируется иерархия" Алмаз". Виртуальное наследование затем нарушит двусмысленность, созданную в нижнем классе, когда вы вызываете функцию в этом классе, и функция должна быть разрешена в класс D1 или D2 выше этого нижнего класса. Вижу пункт FAQ для диаграммы и деталей.
Он также используется в делегация сестра, мощная функция (хотя и не для слабонервных). См.этой 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