Подтвердить что ты не робот

Виртуальное наследование в С++

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

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

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

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

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

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

4b9b3361

Ответ 1

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

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;

Ответ 2

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

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 затем он найдет его независимо от окончательной компоновки.

Ответ 3

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

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

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

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

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

-

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

Ответ 4

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

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();
}