Программирование игр, создание игрового движка, OpenGL, DirectX, физика, форум
GameDev.ru / Программирование / Форум / С++ как узнать смещение классов при виртуальном наследовании?

С++ как узнать смещение классов при виртуальном наследовании?

Поделиться

Страницы: 1 2 Следующая

anzПостоялецwww29 окт. 201712:05#0
Допустим есть несколько классов в классической ромбовидной системе
struct A
{
  int a1;
  int a2;
};

struct B: virtual A
{
  int b1;
  int b2;
};

struct C: virtual A
{
  int c1;
  int c2;
};

struct D: public D, public C
{
  int d1;
  int d2;
};

    A
   / \
  B   C
   \ /
    D

Мне нужно узнать с каким смещением располагаются данные классов относительно данных класса D.
Первый и очевидный способ - создать экземпляр D, затем dynamic_cast<> к A,B,C и запомнить смещение.

Вопрос в том, можно ли получить эти смещения без создания объекта? Теоретически, компилятор знает как расположить данные при создании экземпляра D.
К сожалению на разных компиляторах все работает по-разному. Но, возможно, есть способ узнать смещения без создания объекта

innuendoПостоялецwww29 окт. 201712:11#1
anz
зачем?
anzПостоялецwww29 окт. 201712:46#2
innuendo
рефлексию делаю
loysoПостоялецwww29 окт. 201713:04#3
anz
А кто тебе сказал что объект будет в памяти непрерывным в этом случае? Стандарт не гарантирует.

А зачем ты делаешь рефлексию именно так? Что за проблема?

anzПостоялецwww29 окт. 201713:43#4
loyso
Будет с дырками, ага.

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

struct MyType
{
  int abc;
  float def;

  static Type _type;  

  static void InitializeType()
  {
    MyType* _this = 0;
    _type.AddField("abc", _this->abc);
    _type.AddField("def", _this->def);
  }
};
Далее, имея эту инфу, я могу вытаскивать значения полей:
MyType object;
Type myType = MyType::_type;
FieldInfo* fi = myType.GetField("abc");
int _abc = fi->GetValue<int>(&object); // Здесь беру адрес object, прибавляю к нему смещение и кастую к int

То есть в памяти лежит как-то так:
—MyType
abc + 0b
def + 4b
Если сделать наследника от него, то все работает:
struct MyDerived: public MyType
{
  int xyz;
  ...
};

—MyType
abc + 0b
def + 4b
—MyDerived
xyz + 8b
Смещения могут меняться от выравнивания и тд, но конструкция MyType* _this = 0; offset = (size_t)_this->abc; в любом случае дает правильное смещение.

Все ок, пока нет ни виртуального, ни множественного наследования. Как только оно появляется, смещения могут быть совершенно разными в разных ситуациях.
Например для тех же классов A, B, C, D может быть так:

—D
vbtbl + 0b
d1 + 4b
d2 + 8b
—B
vbtbl + 12b
b1 + 16b
b2 + 20b
—C
vbtbl + 24b
c1 + 28b
c2 + 32b
—A
a1 + 36b
a2 + 40b
А может и так:
struct E: public A
{
  int e1;
  int e2;
};

—A
a1 + 0b
a2 + 4b
—E
e1 + 8b
e2 + 12b
И в двух этих ситуациях смещения a1 и a2 будут разными для разных объектов. Вот я и хочу запомнить в типе смещения +0b и +4b для переменных, а затем прибавлять смещение для самого класса

loysoПостоялецwww29 окт. 201714:15#5
anz
В твоем случае трудно что-то посоветовать.
Множественное наследование никто не использует в здравом уме. Ориентируйся на примитивные mixinы какие-ниубдь хотя-бы (которые ECS).

> я могу вытаскивать значения полей
Обычно поля с конкретной целью вытаскивают/засылают - анимация, репликация, сериализация, скриптинг, редактирование или все вместе.
Т.е. лучше зайти с этой стороны.

innuendoПостоялецwww29 окт. 201714:19#6
loyso
> Множественное наследование никто не использует в здравом уме.

