寻找类似于非POD类型的offsetof()的东西

时间:2008-10-07 10:33:54

标签: c++ hdf5 offsetof

我正在寻找一种方法来获取非POD性质的C ++类数据成员的偏移量。

原因如下:

我想以HDF5格式存储数据,这似乎最适合我的材料(数值模拟输出),但它可能是一个相当于C的库。我想通过C ++接口使用它,这需要我这样声明存储类型(遵循herehere的文档(第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是使用offsetof的HDF特定宏。

问题当然是,只要示例类变得更有特色,它就不再是POD类型,因此使用offsetof会得到未定义的结果。

我能想到的唯一解决方法是首先将我想要存储的数据导出到更简单的结构中,然后将其传递给HDF。然而,这确实涉及数据复制,这正是HDF试图避免的(以及为什么他们有这个CompType使得库能够进入你的对象以将他们的数据保存到文件中)。

所以我希望你有更好的想法。理想情况下,我会为这个问题寻找一个可移植的解决方法,但如果没有这个,你可以给我一个适用于x86和x86_64与GCC的想法,我已经非常感激了。

-----后来追加:-----

Greg Hewgill建议在下面将数据存储在一个简单的结构中,然后通过继承来构建实际的类。特别是对于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。这会导致偏移错误,因为derived_non_pod会添加时髦的东西(比如虚拟函数表,我猜?)。

7 个答案:

答案 0 :(得分:5)

Greg Hewgill的解决方案可能比这更好(可能是合成而不是继承)。

但是,我认为对于x86和x86_64上的GCC,即使对于非POD类型的成员,offsetof实际上也会起作用,只要它“有意义”。因此,例如,它不适用于从虚拟基类继承的成员,因为在GCC中使用额外的间接实现。但是只要你坚持普通的公共单继承,GCC就会恰好以一种方式布置你的对象,这意味着每个成员都可以在偏离对象指针的位置访问,因此实现的偏移将给出正确的答案。

麻烦当然是你必须忽略警告,这意味着如果你做了一些不起作用的事情,你将取消引用一个接近空的指针。从好的方面来说,问题的原因可能在运行时很明显。在负面,eeew。

[编辑:我刚刚在gcc 3.4.4上对此进行了测试,实际上在获取从虚拟基类继承的成员的偏移量时,警告会升级为错误。这很好。我仍然有点担心gcc的未来版本(4,甚至,我没有提到)会更严格,如果你采用这种方法,你的代码可能在将来停止编译。]

答案 1 :(得分:4)

根据您想要的便携性,您甚至可以在非POD类型上使用offsetof()。它并不严格符合,但在offset()在gcc和MSVC上实现的方式,它适用于当前版本和最近的非POD类型。

答案 2 :(得分:3)

您可以在基类中声明POD类型,然后扩展该类(可能具有private继承)以添加您的附加功能。

更新到您的更新:由于derived_non_pod的实例也可以视为base_pod,因此数据成员的偏移量必须相同。关于实现,在布局base_pod结构时,编译器将在derived_non_pod的字段之后分配vtable指针

我觉得如果你使用私有继承,编译器可以能够选择重新排序数据字段。但是,它不太可能这样做,并且保护或公开继承会避免这种可能的陷阱。

答案 3 :(得分:1)

我很确定Roel的answer以及onebyone answer的考虑因素涵盖了你所提出的大部分内容。

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在基类成员'i'之前有一个vtable,那就是你所期望的。

应该可以手动计算偏移量,即使对于有虚拟基础的情况:

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.再次,这与作为B的第一个成员的vtable一致,其后是虚拟基础A2及其成员“j”。最后是非虚拟基础A1及其成员“i”。

关键是你总是根据最衍生的对象计算偏移量,即。 B.如果成员是私人的,那么您可能需要为每个成员添加“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 }
};

然后你可以通过循环构建H5type:

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类型。

答案 4 :(得分:0)

问题是,只要你的struct / class不是extern“C”,C ++编译器就可以自由地重新排列和优化struct / class的布局,所以你最终可能会根据你的编译器重新排序结构。

有类似C行为的预处理器(例如#pragma pack)标志,但在大多数情况下它们都不可移植。

答案 5 :(得分:0)

使用指向成员的指针而不是offsetof()吗?我知道你可能不得不做各种各样的转换才能真正使用指针,因为我猜测InsertMember在运行时对最后一个参数中指定的类型起作用。

但是根据你目前的解决方案,你已经开始使用类型系统,所以我不确定你在那里输了什么。除了指向成员的指针的语法是可怕的。

答案 6 :(得分:-1)

这对我来说很好,我不明白为什么它不会:

#define myOffset(Class,Member) ({Class o; (size_t)&(o.Member) - (size_t)&o;})