10

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

原因如下:

我想以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 是一个使用 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 添加了一些时髦的东西(我猜是虚拟函数表?)。

4

7 回答 7

5

Greg Hewgill 的解决方案可能比这更可取(也许使用组合而不是继承)。

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

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

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

于 2008-10-07T11:11:29.733 回答
4

根据您想要的可移植性,您甚至可以在非 POD 类型上使用 offsetof()。它不是严格一致的,但在 gcc 和 MSVC 上实现 offsetof() 的方式中,它将适用于当前版本和最近的非 POD 类型。

于 2008-10-07T11:15:28.743 回答
3

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

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

我突然想到,如果您使用私有继承,编译器也许可以选择重新排序数据字段。但是,不太可能这样做,并且将继承设置为受保护或公开将避免这种可能的陷阱。

于 2008-10-07T10:36:23.190 回答
1

我很确定 Roel 的回答以及对 onebyone 的回答的考虑涵盖了您所问的大部分内容。

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

于 2009-08-05T18:02:28.567 回答
0

问题是只要你的结构/类不是外部“C”,C++ 编译器就可以自由地重新排列和优化你的结构/类的布局,所以你最终可能会得到一个重新排序的结构,具体取决于你的编译器。

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

于 2008-10-07T11:05:12.460 回答
0

会使用指向成员的指针而不是 offsetof() 吗?我知道您可能必须进行各种强制转换才能实际使用指针,因为我猜测 InsertMember 在运行时作用于最后一个参数中指定的类型。

但是使用您当前的解决方案,您已经在使用类型系统,所以我不确定您会在那里丢失任何东西。除了指向成员的指针的语法是可怕的。

于 2008-10-07T17:34:23.393 回答
-1

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

#define myOffset(Class,Member) ({Class o; (size_t)&(o.Member) - (size_t)&o;})
于 2013-04-29T02:19:04.813 回答