например используются в UE

> Ориентируйся на примитивные mixinы какие-ниубдь хотя-бы (которые ECS).

примеси это статика, а ECS это динамические фичи ?

The PlayerУчастникwww29 окт. 201715:08#7
loyso
> Множественное наследование никто не использует в здравом уме.
Ну ты даешь.
loysoПостоялецwww29 окт. 201715:12#8
The Player
> > Множественное наследование никто не использует в здравом уме.
> Ну ты даешь.
Бунт на gamedev.ru!
innuendoПостоялецwww29 окт. 201715:17#9
loyso
> > > Множественное наследование никто не использует в здравом уме.
> > Ну ты даешь.
> Бунт на gamedev.ru!

прослушиватели для ui?

loysoПостоялецwww29 окт. 201715:20#10
innuendo
> прослушиватели для ui?
Я все понимаю про UE, Java интерфейсы и пр. Но anz и его оффсеты к данным тут совершенно не при чем (вроде)
innuendoПостоялецwww29 окт. 201715:22#11
loyso
> Java интерфейсы и

э... в коде King's Bounty было множественное для виджетов

OverlordffПользовательwww29 окт. 201715:29#12
Конструкция

MyType* _this = 0; offset = (size_t)&_this->abc;

содержит неопределённое поведение (разыменование нулевого указателя).
Подробнее:

https://habrahabr.ru/company/pvs-studio/blog/247973/

Правильно использовать offsetof macro из стандартной библиотеки
http://en.cppreference.com/w/cpp/types/offsetof

Обратите внимание, что там сказано про случай, когда тип не удовлетворяет концепту StandardLayoutType. В вашем случае концепт явно нарушается и использование offsetof также приводит к UB.

Вывод:
Для типов не StandardLayoutType, вычисление смещений некорректно.

Правка: 29 окт. 2017 15:38

Kollect3DПостоялецwww29 окт. 201715:40#13
Overlordff
> содержит неопределённое поведение (разыменование нулевого указателя).

Если структура POD, то там нет никакого разыменования - конструкция легко и просто вычисляется в компил-тайме и не содержит никаких глюков - именно поэтому именно так и устроены макросы offsetof.
Но как только появляется множественное виртуальное наследование, то эта логика ломается - дело в том, что при множественном виртуальном наследовании чтобы узнать где находится поле внутри объекта класса X становится необходимо считать смещение этого класса внутри потенциального наследника - то есть действительно выполнить считывание из памяти - а это действительно требует разыменовать указатель. Что во первых - придётся делать уже в рантайме, а во вторых - мы сами знаем что он nullptr и выбьёт с ошибкой. Поэтому без вариантов.
Поэтому макрос offsetof мгновенно перестаёт работать для не-POD-структур, как только даже виртуальный метод или обычное наследование проскакивает. Хотя это уже наверное черезчур, но повелось именно так.
Поэтому ответа на сабж в общем случае как раз и не существует.

Правка: 29 окт. 2017 15:42

OverlordffПользовательwww29 окт. 201716:05#14
Kollect3D
> Если структура POD, то там нет никакого разыменования - конструкция легко и
> просто вычисляется в компил-тайме и не содержит никаких глюков - именно поэтому
> именно так и устроены макросы offsetof.

Здесь вы немного ошибаетесь, offsetof не может быть реализован средствами текущего стандарта C++ даже для StandardLayoutType. Об этом говорится по ссылке на документацию, которую я привёл
http://en.cppreference.com/w/cpp/types/offsetof

Действительно, offsetof на GCC, например, резолвится в builtin_offsetof. Это сделано именно потому, что конструкция "наивного" вычисления смещения (как у автора) по стандарту является UB. Как бы она не вычислялась конкретным компилятором в конкретный момент времени, в общем случае даже для POD типов никакой гарантии нет. Поведение неопределено.

Со всем, что вы сказали дальше, я полностью согласен.

Страницы: 1 2 Следующая

/ Форум / Программирование игр / Общее

2001—2017 © GameDev.ru — Разработка игр