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

发布于 2024-07-06 02:32:51 字数 1925 浏览 11 评论 0原文

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

原因如下:

我想以 HDF5 格式存储数据,这似乎最适合我喜欢的材料(数值模拟输出),但它可能是一个相当面向 C 的库。 我想通过 C++ 接口使用它,这需要我像这样声明存储类型(以下文档来自 此处此处< /a>(第 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_driven_non_pod 类的实例时,我们无法将其内存布局描述为好像它有一个 base_pod 作为 member_c。 这会使偏移量错误,因为衍生_非_pod添加了一些时髦的东西(我猜像虚拟函数表?)。

I'm looking for a way to obtain offsets of data members of a C++ class which is of non-POD nature.

Here's why:

I'd like to store data in HDF5 format, which seems most suited for my kind of material (numerical simulation output), but it is perhaps a rather C-oriented library. I want to use it through the C++ interface, which would require me to declare storage types like so (following documentation from here and here (section 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

where HOFFSET is a HDF-specific macro that uses offsetof.

The problem is of course, that as soon as the example-class becomes it little bit more featureful, it is no longer of POD-type, and so using offsetof will give undefined results.

The only workaround I can think of is to first export the data I want to store to a simpler struct, then pass that to HDF. That does however involve data copying, which is exactly what HDF is trying to avoid (and why they have this CompType which enables the library to reach into your objects to save their data to file).

So I was hoping you'd have better ideas. Ideally I'd be looking for a portable workaround for this problem, but if short of that you could give me an idea that works on x86 and x86_64 with GCC I'd already be immensely grateful.

----- appended later: -----

Greg Hewgill suggested below to store the data in a simple struct, then build the actual class by inheriting from that. For HDF specifically, I think that may not practically work. A more elaborate usage scenario than above:

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

Now, when we're storing instances of the class that_uses_derived_non_pod, we cannot describe its memory layout as if it had a base_pod as member_c. This would get the offsets wrong because derived_non_pod adds funky stuff (like a virtual function table, I guess?).

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

橘虞初梦 2024-07-13 02:32:51

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

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

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

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

Greg Hewgill's solution is probably preferable to this (maybe with composition rather than inheritance).

However, I think that with GCC on x86 and x86_64, offsetof will actually work even for members of non-POD types, as long as it "makes sense". So for example it won't work for members inherited from virtual base classes, because in GCC that's implemented with an extra indirection. But as long as you stick to plain public single inheritance, GCC just so happens to lay out your objects in a way which means every member is accessible at an offset from the object pointer, so the offsetof implementation will give the right answer.

Trouble with this of course is that you have to ignore the warnings, which means if you do something that doesn't work, you'll dereference a close-to-null pointer. On the plus side, the cause of the problem will probably be obvious at runtime. On the minus side, eeew.

[Edit: I've just tested this on gcc 3.4.4, and actually the warning is upgraded to an error when getting the offset of a member inherited from a virtual base class. Which is nice. I'd still be slightly worried that a future version of gcc (4, even, which I don't have to hand) will be more strict, and that if you take this approach your code may in future stop compiling.]

锦爱 2024-07-13 02:32:51

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

Depending on how portable you want to be, you can use offsetof() even on non-POD types. It's not strictly conformant but in the way offsetof() is implemented on gcc and MSVC, it'll work with non-POD types in the current version and the recent past.

撩起发的微风 2024-07-13 02:32:51

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

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

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

You could declare the POD types in a base class, then extend that class (perhaps with private inheritance) to add your additional functionality.

Update to your update: Because an instance of derived_non_pod can also be treated as a base_pod, therefore the offsets to data members must be the same. With regard to implementation, your compiler will allocate the vtable pointer after the fields of the base_pod when laying out the derived_non_pod structure.

It occurs to me that if you use private inheritance, the compiler may be able to choose to reorder the data fields. It's unlikely to do so, however, and making the inheritance protected or public would avoid this possible trap.

谜兔 2024-07-13 02:32:51

我很确定罗尔的答案 并考虑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。同样,这与 vtable 一致,因为 B 的第一个成员后跟虚拟基数 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 类型。

I'm pretty sure that Roel's answer along with consideration for onebyone's answer covers most of what you ask.

struct A
{
  int i;
};

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

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

With g++, the above outputs 4, which is what you'd expect if B has a vtable before the base class member 'i'.

It should be possible though to calculate the offset manually, even for the case where there are virtual bases:

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;
}

With g++, this program outputs 4 and 8. Again this is consistent with the vtable as the first member of B followed by the virtual base A2 and its member 'j'. Finally the non virtual base A1 and its member 'i'.

The key point is that you always calculate the offsets based on the most derived object, ie. B. If the members are private then you may need to add a "getMyOffset" call for each member. This call will perform the calculation where the name is accessible.

You might find the following useful too. I think it's nice to associate all of this with the object that you're building the HDF type for:

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 }
};

And then you can build the H5type via a loop:

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;
}

Now if you add a member to B or one of its bases, then a simple addition to this table will result in the correct HDF type being generated.

霓裳挽歌倾城醉 2024-07-13 02:32:51

问题是,只要您的结构/类不是 extern "C",C++ 编译器就可以自由地重新排列和优化您的结构/类的布局,因此您最终可能会得到重新排序的结构,具体取决于您的编译器。

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

The problem is as soon as your struct/class is not extern "C", C++ compilers are free to rearrange and optimize the layout of your struct/class, so you might end up with an reordered struct depending on your compiler.

There are preprocessor (e.g. #pragma pack) flags for C like behaviour, but they are not portable in most cases.

乖乖 2024-07-13 02:32:51

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

但是使用您当前的解决方案,您已经在使用类型系统了,所以我不确定您是否会在那里丢失任何内容。 除了指向成员的指针的语法很丑陋之外。

Would using a pointer to member work instead of offsetof()? I know that you'd probably have to do all sorts of casting to be able to actually use the pointer since I'm guessing that InsertMember is acting on the type specified in the last parameter at runtime.

But with your current solution you're already going around the type system so I'm not sure that your losing anything there. Except that the syntax for pointers to member is hideous.

把昨日还给我 2024-07-13 02:32:51

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

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

This works fine for me, and I can't see why it wouldn't:

#define myOffset(Class,Member) ({Class o; (size_t)&(o.Member) - (size_t)&o;})
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文