Что такое виртуальный базовый класс в C ++? - PullRequest
376 голосов
/ 22 августа 2008

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

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

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

class Bar : public virtual Foo
{
public:
    void DoSpecific() { /* ... */ }
};

Ответы [ 10 ]

514 голосов
/ 22 августа 2008

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

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

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

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

233 голосов
/ 22 сентября 2008

Об устройстве памяти

В качестве примечания, проблема с Dreaded Diamond заключается в том, что базовый класс присутствует несколько раз. Таким образом, при регулярном наследовании вы верите:

  A
 / \
B   C
 \ /
  D

Но в макете памяти у вас есть:

A   A
|   |
B   C
 \ /
  D

Это объясняет, почему при звонке D::foo() возникает проблема неоднозначности. Но проблема real возникает, когда вы хотите использовать переменную-член 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 является неоднозначным, и это должно быть решено. Даже если это так, внутри D есть два m_iValues, так что лучше запомните это и обновите их одновременно.

С виртуальным наследованием изменение m_iValue из D в порядке ... Но ... Допустим, у вас есть D. Через интерфейс C вы подключили наблюдателя. А через интерфейс B вы обновляете классный массив, побочным эффектом которого является прямое изменение m_iValue ...

Поскольку изменение m_iValue выполняется напрямую (без использования метода виртуального средства доступа), наблюдатель, «прослушивающий» через C, вызываться не будет, поскольку код, реализующий прослушивание, находится в C, и B не знает об этом ...

Заключение

Если у вас есть ромб в вашей иерархии, это означает, что у вас есть 95%, чтобы сделать что-то не так с указанной иерархией.

32 голосов
/ 14 июня 2009

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

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

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

9 голосов
/ 22 августа 2008

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

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

6 голосов
/ 22 августа 2008

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

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

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

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

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

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

4 голосов
/ 22 сентября 2008

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

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);
}
1 голос
/ 22 августа 2008

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

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

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

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

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

1 голос
/ 22 августа 2008

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

C ++ FAQ Lite FTW.

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

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

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

0 голосов
/ 22 августа 2008

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...