4

さまざまなC および C++コードで ( 経由で)使用される C 構造体がありextern "C"ます。

#ifdef __cplusplus
extern "C" {
#endif

  typedef struct A A;
  struct A {
    /*some members*/
  };

#ifdef __cplusplus
}
#endif

割り当て、初期化、および解放は、私の制御下にある個別のメンバー関数によって行われますが、メンバーへのアクセスは、いたるところでアクセスされるため、私は制御しません。

問題は、システム全体で頻繁に使用されるヘッダーの の定義を変更できないことですstructが、それでも型を拡張していくつかのメンバーを追加したいのです。これは C++ と C の両方としてコンパイルする必要があるため、単純に派生型を作成することはできませんstruct B : public A。だから私の考えは、このタイプを cpp ファイルに追加することでした:

#ifdef __cplusplus
extern "C" {
#endif

  typedef struct B B;
  struct B {
    A parent; // <---- public inheritance
    /*some members*/
  };

#ifdef __cplusplus
}
#endif

これで、cpp ファイル内のすべての関数を変更できますがA*、コンパイル ユニットの外部で配布して受け入れる必要Bがあります。

だから、これを行うための正気で明確に定義された方法があるかどうか疑問に思っています。B*sを単純にキャストしA*sたり戻したりできますか、それとも明示的に変換する必要がありますか:

A* static_cast_A(B* b) {
  return &(b->parent);
}
B* static_cast_B(A* a) {
  B* const b = 0;
  unsigned const ptrdiff = (unsigned)((void*)(&(b->parent)));
  return (B*)(((void*)a)-ptrdiff);
}

これにはさらに問題がありますか、それともまったく別の方法で行う必要がありますか?

4

2 に答える 2

4

仮想テーブルを (仮想メソッドまたはデストラクタを追加することによって) に導入せず、その構造体のメンバーが最初の要素である限り、struct B大文字とparent小文字Bを区別してA.

AへのキャストBも安全ですが、元のポインターが実際に何かを指していて、構造自体Bだけのように他の何かを指していない場合に限ります。A

whenparentが struct の最初のメンバーでない場合、たとえば Linux カーネルで見られるようにマクロBを使用する必要があります。container_ofこれは、メンバー ポインターのオフセットで機能します (つまり、適切な説明がここにあります)。

また、デフォルトでは、両方の構造体のアライメントが同じになります。構造体のアラインメントの 1 つを微調整した場合、コンパイラがどのように配置Aされるかはわかりません。Bこれはコンパイラに依存すると思いますが、簡単に理解できるはずです。

仮想テーブルがある場合Bは、ポインターを正しく変換するためにコンパイラの内部構造を知っている必要があります。たとえば、GCCx86_64 では 8 バイトのオフセットが追加されるため、変換時に手動でオフセットする必要があります。ただし、仮想継承(仮想基本クラス)の場合は機能しません。解決するには、RTTIなどを使用する必要があります...しかし、これは質問の範囲外であり、しないことをお勧めしますその道を行きなさい。

それが役に立てば幸い。

于 2012-09-16T01:35:08.417 に答える
2

アラインメント要件が問題を引き起こすことはありません。実際、これは C で継承を偽造するデフォルトの方法です。

ただし、static_cast_B()移植性はありません(特にvoid *算術演算と変換unsignedの場合sizeof (void *) > sizeof (unsigned)- 後者は引き続き機能するはずですが、コードの匂いがします)。

代わりに次のコードを使用することをお勧めします。

#include <stddef.h>

B* static_cast_B(A* a) {
  return (B*)((char*)a - offsetof(B, parent));
}
于 2012-09-16T08:05:57.197 に答える