Что такое виртуальный базовый класс в C ++?

c++ virtual-inheritance

295208 просмотра

10 ответа

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

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

class Foo
{
public:
    void DoSomething() { /* ... */ }
};

class Bar : public virtual Foo
{
public:
    void DoSpecific() { /* ... */ }
};
Автор: popopome Источник Размещён: 12.11.2019 09:59

Ответы (10)


520 плюса

Решение

Виртуальные базовые классы, используемые в виртуальном наследовании, являются способом предотвращения появления нескольких «экземпляров» данного класса в иерархии наследования при использовании множественного наследования.

Рассмотрим следующий сценарий:

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() ??

Виртуальное наследование призвано решить эту проблему. Когда вы указываете 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

Надеюсь, что это поможет в качестве мини-резюме. Для получения дополнительной информации, прочитайте это и это . Хороший пример также доступен здесь .

Автор: OJ. Размещён: 22.08.2008 01:45

240 плюса

О расположении памяти

В качестве примечания, проблема с 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%, чтобы сделать что-то не так с указанной иерархией.

Автор: paercebal Размещён: 21.09.2008 11:06

32 плюса

Для объяснения множественного наследования с помощью виртуальных баз требуется знание объектной модели C ++. И объяснение темы лучше всего делать в статье, а не в поле для комментариев.

Наилучшим понятным объяснением, которое я нашел и которое разрешило все мои сомнения по этому вопросу, была эта статья: http://www.phpcompiler.org/articles/virtualinheritance.html.

Вам действительно не нужно будет читать что-либо еще по этой теме (если вы не писатель компилятора) после прочтения этого ...

Автор: lenkite Размещён: 13.06.2009 10:59

9 плюса

Виртуальный базовый класс - это класс, создание которого невозможно: из него нельзя создать прямой объект.

Я думаю, что вы путаете две совершенно разные вещи. Виртуальное наследование - это не то же самое, что абстрактный класс. Виртуальное наследование изменяет поведение вызовов функций; иногда он разрешает вызовы функций, которые в противном случае были бы неоднозначными, иногда он переносит обработку вызовов функций на класс, отличный от того, который можно ожидать в невиртуальном наследовании.

Автор: wilhelmtell Размещён: 22.08.2008 01:47

6 плюса

Я хотел бы добавить к добрым разъяснениям OJ.

Виртуальное наследство не обходится без цены. Как и со всеми виртуальными вещами, вы получаете удар по производительности. Этот хит производительности может быть менее элегантным.

Вместо того, чтобы разбивать алмаз путем виртуального извлечения, вы можете добавить еще один слой к алмазу, чтобы получить что-то вроде этого:

   B
  / \
D11 D12
 |   |
D21 D22
 \   /
  DD

Ни один из классов не наследует виртуально, все наследуют публично. Классы D21 и D22 будут затем скрывать виртуальную функцию f (), которая неоднозначна для DD, возможно, объявив функцию частной. Каждый из них определил бы функцию-оболочку, f1 () и f2 () соответственно, каждый из которых вызывал бы локальный класс (private) f (), таким образом разрешая конфликты. Класс DD вызывает f1 (), если он хочет D11 :: f () и f2 (), если он хочет D12 :: f (). Если вы определите встроенные оболочки, вы, вероятно, получите около нуля накладных расходов.

Конечно, если вы можете изменить D11 и D12, вы можете сделать один и тот же трюк внутри этих классов, но часто это не так.

Автор: wilhelmtell Размещён: 22.08.2008 02:03

4 плюса

В дополнение к тому, что уже было сказано о множественном и виртуальном наследовании, в журнале д-ра Добба есть очень интересная статья: множественное наследование считается полезным

Автор: Luc Hermitte Размещён: 22.09.2008 12:58

1 плюс

Ты немного сбиваешь с толку. Я не знаю, смешиваете ли вы некоторые понятия.

У вас нет виртуального базового класса в вашем OP. У вас просто есть базовый класс.

Вы сделали виртуальное наследование. Это обычно используется в множественном наследовании, так что несколько производных классов используют члены базового класса, не воспроизводя их.

Базовый класс с чисто виртуальной функцией не создается. для этого требуется синтаксис, который использует Пол. Обычно он используется для того, чтобы производные классы определяли эти функции.

Я не хочу больше об этом объяснять, потому что я не совсем понимаю, что вы спрашиваете.

Автор: Baltimark Размещён: 22.08.2008 01:48

1 плюс

Это означает, что вызов виртуальной функции будет перенаправлен в «правильный» класс.

C ++ FAQ Lite FTW.

Короче говоря, он часто используется в сценариях множественного наследования, где формируется «алмазная» иерархия. Виртуальное наследование разрушит неоднозначность, созданную в нижнем классе, когда вы вызываете функцию в этом классе, и функция должна быть разрешена либо в класс D1, либо в D2 выше этого нижнего класса. См. Пункт часто задаваемых вопросов для диаграммы и деталей.

Это также используется в делегировании сестры , мощная функция (хотя и не для слабонервных). Смотрите этот FAQ.

Также см. Пункт 40 в Effective C ++ 3-е издание (43 в 2-е издание).

Автор: wilhelmtell Размещён: 22.08.2008 01:42

1 плюс

Пример использования бриллиантового наследования

В этом примере показано, как использовать виртуальный базовый класс в типичном сценарии: для решения проблемы наследования алмазов.

#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);
}
Автор: Ciro Santilli 新疆改造中心法轮功六四事件 Размещён: 07.12.2016 07:52

0 плюса

Виртуальные классы - это не то же самое, что виртуальное наследование. Виртуальные классы, которые вы не можете создать, виртуальное наследование - это совсем другое.

Википедия описывает это лучше, чем я. http://en.wikipedia.org/wiki/Virtual_inheritance

Автор: bradtgmurray Размещён: 22.08.2008 01:52
Вопросы из категории :
32x32