Ищете что-то похожее на offsetof () для не POD-типов - PullRequest
9 голосов
/ 07 октября 2008

Я ищу способ получения смещений членов данных класса C ++, который имеет не-POD характер.

И вот почему:

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

class example { 
public:
    double member_a;
    int member_b;
} //class example

H5::CompType func_that_creates_example_CompType() {
    H5::CompType ct;
    ct.insertMember("a", HOFFSET(example, member_a), H5::PredType::NATIVE_DOUBLE);
    ct.insertMember("b", HOFFSET(example, member_b), H5::PredType::NATIVE_INT);
    return ct;
} //func_that_creates_example_CompType

где HOFFSET - специфичный для HDF макрос, который использует offsetof.

Проблема, конечно, в том, что, как только пример класса становится немного более функциональным, он перестает быть POD-типом, и поэтому использование offsetof даст неопределенные результаты.

Единственный обходной путь, о котором я могу подумать - это сначала экспортировать данные, которые я хочу сохранить, в более простую структуру, а затем передать их в HDF. Это, однако, включает в себя копирование данных, чего именно HDF пытается избежать (и почему у них есть этот CompType, который позволяет библиотеке обращаться к вашим объектам, чтобы сохранить свои данные в файл).

Так что я надеялся, что у вас будут лучшие идеи. В идеале я бы искал переносное решение этой проблемы, но если бы не это, вы могли бы дать мне идею, которая работает на x86 и x86_64 с GCC, я уже был бы безмерно благодарен.

----- добавлено позже: -----

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

class base_pod {
public:
    double member_a;
    int member_b;
}; //class base_pod

class derived_non_pod : private base_pod {
public:
    //the following method is only virtual to illustrate the problem
    virtual double get_member_a() {return member_a; }
}; //class derived_non_pod

class that_uses_derived_non_pod {
public:
    void whatever();
private:
    derived_non_pod member_c;
}; //class that_uses_derived_non_pod

Теперь, когда мы храним экземпляры класса that_uses_derived_non_pod, мы не можем описать макет его памяти, как если бы он имел base_pod как member_c. Это может привести к неправильному смещению, потому что output_non_pod добавляет интересные вещи (например, таблицу виртуальных функций, я полагаю?).

Ответы [ 7 ]

5 голосов
/ 07 октября 2008

Решение Грега Хьюгилла, вероятно, предпочтительнее этого (возможно, с составом, а не наследованием).

Однако, я думаю, что с GCC на x86 и x86_64, offsetof будет фактически работать даже для членов не POD-типов, если это «имеет смысл». Так, например, он не будет работать для членов, унаследованных от виртуальных базовых классов, потому что в GCC это реализовано с дополнительной косвенностью. Но до тех пор, пока вы придерживаетесь простого публичного одиночного наследования, GCC просто так расположит ваши объекты так, что каждый член будет доступен со смещением от указателя объекта, поэтому реализация offsetof даст правильный ответ.

Проблема, конечно, в том, что вы должны игнорировать предупреждения, что означает, что если вы делаете что-то, что не работает, вы разыменовываете указатель, близкий к нулю. С другой стороны, причина проблемы, вероятно, будет очевидна во время выполнения. На минусовой стороне, eeew.

[Edit: я только что проверил это на gcc 3.4.4, и на самом деле предупреждение обновляется до ошибки при получении смещения члена, унаследованного от виртуального базового класса. Что приятно. Я все еще немного обеспокоен тем, что будущая версия gcc (даже 4, которая мне не нужна) будет более строгой, и если вы воспользуетесь этим подходом, ваш код может в будущем прекратить компиляцию.]

4 голосов
/ 07 октября 2008

В зависимости от того, насколько портативным вы хотите быть, вы можете использовать offsetof () даже для типов не POD. Он не является строго совместимым, но в способе реализации offsetof () в gcc и MSVC он будет работать с не POD-типами в текущей версии и недавнем прошлом.

3 голосов
/ 07 октября 2008

