Вопрос

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top