9

上記のタイトルに示されているように、私の質問は単純に、C++ キャストがターゲット クラスの新しいオブジェクトを作成するかどうかです。もちろん、これを質問する前に Google、MSDN、IBM、stackoverflow の検索ツールを使用しましたが、質問に対する適切な回答が見つかりません。

仮想継承を使用して解決されたダイヤモンド問題の次の実装を考えてみましょう。

#include <iostream>
#include <cstdlib>

struct A
{
  int a;

  A(): a(2) {  }
};

struct B: virtual public A
{
  int b;

  B(): b(7) {  }
};

struct C: virtual public A
{
  int c;

  C(): c(1) {  }
};

struct END: virtual public B, virtual public C
{
  int end;

  END(): end(8) {  }
};

int main()
{
  END *end = new END();
  A *a = dynamic_cast<A*>(end);
  B *b = dynamic_cast<B*>(end);
  C *c = dynamic_cast<C*>(end);

  std::cout << "Values of a:\na->a: " << a->a << "\n\n";
  std::cout << "Values of b:\nb->a: " << b->a << "\nb->b: " << b->b << "\n\n";
  std::cout << "Values of c:\nc->a: " << c->a << "\nc->c: " << c->c << "\n\n";

  std::cout << "Handle of end: " << end << "\n";
  std::cout << "Handle of a: " << a << "\n";
  std::cout << "Handle of b: " << b << "\n";
  std::cout << "Handle of c: " << c << "\n\n";
  system("PAUSE");
  return 0;
}

私が理解したように、B と C の実際の構造は、通常、A の埋め込みインスタンスと B の変数の両方で構成されています。あいまいさを避けるために、B と C の仮想 A が END の 1 つの埋め込みオブジェクトにマージされるため、C は破棄されます。(私がいつも思っていたように)dynamic_castは通常、ポインターによって格納されたアドレスを、埋め込まれた(キャストの)ターゲットクラスのオフセットだけ増加させるだけなので、ターゲット(BまたはC)クラスがいくつかに分割されているという事実のために問題が発生します部品。

しかし、MSVC++ 2011 Express で例を実行すると、すべてが期待どおりに実行されます (つまり、すべて *.a 出力 2 で実行されます)。ポインターはわずかに異なるだけです。したがって、キャストはソースポインターのアドレスを B の / C のインスタンスの内部オフセットだけ移動するだけだと思います。

しかし、どのように?B / C の結果のインスタンスは、共有された A オブジェクトの位置をどのように認識しますか。END オブジェクト内には A オブジェクトが 1 つしかありませんが、通常は B と C に A オブジェクトがあるため、B または C のいずれかに A のインスタンスがあってはなりませんが、実際には両方にインスタンスがあるように見えます。

またはvirtual、A から virtual を継承する各基本クラスのそれぞれの A オブジェクトを削除せずに、A のメンバーへの呼び出しのみを中央の A オブジェクトに委譲します (つまりvirtual、実際には、継承されたオブジェクトと埋め込まれたオブジェクトの内部構造を破壊せず、仮想化されたオブジェクトを使用しないだけです ( = 共有) メンバー)?

またはvirtual、そのようなキャストされたオブジェクトが「分散」を処理するために、新しい「オフセットマップ」(つまり、クラスインスタンスへのポインターに関連するすべてのメンバーのアドレスオフセットを示すマップ、実際の用語はわかりません) を作成しますか?

すべてを明確にしたことを願っています。事前に感謝します
BlueBlobb

PS:
文法の間違いがありましたら申し訳ありません。私はビールが大好きなバイエルン人であり、ネイティブ スピーカーではありません :P

編集:
すべての int a のアドレスを出力するためにこれらの行を追加した場合:

  std::cout << "Handle of end.a: " << &end->a << "\n";
  std::cout << "Handle of a.a: " << &a->a << "\n";
  std::cout << "Handle of a.b: " << &b->a << "\n";
  std::cout << "Handle of a.c: " << &c->a << "\n\n";

それらは同じであり、実際には A オブジェクトが 1 つしかないことを意味します。

4

4 に答える 4

6

私の質問は、単に C++ キャストがターゲット クラスの新しいオブジェクトを作成するかどうかです。

はい、クラス型へのキャストは、その型の新しい一時オブジェクトを作成します。

あなたの例はどこにもクラスにキャストしないことに注意してください.それが実行する唯一のキャストはポインタ型です. これらのキャストは、ポインターの新しいインスタンスを作成しますが、指すオブジェクトのインスタンスは作成しません。あなたの例が何を示すことになっていたのか、それがあなたの述べた質問にどのように関連しているのかわかりません。

また、dynamic_cast使用する場所は不要です。暗黙的な変換も同様に機能します。