Вы можете объявить типы POD в базовом классе, а затем расширить этот класс (возможно, с помощью private наследования), чтобы добавить дополнительные функции.

Обновление до вашего обновления: поскольку экземпляр derived_non_pod также может рассматриваться как base_pod, поэтому смещения для элементов данных должны быть одинаковыми. Что касается реализации, ваш компилятор будет выделять указатель vtable после полей base_pod при разметке структуры derived_non_pod.

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

1 голос
/ 05 августа 2009

Я почти уверен, что ответ Роэла вместе с учетом ответа одного человека охватывает большую часть того, что вы спрашиваете.

struct A
{
  int i;
};

class B: public A
{
public:
  virtual void foo ()
  {
  }
};

int main ()
{
  std::cout << offsetof (B, A::i) << std::endl;
}

При использовании g ++ вышеприведенное выводит 4, что вы и ожидаете, если у B будет vtable перед членом базового класса 'i'.

Должно быть возможно рассчитать смещение вручную, даже для случая, когда существуют виртуальные базы:

struct A1 {
  int i;
};

struct A2 {
  int j;
};

struct A3 : public virtual A2 {
};

class B: public A1, public A3 {
public:
  virtual void foo () {
  }
};

template <typename MostDerived, typename C, typename M>
ptrdiff_t calcOffset (M C::* member)
{
  MostDerived d;
  return reinterpret_cast<char*> (&(d.*member)) - reinterpret_cast<char*> (&d);
}

int main ()
{
  B b;
  std::cout << calcOffset<B> (&A2::j) << ", " 
            << calcOffset<B> (&A1::i) << std::endl;
}

С g ++ эта программа выводит 4 и 8. Опять же, это согласуется с vtable в качестве первого члена B, за которым следует виртуальная база A2 и ее член 'j'. Наконец, не виртуальная база A1 и ее член 'i'.

Ключевым моментом является то, что вы всегда рассчитываете смещения на основе самого производного объекта, т.е. Б. Если участники являются личными, вам может потребоваться добавить вызов getMyOffset для каждого участника. Этот вызов будет выполнять вычисления, где имя доступно.

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

struct H5MemberDef
{
  const char * member_name;
  ptrdiff_t offset;
  H5PredType h5_type;
};


class B  // ....
{
public:

  // ...

  static H5memberDef memberDef[];
};

H5MemberDef B::memberDef[] = {
  { "i", calcOffset<B> (&A1::i), H5::PredType::NATIVE_INT }
  , { "j", calcOffset<B> (&A2::j), H5::PredType::NATIVE_INT }
  , { 0, 0, H5::PredType::NATIVE_INT }
};

И затем вы можете построить тип H5 через цикл:

H5::CompType func_that_creates_example_CompType(H5MemberDef * pDef) {
  H5::CompType ct;
  while (*pDef->member_name != 0)
  {
    ct.insertMember(pDef->member_name, pDef->offset, pDef->h5_type);
    ++pDef;
  }
  return ct;
}

Теперь, если вы добавите элемент в B или одну из его баз, то простое добавление к этой таблице приведет к генерированию правильного типа HDF.

0 голосов
/ 29 апреля 2013

Это прекрасно работает для меня, и я не понимаю, почему это не так:

#define myOffset(Class,Member) ({Class o; (size_t)&(o.Member) - (size_t)&o;})
0 голосов
/ 07 октября 2008

Будет ли работать указатель на член вместо offsetof ()? Я знаю, что вам, вероятно, придется выполнять все виды приведения, чтобы иметь возможность фактически использовать указатель, так как я предполагаю, что InsertMember действует на тип, указанный в последнем параметре во время выполнения.

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

0 голосов
/ 07 октября 2008

Проблема в том, что как только ваш struct / class не является внешним "C", компиляторы C ++ могут свободно переупорядочивать и оптимизировать компоновку вашего struct / class, так что вы можете получить переупорядоченную структуру в зависимости от вашего компилятора.

Существуют флаги препроцессора (например, #pragma pack) для поведения, подобного C, но в большинстве случаев они не переносимы.

...