5

私が取り組んでいるプロジェクトの C++ と構造体を調べています。現時点では、「連鎖した」テンプレート構造を使用して、データ フィールドを疑似特性として追加しています。

それは機能しますが、以下の例のように多重継承のようなものを好むと思います:

struct a {
    int a_data;
}; // 'Trait' A

struct b {
    int b_data;
}; // 'Trait' B

struct c : public a, public b {
    int c_data;
}; // A composite structure with 'traits' A and B.

struct d : public b {
    int d_data;
}; // A composite structure with 'trait' B.

私の実験的なコード例は、それらがうまく機能することを示していますが、物事が複雑になったときに実際にどのように機能するかについては少し困惑しています.

例えば:

b * basePtr = new c;
cout << basePtr->b_data << endl;
b * basePtr = new d;
cout << basePtr->b_data << endl;

これは、ポインターをパラメーターとして関数呼び出しを行っても、毎回正常に機能します。

私の質問は、派生した構造体の 1 つで b_data が格納されている場所をコードがどのように認識するのかということです。私が知る限り、構造体は余分なデータのないコンパクトな構造体を使用しています (つまり、3 つの int 構造体は 12 バイト、2 つの int は 8 バイトなど)。特定の構造体のどこに a_data と b_data が格納されているかを示すには、ある種の追加のデータ フィールドが必要なのでしょうか?

それはすべて関係なく機能するように見えるので、好奇心の問題です.複数の実装が使用されている場合は、1つの例を喜んで受け入れます. プロセス間メッセージキューを介してこれらの構造体の背後にあるバイトを転送し、反対側で問題なくデコードされるかどうかを知りたいので、少し懸念があります (キューを使用するすべてのプログラムは同じコンパイラでコンパイルされ、単一のプラットフォームで実行されます)。

4

7 に答える 7

3

どちらの場合もTruly型のオブジェクトへのポインタなbasePtrので問題ありません。このオブジェクトが完全なオブジェクトではなく、より派生したオブジェクト (これは実際には専門用語です) のサブオブジェクトであるという事実は重要ではありません。b

からへの (静的で暗黙的な) 変換と からd *b *の変換では、実際にサブオブジェクトを指すようにポインター値を調整します。すべての情報は静的に知られているため、コンパイラはこれらすべての計算を自動的に行います。c *b *b

于 2013-01-16T20:49:11.567 に答える
2

メモリ管理クラス継承の内容の下にある C++ クラスのウィキペディアの値を読む必要があります。

基本的に、コンパイラはクラス構造を作成するため、コンパイル時にクラスの各部分へのオフセットを認識します。

変数を呼び出すと、コンパイラは型とその構造を認識します。変数を基底クラスにキャストする場合は、正しいオフセットにジャンプするだけで済みます。

于 2013-01-16T20:49:16.150 に答える
2

ほとんどの実装では、ポインタ変換、たとえば からc*b*の変換により、必要に応じてアドレスが自動的に調整されます。声明では

b * basePtr = new c;

新しい式は、基本クラスのサブオブジェクト、基本クラスのサブオブジェクト、およびメンバーのサブオブジェクトcを含むオブジェクトを割り当てます。生のメモリでは、これはおそらく 3 つの int のように見えます。新しい式は、作成された完全なオブジェクトのアドレスを返します。これは、(ほとんどの実装では)基本クラス サブオブジェクトのアドレスおよびメンバー サブオブジェクトのアドレスと同じです。abc_datacaa_data

しかし、new ctypeの式 がポインターのc*初期化に使用され、暗黙的な変換が行われます。b*コンパイラは、完全なオブジェクト内の基底クラス サブオブジェクトbasePtrのアドレスに設定します。コンパイラはオブジェクトからその一意のサブオブジェクトへのオフセットを知っているので、難しいことではありません。bccb

その後、式 likebasePtr->b_dataは、完全なオブジェクト型が何であったかを知る必要はありません。b_dataそれが の最初にあることを知っているだけなので、ポインタbを単純に逆参照できます。b*

于 2013-01-16T20:49:32.923 に答える
1

はい、各サブコンポーネントが集合体に対して持つオフセットを定義する追加のフィールドがあります。ただし、それらは集合体自体には格納されませんが、データセグメントの隠れた側にある補助構造に格納される可能性があります(ただし、その方法についての最終的な選択はコンパイラの設計者に任されています)。

