Что такое виртуальный базовый класс в C++?
-
09-06-2019 - |
Вопрос
Я хочу знать, что такое "виртуальный базовый класс" и что это значит.
Позвольте мне показать пример:
class Foo
{
public:
void DoSomething() { /* ... */ }
};
class Bar : public virtual Foo
{
public:
void DoSpecific() { /* ... */ }
};
Решение
Виртуальные базовые классы, используемые при виртуальном наследовании, — это способ предотвращения появления нескольких «экземпляров» данного класса в иерархии наследования при использовании множественного наследования.
Рассмотрим следующий сценарий:
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
Надеюсь, это поможет в качестве мини-резюме.Для получения дополнительной информации прочитайте этот и этот.Хороший пример также доступен здесь.
Другие советы
О структуре памяти
Кстати, проблема Dreaded Diamond заключается в том, что базовый класс присутствует несколько раз.Итак, при обычном наследовании вы считаете, что у вас есть:
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
Вам действительно не нужно будет читать что-либо еще по этой теме (если вы не являетесь автором компилятора) после прочтения этого...
Виртуальный базовый класс - это класс, который не может быть создан:Вы не можете создать прямой объект из него.
Мне кажется, вы путаете две совершенно разные вещи.Виртуальное наследование — это не то же самое, что абстрактный класс.Виртуальное наследование изменяет поведение вызовов функций;иногда он разрешает вызовы функций, которые в противном случае были бы неоднозначными, иногда он откладывает обработку вызовов функций на класс, отличный от того, который можно было бы ожидать при невиртуальном наследовании.
Я хотел бы добавить к любезным разъяснениям О.Дж.
Виртуальное наследование не обходится без цены.Как и во всех виртуальных вещах, вы теряете производительность.Есть способ обойти этот недостаток производительности, возможно, менее элегантный.
Вместо того, чтобы разбивать ромб путем виртуального вывода, вы можете добавить к ромбу еще один слой, чтобы получить что-то вроде этого:
B
/ \
D11 D12
| |
D21 D22
\ /
DD
Ни один из классов не наследуется виртуально, все наследуются публично.Классы D21 и D22 затем скроют виртуальную функцию f(), которая неоднозначна для DD, возможно, объявив функцию частной.Каждый из них определял бы функцию-оболочку f1() и f2() соответственно, каждая из которых вызывала локальный (частный) класс f(), разрешая таким образом конфликты.Класс DD вызывает f1(), если ему нужен D11::f(), и f2(), если ему нужен D12::f().Если вы определите встроенные оболочки, вы, вероятно, получите нулевые накладные расходы.
Конечно, если вы можете изменить D11 и D12, вы сможете проделать тот же трюк и внутри этих классов, но зачастую это не так.
В дополнение к тому, что уже было сказано о множественном и виртуальном наследовании, в журнале доктора Добба есть очень интересная статья: Множественное наследование считается полезным
Вы немного путаете.Не знаю, не путаете ли вы какие-то понятия.
У вас нет виртуального базового класса в вашем ОП.У вас просто есть базовый класс.
Вы сделали виртуальное наследование.Обычно это используется при множественном наследовании, чтобы несколько производных классов использовали члены базового класса, не воспроизводя их.
Базовый класс с чисто виртуальной функцией не может быть создан.для этого требуется синтаксис, который понимает Пол.Обычно он используется для того, чтобы производные классы определяли эти функции.
Я не хочу больше ничего объяснять по этому поводу, потому что не совсем понимаю, о чем вы спрашиваете.
Это означает, что вызов виртуальной функции будет перенаправлен в «правильный» класс.
С++ Часто задаваемые вопросы Lite Кстати.
Короче говоря, он часто используется в сценариях множественного наследования, где формируется «ромбовидная» иерархия.Виртуальное наследование тогда разрушит неоднозначность, созданную в нижнем классе, когда вы вызываете функцию в этом классе, и функцию необходимо разрешить либо в класс D1, либо в класс D2 выше этого нижнего класса.См. Пункт часто задаваемых вопросов для схемы и подробностей.
Он также используется в сестра-делегация, мощная функция (хотя и не для слабонервных).Видеть этот ЧАСТО ЗАДАВАЕМЫЕ ВОПРОСЫ.
См. также пункт 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