24

これを行う際に副作用はありますか:

C コード:

struct foo {
      int k;
};

int ret_foo(const struct foo* f){ 
    return f.k; 
}

C++ コード:

class bar : public foo {

   int my_bar() { 
       return ret_foo( (foo)this ); 
   }

};

C++ コードの周りに がありextern "C"、各コードは独自のコンパイル ユニット内にあります。

これはコンパイラ間で移植可能ですか?

4

10 に答える 10

30

これは完全に合法です。C ++では、クラスと構造体は同じ概念ですが、すべての構造体メンバーがデフォルトでパブリックになっている点が異なります。それが唯一の違いです。したがって、構造体を拡張できるかどうかを尋ねるのは、クラスを拡張できるかどうかを尋ねるのと同じです。

ここに1つの注意点があります。コンパイラ間でのレイアウトの一貫性は保証されません。したがって、CコードをC ++コードとは異なるコンパイラでコンパイルすると、メンバーのレイアウト(特にパディング)に関連する問題が発生する可能性があります。これは、同じベンダーのCおよびC++コンパイラを使用している場合でも発生する可能性があります。

これをgccとg++で発生させました。私はいくつかの大きな構造体を使用するプロジェクトに取り組みました。残念ながら、g ++は構造体をgccよりも大幅に緩くパックしたため、CコードとC++コード間でオブジェクトを共有する際に重大な問題が発生しました。最終的には、CコードとC ++コードで構造体が同じように扱われるように、手動でパッキングを設定してパディングを挿入する必要がありました。ただし、この問題はサブクラス化に関係なく発生する可能性があることに注意してください。実際、この場合、C構造体をサブクラス化していませんでした。

于 2008-09-24T16:30:06.163 に答える
11

このような奇妙なサブクラスの使用はお勧めしません。継承ではなく構成を使用するように設計を変更することをお勧めします。メンバーを1人にするだけ

foo* m_pfoo;

バークラスで、同じ仕事をします。

他にできることは、もう 1 つのクラス FooWrapper を作成することです。これには、対応する getter メソッドを使用して構造自体が含まれます。次に、ラッパーをサブクラス化できます。このようにして、仮想デストラクタの問題はなくなりました。

于 2008-09-24T14:04:18.683 に答える
3

「具体的なクラスから派生することはありません。」—サッター

「非リーフクラスを抽象化します。」—マイヤーズ

非インターフェースクラスをサブクラス化するのは単に間違っています。ライブラリをリファクタリングする必要があります。

技術的には、たとえば、基本クラスのサブオブジェクトへのポインタによって派生クラスへのポインタを削除することによって未定義の動作を呼び出さない限り、必要なことを実行できます。extern "C"C++コードも必要ありません。はい、ポータブルです。しかし、それは貧弱なデザインです。

于 2008-09-24T14:21:38.970 に答える
3

これは完全に合法ですが、他のプログラマーにとっては混乱を招くかもしれません。

継承を使用して、C 構造体をメソッドとコンストラクターで拡張できます。

サンプル :

struct POINT { int x, y; }
class CPoint : POINT
{
public:
    CPoint( int x_, int y_ ) { x = x_; y = y_; }

    const CPoint& operator+=( const POINT& op2 )
    { x += op2.x; y += op2.y; return *this; }

    // etc.
};

構造体を拡張することは「もっと」悪いことかもしれませんが、禁止されているわけではありません。

于 2008-09-24T14:31:06.340 に答える
2

これは完全に合法であり、実際にはMFCCRectクラスとCPointクラスで確認できます。CPointはPOINT(windef.hで定義)から派生し、CRectはRECTから派生します。単にメンバー関数でオブジェクトを装飾しているだけです。より多くのデータでオブジェクトを拡張しない限り、問題はありません。実際、デフォルトで初期化するのが面倒な複雑なC構造体がある場合、デフォルトのコンストラクターを含むクラスでそれを拡張することは、その問題に対処する簡単な方法です。

これを行っても:

foo *pFoo = new bar;
delete pFoo;

コンストラクタとデストラクタは簡単で、余分なメモリを割り当てていないので、問題ありません。

また、実際にはC ++型をC関数に渡していないため、C++オブジェクトを「extern"C"」でラップする必要はありません。

于 2008-09-24T15:43:36.400 に答える
2

うわー、それは悪だ。

これはコンパイラ間で移植可能ですか?

間違いなくそうではありません。次の点を考慮してください。

foo* x = new bar();
delete x;

これが機能するためには、foo のデストラクタが仮想である必要がありますが、明らかにそうではありません。ただし、使用しないnew限り、派生オブジェクトにカスタム デストラクタがない限り、幸運になる可能性があります。

/EDIT:一方、コードが質問のようにのみ使用されている場合、継承は構成よりも利点がありません。m_pGladiator のアドバイスに従ってください。

于 2008-09-24T14:00:55.057 に答える
1

必ずしも問題ではないと思います。動作は明確に定義されており、生涯の問題に注意している限り(C ++コードとCコードの間で割り当てを混在させたり一致させたりしないでください)、希望どおりに動作します。コンパイラ間で完全に移植可能である必要があります。

デストラクタの問題は現実的ですが、基本クラスのデストラクタがC構造体だけでなく、仮想ではない場合はいつでも当てはまります。これは知っておく必要のあることですが、このパターンの使用を妨げるものではありません。

于 2008-09-24T14:07:49.750 に答える
1

これは機能しますが、移植性はありますが、仮想関数(デストラクタを含む)を使用することはできません。

これを行う代わりに、BarにFooを含めることをお勧めします。

class Bar
{
private:
   Foo  mFoo;
};
于 2008-09-24T14:08:50.717 に答える
0

それはおそらくうまくいくでしょうが、私はそれが保証されているとは思いません。以下は、ISO C++10/5からの引用です。

基本クラスのサブオブジェクトは、同じタイプの最も派生したオブジェクトのレイアウトとは異なるレイアウト(3.7)を持つ場合があります。

「現実の世界」でこれが実際にどのように当てはまるのかを理解するのは難しいです。

編集:

要するに、標準では、基本クラスのサブオブジェクトのレイアウトが同じ基本タイプの具体的なオブジェクトと異なる可能性がある場所の数が制限されていないということです。その結果、POD性などの仮定は、基本クラスのサブオブジェクトには必ずしも当てはまりません。

編集:

別のアプローチであり、動作が明確に定義されているアプローチは、「foo」を「bar」のメンバーにし、必要に応じて変換演算子を提供することです。

class bar {
public:    
   int my_bar() { 
       return ret_foo( foo_ ); 
   }

   // 
   // This allows a 'bar' to be used where a 'foo' is expected
   inline operator foo& () {
     return foo_;
   }

private:    
  foo foo_;
};
于 2008-09-24T14:20:14.510 に答える
0

ret_fooを単純にメンバーメソッドにしない理由がわかりません。現在の方法では、コードを非常に理解しにくくしています。そもそも、メンバー変数とget / setメソッドで実際のクラスを使用することの難しさは何ですか?

C ++で構造体をサブクラス化することは可能ですが、危険なのは、誰かが実際にコーディングすることはめったにないため、コーディングした内容を他の人が理解できないことです。代わりに、堅牢で一般的なソリューションを選びます。

于 2008-09-24T14:10:12.850 に答える