あなたのオブジェクトは多形ではありません(そしてあなたはそれらを間違って使用しました、しかし私は後でこれに行きます)、しかしただ次のような複合体です:

c[a[a_data],b[b_data],c_data];
            ^
            b* points here 

d[b[b_data],d_data]
  ^
  b* points here

(実際のレイアウトは、特定のコンパイラー、さらには使用される最適化フラグに依存する場合があることに注意してください)

bの始まりに対する始まりのオフセットは、特定のオブジェクトインスタンスに依存しないため、オブジェクトにとどまるために必要な値ではなく、コンパイラに知られているが必ずしも利用できるとは限らない一般的なc説明になります。君。ddc

コンパイラは、acまたはaが与えられるdと、コンポーネントがどこbから始まるかを認識します。しかし、与えられたものは、それがaまたは。のb内部にあるかどうかを知ることができません。dc

オブジェクトを誤って使用した理由は、オブジェクトの破壊を気にしなかったためです。それらをで割り当てますが、後でそれらを-edするnewことはありません。delete

delete baseptrまた、bサブコンポーネントには、実際に(実行時に)どのアグリゲートが含まれているかを示すものがないため、単に呼び出すことはできません。

それを回避するための2つのプログラミングスタイルがあります。

  • 従来のOOPは、実際の型が実行時に既知であると想定し、すべてのクラスにvirtualデストラクタがあるように見せかけます。これにより、すべての構造体に追加の「ゴースト」フィールド(vテーブルポインタ、「補助記述子」、すべての仮想関数のアドレスを含む)によって開始されたデストラクタ呼び出しdeleteを、最も派生したものに実際にディスパッチします(したがって、削除pbaseは実際に呼び出されるc::~cd::~d、実際のオブジェクトによって異なります)

  • ジェネリックプログラミングスタイルでは、他の方法で(ほとんどの場合、テンプレートパラメータから)実際の派生型を知っていると想定しているため、実際の派生型はわかりませんdelete pbaseが、static_cast<actual_derived_class*>(pbase)

于 2013-01-16T21:08:30.187 に答える
1

詳細は C++ の実装次第ですが、このような非仮想継承の場合、次のように考えることができます。

cには 2 つのサブオブジェクトがあり、1 つは typeaで、もう 1 つは typebです。

cへのポインターをへのポインターにキャストするbと、コンパイラーは十分に賢く、キャストの結果は、元のポインターによって参照されるオブジェクトのbサブオブジェクトへのポインターになります。cこれには、返されるポインターの数値の変更が含まれる場合があります。

通常、単一継承では、サブオブジェクト ポインターは元のポインターと同じ数値になります。多重継承では、そうではないかもしれません。

于 2013-01-16T20:49:34.510 に答える
0

継承は、メソッドがその配下の別のクラスから関数を再利用するための抽象化です。メソッドがその下のクラスにある場合、そのクラスからメソッドを呼び出すことができます。構造体を使用すると、変数や関数を使用するクラスに似たデータ構造のように変数を持つことができます。

class trait
{
  //variable definition 
  //variable declaration

  function function_name(variable_type variable_name, and more)
  {
    //operation on variables in function call
  }

  variable_name = function_name(variable_name);

  struct struct_name
  { 
    //variable definition
  }

  struct_name = {value_1, value_2, and more}

  operation on struct_name.value_1
} 
于 2013-01-16T21:27:55.907 に答える
0

コンパイル時の知識と実行時の知識には違いがあります。コンパイラの仕事の一部は、コンパイル時の情報をできるだけ多く利用して、実行時に何かをしなくても済むようにすることです。

この場合、特定の型の各データが正確にどこにあるかのすべての詳細は、コンパイル時に認識されます。したがって、コンパイラは実行時にそれを知る必要はありません。特定のメンバーにアクセスするときはいつでも、コンパイル時の知識を使用して、必要なデータの適切なオフセットを計算します。

同じことがポインタ変換にも当てはまります。適切なサブパーツのポイントを確認するために、変換時にポインター値を調整します。

これが機能する理由の一部は、個々のクラスまたは構造体からのデータ値が、クラス定義で言及されていない他の値と決してインターリーブされないことです。たとえその構造体が構成または継承によって別の構造体のサブコンポーネントであってもです。 . したがって、個々の構造体の相対的なレイアウトは、メモリ内のどこにあるかに関係なく、常に同じです。

于 2013-01-17T03:46:11.973 に答える