Размер объекта C ++ с виртуальными методами

Когда вы объявляете ссылочную переменную (т. е. объект), вы действительно создаете указатель на объект. Рассмотрим следующий код, в котором вы объявляете переменную примитивного типа int:

int x;
x = 10;

В этом примере переменная x является int, и Java инициализирует ее для 0. Когда вы назначаете его 10 во второй строке, ваше значение 10 записывается в ячейку памяти, на которую указывает x.

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

Integer num;
num = new Integer(10);

Первая строка объявляет переменную с именем num, но она не содержит примитивного значения. Вместо этого он содержит указатель (потому что тип Integer является ссылочным типом). Поскольку вы еще не указали, что указать на Java, он устанавливает значение null, что означает «Я ничего не указываю».

Во второй строке ключевое слово new используется для создания экземпляра (или создания ) объекту типа Integer и переменной указателя num присваивается этот объект. Теперь вы можете ссылаться на объект, используя оператор разыменования . (точка).

Exception, о котором вы просили, возникает, когда вы объявляете переменную, но не создавали объект. Если вы попытаетесь разыменовать num. Перед созданием объекта вы получите NullPointerException. В самых тривиальных случаях компилятор поймает проблему и сообщит вам, что «num не может быть инициализирован», но иногда вы пишете код, который непосредственно не создает объект.

Например, вы можете имеют следующий метод:

public void doSomething(SomeObject obj) {
   //do something to obj
}

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

doSomething(null);

В этом случае obj имеет значение null. Если метод предназначен для того, чтобы что-то сделать для переданного объекта, целесообразно бросить NullPointerException, потому что это ошибка программиста, и программисту понадобится эта информация для целей отладки.

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

/**
  * @param obj An optional foo for ____. May be null, in which case 
  *  the result will be ____.
  */
public void doSomething(SomeObject obj) {
    if(obj != null) {
       //do something
    } else {
       //do something else
    }
}

Наконец, Как определить исключение & amp; причина использования Трассировки стека

25
задан jww 2 June 2018 в 13:59
поделиться

6 ответов

Определена вся реализация. Я использую VC10 Beta2. Ключ, который поможет разобраться в этом (реализация виртуальных функций), нужно знать о секретном ключе в компиляторе Visual Studio, /d1reportSingleClassLayoutXXX. До этого я дойду через секунду.

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

Пара вопросов здесь, я начну сверху:

Означает ли это, что только один vptr имеет виртуальную функцию и в классе B, и в классе A? Почему есть только один vptr?

