10

非 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 固有のマクロです。

問題はもちろん、example-class がもう少し機能的になるとすぐに、もはや POD タイプではなくなるため、offsetof を使用すると未定義の結果が生じることです。

私が考えることができる唯一の回避策は、最初に保存したいデータをより単純な構造体にエクスポートしてから、それを HDF に渡すことです。ただし、これにはデータのコピーが含まれます。これはまさに HDF が回避しようとしているものです (そして、ライブラリがオブジェクトに到達してデータをファイルに保存できるようにするこの CompType がある理由)。

だから、もっといいアイデアがあればいいなと思っていました。理想的には、この問題の移植可能な回避策を探していますが、それが不足している場合でも、GCC を使用して x86 および x86_64 で動作するアイデアを教えていただければ幸いです。

----- 後で追加: -----

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 として持っているかのように、そのメモリ レイアウトを記述することはできません。派生した_non_podがファンキーなものを追加するため、これはオフセットを間違って取得します(仮想関数テーブルのようなものだと思いますか?)。

4

7 に答える 7

5

Greg Hewgill の解決策は、おそらくこれよりも好ましいでしょう (おそらく、継承ではなく合成を使用します)。

ただし、x86 および x86_64 上の GCC では、offsetof は、「意味がある」限り、非 POD 型のメンバーに対しても実際に機能すると思います。たとえば、仮想基本クラスから継承されたメンバーでは機能しません。GCC では追加の間接化で実装されているためです。しかし、単純なパブリック単一継承に固執する限り、GCC はたまたまオブジェクト ポインターからのオフセットですべてのメンバーにアクセスできることを意味する方法でオブジェクトをレイアウトするため、offsetof 実装は正しい答えを提供します。

もちろん、これに関する問題は、警告を無視する必要があることです。つまり、機能しないことを行うと、null に近いポインターを逆参照することになります。プラス面としては、問題の原因はおそらく実行時に明らかになるでしょう。マイナス面はええええ。

[編集: gcc 3.4.4 でこれをテストしたところ、実際には、仮想基本クラスから継承されたメンバーのオフセットを取得するときに、警告がエラーにアップグレードされます。いいですね。gcc の将来のバージョン (4 でさえ、私が手に入れる必要はありません) がより厳密になり、このアプローチを採用した場合、コードが将来コンパイルを停止する可能性があることを、私はまだ少し心配しています.]

于 2008-10-07T11:11:29.733 に答える
4

移植性に応じて、POD 以外の型でも offsetof() を使用できます。厳密には準拠していませんが、offsetof() が gcc および MSVC で実装されている方法では、現在のバージョンおよび最近の過去の非 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 またはそのベースの 1 つに追加すると、このテーブルに単純に追加するだけで正しい HDF タイプが生成されます。

于 2009-08-05T18:02:28.567 に答える
0

問題は、構造体/クラスが extern "C" ではない場合、C++ コンパイラが構造体/クラスのレイアウトを自由に再配置および最適化できるため、コンパイラによっては構造体の順序が変更される可能性があることです。

C のような動作のためのプリプロセッサ (#pragma pack など) フラグがありますが、ほとんどの場合、それらは移植可能ではありません。

于 2008-10-07T11:05:12.460 に答える
0

offsetof() の代わりにメンバへのポインタを使用できますか? 実行時に InsertMember が最後のパラメーターで指定された型に作用していると推測しているため、ポインターを実際に使用できるようにするには、おそらくあらゆる種類のキャストを行う必要があることを知っています。

しかし、あなたの現在のソリューションでは、すでに型システムを回避しているので、そこで何かを失うかどうかはわかりません。メンバーへのポインターの構文がひどいことを除いて。

于 2008-10-07T17:34:23.393 に答える