(私がいつも思っていたように)dynamic_castは通常、ポインターによって格納されたアドレスを、埋め込まれた(キャストの)ターゲットクラスのオフセットだけ増加させるだけなので

static_castか何かを考えているに違いない。dynamic_castはるかに強力です。たとえば、コンパイル時には関係がなくても、別のブランチに移動してからバックアップすることで、 from からB*toにキャストできます。実行時の型情報を利用します。C*END*dynamic_cast

B / C の結果のインスタンスは、共有された A オブジェクトの位置をどのように認識しますか。

これは実装依存です。典型的な実装では、派生クラス インスタンス内にスペースを確保して、その仮想基本クラス インスタンスへのオフセットを格納します。最も派生したクラスのコンストラクターは、これらすべてのオフセットを初期化します。

于 2013-09-07T03:13:02.973 に答える
5

いいえ、多重継承の効果を見ているだけです。ポインターを別の基本型にキャストするには、その正確な型を表すオブジェクトの部分に合わせてポインターを調整する必要があります。コンパイラは、ポインターの元の型と結果の型を認識しているため、必要なオフセットを適用できます。派生型が「is-a」要件を満たすためには、すべての基本型をエミュレートするために必要な構造が組み込まれている必要があります。

キャストによって新しいオブジェクトを作成できるケースが 1 つあります。それは、ポインターまたは参照型以外の型にキャストする場合です。多くの場合、その型のキャスト演算子を定義していない限り、それは不可能です。

于 2013-09-07T03:13:23.643 に答える
0

あなたが示した例では、ポインターを使用しています。

A* a = dynamic_cast<A*>(end);

したがって、ここで作成される唯一の「新しい」ものは、​​「end」が指すオブジェクトの「A」vtable を指す別のポインターです。使用しているクラス/構造体型の新しいオブジェクトを実際に構築するわけではありません。

対比

A a;
B b(a);

ここで、新しいオブジェクトが作成されます。ただし、それ以外の場合、キャストは宛先キャスト タイプの新しいオブジェクトを作成しません。

ポインターが異なる理由は、基になるオブジェクトのデータ セクションに先行する異なる vtables を指しているためです。

例:

#include <iostream>

using namespace std;

struct A {
    int a[64];
    A() { cout << "A()" << endl; }
    A(const A&) { cout << "A(A&)" << endl; }
    A& operator = (const A&) { cout << "A=A" << endl; return *this; }
};

struct B : virtual public A {
    int b[64];
    B() { cout << "B()" << endl; }
    B(const B&) { cout << "B(B&)" << endl; }
    B(const A&) { cout << "B(A&)" << endl; }
    B& operator = (const B&) { cout << "B=B" << endl; return *this; }
    B& operator = (const A&) { cout << "B=A" << endl; return *this; }
};

struct C : virtual public A {
    int c[64];
    C() { cout << "C()" << endl; }
    C(const C&) { cout << "C(C&)" << endl; }
    C(const B&) { cout << "C(B&)" << endl; }
    C(const A&) { cout << "C(A&)" << endl; }
    C& operator = (const C&) { cout << "C=C" << endl; return *this; }
    C& operator = (const B&) { cout << "C=B" << endl; return *this; }
    C& operator = (const A&) { cout << "C=A" << endl; return *this; }
};

struct END : virtual public B, C {
    int end[64];
    END() { cout << "END()" << endl; }
    END(const END&) { cout << "END(END&)" << endl; }
    END(const C&) { cout << "END(C&)" << endl; }
    END(const B&) { cout << "END(B&)" << endl; }
    END(const A&) { cout << "END(A&)" << endl; }
    END& operator = (const END&) { cout << "END=END" << endl; return *this; }
    END& operator = (const C&) { cout << "END=C" << endl; return *this; }
    END& operator = (const B&) { cout << "END=B" << endl; return *this; }
    END& operator = (const A&) { cout << "END=A" << endl; return *this; }
};

int main() {
    END* end = new END();

    A *a = dynamic_cast<A*>(end);
    B *b = dynamic_cast<B*>(end);
    C *c = dynamic_cast<C*>(end);

    std::cout << "end = " << (void*)end << std::endl;
    std::cout << "a = " << (void*)a << std::endl;
    std::cout << "b = " << (void*)b << std::endl;
    std::cout << "c = " << (void*)c << std::endl;

    // the direct pointers are going to have to differ
    // to point to the correct vtable. what about 'a' in all cases?
    std::cout << "end->a = " << (void*)&(end->a) << std::endl;
    std::cout << "a->a = " << (void*)&(a->a) << std::endl;
    std::cout << "b->a = " << (void*)&(b->a) << std::endl;
    std::cout << "c->a = " << (void*)&(c->a) << std::endl;


}

ここで実行されていることがわかります: http://ideone.com/0QAoWE

于 2013-09-07T04:00:22.240 に答える