Виртуальное наследование в C ++ - PullRequest
12 голосов
/ 03 марта 2011

Я нашел это на веб-сайте, читая о виртуальном наследовании в c ++

Когда используется множественное наследование, иногда необходимо использовать виртуальное наследование.Хорошим примером этого является стандартная иерархия классов iostream:

//Note: this is a simplified description of iostream classes

class  ostream: virtual public ios { /*..*/ }
class  istream: virtual public ios { /*..*/ }

class iostream : public istream, public ostream { /*..*/ } 
//a single ios inherited

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

Я запутался в утверждении:

C ++ использует дополнительный уровень косвенности для доступавиртуальный класс, обычно с помощью указателя

Кто-нибудь может объяснить это?

Ответы [ 4 ]

9 голосов
/ 03 марта 2011

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

struct base { int x; };
struct derived : base { int y };

Макет для производных:

--------- <- base & derived start here
    x
---------
    y
---------

Если вы добавите второй производный и большинство производных типов (опять же, без виртуального наследования), вы получите что-то вроде:

struct derived2 : base { int z; };
struct most_derived : derived, derived 2 {};

С этим макетом:

--------- <- derived::base, derived and most_derived start here
    x
---------
    y
--------- <- derived2::base & derived2 start here
    x
---------
    z
---------

Если у вас есть объект most_derived и вы связываете указатель / ссылку типа derived2, он будет указывать на строку, помеченную derived2::base. Теперь, если наследование от базы было виртуальным, то должен быть один экземпляр base. Ради обсуждения просто предположим, что мы наивно удаляем второй base:

--------- <- derived::base, derived and most_derived start here
    x
---------
    y
--------- <- derived2 start here??
    z
---------

Теперь проблема в том, что если мы получим указатель на derived, он будет иметь ту же структуру, что и оригинал, но если мы попытаемся получить указатель на derived2, то макет будет отличаться, а код в derived2 не будет быть в состоянии найти x участника. Нам нужно сделать что-то умнее, и именно здесь указатель вступает в игру. Добавляя указатель на каждый объект, который виртуально наследуется, мы получаем такой макет:

---------  <- derived starts here
base::ptr  --\
    y        |  pointer to where the base object resides
---------  <-/
    x
---------

Аналогично для derived2. Теперь за счет дополнительной косвенности мы можем найти подобъект x через указатель. Когда мы можем создать most_derived макет с одной базой, это может выглядеть так:

---------          <- derived starts here
base::ptr  -----\
    y           |
---------       |  <- derived2
base::ptr  --\  |
    z         | |
---------  <--+-/  <- base
    x
---------

Теперь код в derived и derived2 теперь показывает, как получить доступ к базовому подобъекту (просто разыменование объекта-члена base::ptr), и в то же время у вас есть один экземпляр base. Если код в любом промежуточном классе обращается к x, они могут сделать это, выполнив this->[hidden base pointer]->x, и это будет разрешено во время выполнения в правильную позицию.

Важным моментом здесь является то, что код, скомпилированный на уровне derived / derived2, может использоваться с объектом этого типа или любым производным объектом. Если бы мы написали второй объект most_derived2, в котором порядок наследования был изменен, тогда их расположение y и z можно было бы поменять местами, а смещения от указателя на подобъекты derived или derived2 до Подобъект base будет другим, но код для доступа к x будет таким же: разыменуйте свой собственный скрытый базовый указатель, гарантируя, что если метод в derived является окончательным переопределением, а доступ base::x тогда он найдет его независимо от окончательного макета.

8 голосов
/ 03 марта 2011

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

class Base {
public:
    int base_member;
};

class Derived: public Base {
public:
    int derived_member;
};


Derived *d = new Derived();
int foo = d->derived_member;  // Only one indirection necessary.
int bar = d->base_member;     // Same here.
delete d;

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

class Base {
public:
    // Shared by Derived from Intermediate1 and Intermediate2.
    int base_member;  
};

class Intermediate1 : virtual public Base {
};

class Intermediate2 : virtual public Base {
};

class Derived: public Intermediate1, public Intermediate2 {
public:
    int derived_member;
};

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

Derived *d = new Derived();
int foo = d->derived_member;  // Only one indirection necessary.
int bar = d->base_member;     // Roughly equivalent to
                              // d->shared_Base->base_member.
delete d;
3 голосов
/ 03 марта 2011

В C ++ класс размещается в памяти в фиксированном порядке.Базовый класс существует буквально внутри памяти, выделенной производному классу, с фиксированным смещением, аналогично меньшему блоку внутри большего блока.

Если у вас нет виртуального наследования, вы говорите, что iostream содержитistream и ostream, каждый из которых содержит ios.Следовательно, iostream содержит два ios es.

При виртуальном наследовании виртуальный базовый класс не существует с фиксированным смещением.Это аналогично висящему снаружи окну, связанному с небольшим количеством шнура.

Итак, iostream содержит istream и ostream, каждый из которых связан с ios строкой.Следовательно, iostream имеет один ios, связанный двумя отдельными битами строки.

На практике бит строки представляет собой целое число, которое говорит, где начинается фактический ios относительно адреса полученногоучебный класс.Т.е. у istream есть скрытый член, который называется, например, __virtual_base_offset_ios.Когда методы istream хотят получить доступ к базе ios, они берут свой собственный указатель this, добавляют __ios_base_offset, и это указатель ios базового класса.

-

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

0 голосов
/ 12 октября 2017

Для удаления неоднозначности используется виртуальное наследование.

class base {
    public:
        int a;
};

class new1 :virtual public base
{
    public:
        int b;
};
class new2 :virtual public base
{
    public:
        int c;
};

class drive : public new1,public new2
{
    public:
        void getvalue()
        {
            cout<<"input a b c "<<endl;
            cin>>a>>b>>c;
        }
        void printf()
        {

            cout<<a<<b<<c;
        }
};

int main()
{
    drive ob;
    ob.getvalue();
    ob.printf();
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...