Так работают виртуальные функции, вы хотите, чтобы базовый и производный класс имели один и тот же указатель (указывающий на реализацию в производном классе.

Похоже, что в данном случае два vptr находятся в layout.....Как это происходит? Я думаю, что два vptrs один для класса A, а другой для класса B....so нет vptr для виртуальной функции класса C?

Это макет класса C, о чем сообщает /d1reportSingleClassLayoutC:

class C size(20):
        +---
        | +--- (base class A)
 0      | | {vfptr}
 4      | | a
        | +---
        | +--- (base class B)
 8      | | {vfptr}
12      | | b
        | +---
16      | c
        +---

Вы правы, есть две таблицы, по одной для каждого базового класса. Так он работает при множественном наследовании; если C* кастится до B*, то значение указателя корректируется на 8 байт. Для того, чтобы виртуальная функция могла работать, необходимо, чтобы vtable все еще был со смещением 0.

Столбличка в вышеприведенной компоновке для класса A рассматривается как столбчатая версия класса C (при вызове через C*).

Размер B равен 16 байтам -------------- Без виртуальной он должен быть 4 + 4 + 4 = 12. Почему здесь еще 4 байта? Какова компоновка класса B ?

В данном примере это компоновка класса B:

class B size(20):
        +---
 0      | {vfptr}
 4      | {vbptr}
 8      | b
        +---
        +--- (virtual base A)
12      | {vfptr}
16      | a
        +---

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

Размер D равен 32 байтам -------------- он должен быть 16(класс B) + 12(класс C) + 4(int d) = 32. Правда?

Нет, 36 байт. То же самое и с виртуальным наследованием. Макет D в данном примере:

class D size(36):
        +---
        | +--- (base class B)
 0      | | {vfptr}
 4      | | {vbptr}
 8      | | b
        | +---
        | +--- (base class C)
        | | +--- (base class A)
12      | | | {vfptr}
16      | | | a
        | | +---
20      | | c
        | +---
24      | d
        +---
        +--- (virtual base A)
28      | {vfptr}
32      | a
        +---

Мой вопрос, зачем нужно дополнительное место при применении виртуального наследования?

Указатель на виртуальный базовый класс, это сложно. Базовые классы "комбинируются" при виртуальном наследовании. Вместо того, чтобы базовый класс был встроен в класс, он будет иметь на макете указатель на объект базового класса. Если у вас есть два базовых класса, использующих виртуальное наследование (иерархия классов "алмаз"), то оба они будут указывать на один и тот же виртуальный базовый класс в объекте, вместо того, чтобы иметь отдельную копию этого базового класса.

Какое правило внизу для размера объекта в данном случае?

Важный момент; нет никаких правил: компилятор может делать все, что ему нужно.

И последняя деталь; чтобы сделать все эти диаграммы компоновки классов, я компилирую:

cl test.cpp /d1reportSingleClassLayoutXXX

Где XXX - это соответствие подстроки структурам/классам, которые вы хотите видеть компоновку. Используя это, вы можете сами изучить влияние различных схем наследования, а также почему/где добавляется подстрока и т.д.

21
ответ дан 28 November 2019 в 07:50
поделиться

Все это полностью зависит от реализации, которую вы понимаете. Вы не можете рассчитывать ни на что из этого. Здесь нет «правила».

В примере наследования, вот как может выглядеть виртуальная таблица для классов A и B:

      class A
+-----------------+
| pointer to A::v |
+-----------------+

      class B
+-----------------+
| pointer to A::v |
+-----------------+
| pointer to B::w |
+-----------------+

Как видите, если у вас есть указатель на виртуальную таблицу класса B, он также отлично подходит как виртуальная таблица класса А.

В вашем примере с классом C, если вы об этом думаете, нет способа создать виртуальную таблицу, которая была бы действительной как таблица для класса C, класса A и класса B. Таким образом, компилятор создает два. Одна виртуальная таблица действительна для классов A и C (наиболее вероятно), а другая - для классов A и B.

2
ответ дан Omnifarious 2 June 2018 в 13:59
поделиться

Я не уверен, но думаю, что это из-за указателя на таблицу виртуальных методов

.
0
ответ дан Gaim 2 June 2018 в 13:59
поделиться

Цитата> Мой вопрос: каково правило о количестве vptr в наследовании?

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

класс B: общедоступный A {}, размер = 12. Это довольно нормально, одна таблица для B, в которой есть оба виртуальных метода, указатель таблицы + 2 * int = 12

класс C: общедоступный A, общедоступный B {}, размер = 20. C может произвольно расширить виртуальную таблицу A или B. 2 * указатель виртуальной таблицы + 3 * int = 20

Виртуальное наследование: вот где вы действительно достигли краев недокументированного поведения. Например, в MSVC параметры компиляции #pragma vtordisp и / vd становятся актуальными. Есть некоторая справочная информация в этой статье . Я изучил это несколько раз и решил, что аббревиатура опции компиляции является репрезентативной для того, что может случиться с моим кодом, если я когда-либо его использую.

3
ответ дан Hans Passant 2 June 2018 в 13:59
поделиться

Это, очевидно, зависит от реализации компилятора. Во всяком случае, я думаю, что я могу подготовить следующие правила из реализации, заданной классической бумагой, связанной ниже, и что дает количество байтов, которые вы попадаете в ваши примеры (кроме класса D, который будет 36 байт, а не 32 !!!) :

Размер объекта класса T:

  • размер его полей плюс сумма размера каждого объекта, из которой T наследует плюс 4 байта для каждого объекта, из которого только Если t нуждается в другой v-таблице
  • Обратите внимание: если класс k практически наследуется несколько раз (на любом уровне), вы должны добавить размер K только один раз

, поэтому мы должны ответить на другой вопрос: когда Класс нужен другой V-таблице?

  • класс, который не наследует от других классов, нуждается в V-таблице, только если он имеет один или несколько виртуальных методов
  • в противном случае, класс нуждается в другом V-таблице, только если ни один из Классы, из которых он не практически наследует, имеет V-таблицу

конец правил (которые, как я думаю, можно применять, чтобы соответствовать тому махрому Mahaffey объяснил в своем ответе) :)

Во всяком случае, мое предложение состоит в том, чтобы прочитать следующую статью Bjarne Stroustrup (создатель C ++), который точно объясняет эти вещи: сколько виртуальных таблиц необходимы с виртуальным или не виртуальным наследством. . и почему!

Это действительно хорошее чтение: http://www.hpc.unimelb.edu.au/nec/g1af05e/chap5.html

1
ответ дан 28 November 2019 в 07:50
поделиться

Хороший способ подумать о том, чтобы понять, что должно быть сделано, чтобы справиться с актерами. Я постараюсь ответить на ваши вопросы, показывая планировку памяти объектов классов, которые вы описываете.

Образец кода № 2

Макет памяти выглядит следующим образом:

vptr | A::a | B::b

Универсание указатель на B к типу A, приведет к тому же адресу, с тем же VPTR. Вот почему здесь нет необходимости в дополнительных VPTR.

Образец кода № 3

vptr | A::a | vptr | B::b | C::c

Как вы можете видеть, здесь есть два VPTR, так же, как вы уже догадались. Почему? Поскольку это правда, что если мы удивляем от C к A, нам не нужно изменять адрес, и, таким образом, можно использовать тот же VPTR. Но если мы удивляем от C до B, мы делаем , нуждаются в этой модификации, и соответственно нам нужен VPTR в начале результирующего объекта.

Итак, любой унаследованный класс за пределы первого потребуется дополнительной VPTR (если только унаследованный класс не имеет виртуальных методов, в этом случае у него нет VPTR).

Образец кода № 4 и помимо

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

Итак, как выглядит макет памяти? Это зависит от компилятора. В вашем компиляторе это, вероятно, что-то вроде

vptr | base pointer | B::b | vptr | A::a | C::c | vptr | A::a
          \-----------------------------------------^

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

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

Редактировать: разъяснение - все действительно зависит от компилятора, макет памяти, которую я показал, который я показал, может отличаться в разных компиляторах.

3
ответ дан 28 November 2019 в 07:50
поделиться
Другие вопросы по тегам:

Похожие вопросы: