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

Если бы у нас был этот код:

class Base
{
int anint;
float afloat;
};
class Derived : Base
{
//inherited member variables...
};

Мне сказали, что члены Base будут унаследованы от Derived, а эти унаследованные члены Derived на самом деле находятся внутри подобъекта базового класса Base (но этот подобъект безымянный); в Derived создается подобъект Base, который содержит унаследованные члены. Итак, при доступе к члену в классе происходит неявный вызов указателя this, если вы не делаете что-то явно, но есть ли также неявный указатель (или что-то еще), вызываемый при доступе к унаследованному объекту? Например, если бы мы обратились к anint в экземпляре Derived с помощью derivedInstance->anint, это действительно выглядело бы как derivedInstance->this->somethingToBaseObjectThatHoldsTheMembers->anint или как это работает?


person Community    schedule 18.09.2017    source источник
comment
Компилятор может определить правильное смещение всех членов базовых классов, поэтому, если в игре нет виртуального наследования, оно будет похоже на this + member offset -> или даже полностью оптимизировано, если компилятору удастся встроить класс и просто сохранить anint и afloat в регистрах. Возможно, связано.   -  person user7860670    schedule 18.09.2017
comment
Классы не имеют представления в скомпилированном коде (за исключением, возможно, указателя vtable) отдельно для своих элементов данных, как и структуры в C.   -  person    schedule 18.09.2017
comment
@VTT, так что вы говорите, что нет какой-то неявной вещи, которая добавляется при доступе к членам, которые унаследованы и хранятся внутри подобъекта базового класса?   -  person    schedule 18.09.2017
comment
Вы можете думать о цепочке derivedInstance->this->somethingToBaseObjectThatHoldsTheMembers->anint как о шагах, необходимых для вычисления смещения члена. Но поскольку каждое смещение известно во время компиляции (если нет виртуального наследования), результирующее смещение также будет вычисляться во время компиляции, и никаких дополнительных перенаправлений не происходит. В случае виртуального наследования будет дополнительное перенаправление через указатель на базовый класс.   -  person user7860670    schedule 18.09.2017
comment
@VTT хорошо, спасибо! Но нет ничего похожего на thispointer, который вы можете написать явно, для доступа к членам, которые с точки зрения памяти находятся внутри безымянного подобъекта базового класса, только гипотетический?   -  person    schedule 18.09.2017


Ответы (2)


Нет специального указателя на данные базового класса. Расположение членов данных в C++ близко к C. (На самом деле в вашем примере нет виртуальных методов, поэтому он должен точно следовать C). Итак, давайте рассмотрим, как ваш код будет выглядеть на C:

 struct Base
 {
    int anint;
    float afloat;
 };
 struct Derived
 {
   struct Base; // C style inherit from struct Base
   int a2ndInt;
 };

В C структура памяти определяется так, как вы ее пишете. Это означает, что структура Derived имеет следующую структуру памяти.

struct Derived
{
    int anint;
    float afloat;
    int a2ndInt;
};

Указатель this указывает на начало структуры, поэтому доступ к anint или afloat из указателя на Derived или Base требует одинакового смещения памяти. . Таким образом, заброс вниз здесь всегда прост.

Все становится сложнее, когда у вас есть виртуальные функции, поскольку структура данных должна иметь скрытый указатель на свои виртуальные функции, но такой указатель должен быть только один. Давайте рассмотрим случай одиночного наследования, и вы можете представить что-то вроде макета (фактический макет зависит от ABI):

 struct Base
 {
    <ABI defined pointer type> * class_; // hidden virtual function table
    int anint;
    float afloat;
 };
 struct Derived
 {
   struct Base; // inherit from struct Base
   int a2ndInt;
 };

Теперь структура Derived может иметь следующую структуру памяти. Обратите внимание, что при создании производного объекта конструктор должен установить указатель class_. Это одна из причин, по которой конструкторы начинаются с конструктора базового класса, поскольку каждый производный класс может затем переопределить указатель class_.

struct Derived
{
    <ABI defined pointer type> * class_; 
    int anint;
    float afloat;
    int a2ndInt;
};

Таким образом, снова доступ к anint или afloat из указателя на Derived или Base включает одно и то же смещение. Таким образом, заброс вниз здесь снова прост.

Множественное наследование намного сложнее, и именно здесь необходимо использовать static_cast‹> для приведения вниз. Этот случай наиболее близок к тому, что вы думаете, но по-прежнему включает смещение только одного указателя this.

struct Base1
{
   <ABI defined pointer type> * class_; // hidden virtual function table
   int anint;
};
struct Base2
{
   <ABI defined pointer type> * class_; // hidden virtual function table
   float afloat;
};

Я не так хорошо знаком с ABI, но я полагаю, что скрытые указатели виртуальных таблиц могут быть каким-то образом объединены, что приведет к примерному расположению памяти:

struct Derived
{
    <ABI defined pointer type> * class_; // merged Base1 and Base2
    int anint;
    float afloat;
    int a2ndInt;
};

или не объединены (в зависимости от ABI)

struct Derived
{
    <ABI defined pointer type> * class_; // from Base1
    int anint;
    <ABI defined pointer type> * class_; // from Base2
    float afloat;
    int a2ndInt;
};

Итак, снова доступ к anint из указателя на Derived или Base1 требует того же смещения, но доступ к на плаву не работает. Это означает, что использование приведения в стиле C (т. е. использование (Base2*)) из указателя Derived в указатель Base2 не работает, вам нужен static_cast<>, который обрабатывает изменение смещения.

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

ПРИМЕЧАНИЕ. Реальный ABI определяет истинное расположение данных в структуре. То, что показано здесь, предназначено только для иллюстрации.

person Justin Finnerty    schedule 19.09.2017

Нет. Компилятор выбирает макет (ABI). Статические приведения используют знания об этом макете для настройки указателей с помощью static_cast.

RTTI включает динамическую корректировку указателя с помощью dynamic_cast.

См., например. Обычное приведение, static_cast и dynamic_cast

person sehe    schedule 18.09.2017