7

ダウンキャストとアップキャストを使用している場合、ポインター キャストで実際に何が起こるのか疑問に思っています。2つ質問があります。最初の 2 つはコメントです。Q3が終わりました。

#include<iostream>
using namespace std;
class A{
  public:
    virtual void  f()
    {
      cout<<"A"<<endl;
    }
};
class B: public A{
  public:
     virtual void   f()
    {
      cout<<"B"<<endl;
    }
};
int main()
{
  A* pa =new A();
  B* pb =new B();
  A* paUpcast= new B();
  /*
    Q1:
    Is the line above equivalent to the following?
    A* paUpcast = (A*) B;
  */
  B* pbDowncast=(B*)paUpcast;
  /*
    Q2:Why we cannot use the following 2 code;
    B* pbDowncast=new A();
    B* pbDowncast = (B*) pa;
  */
  pa->f();
  pb->f();
  paUpcast->f();
  pbDowncast->f();


  return 1;
}

Q3: 仮想関数とポインター キャストを一緒に使用するとどうなるかを推測するルールをまとめようとしていますが、わかりません。

本来、仮想関数はポインターが実際に指し示した場所に私たちを導くと思います。したがって、入力すると

A* paUpcast= new B();
paUpcast->f();

paUpcast は実際には B オブジェクトを指しているため、Af() が仮想関数の場合、2 行目には「B」が表示されます。

ただし、入力すると

B* pbDowncast=(B*)pa;
pbDowncast->f();

「B」ではなく「A」が表示されるため、矛盾が生じます。

誰かが説明したり、ヒントを見せたりできますか? どうもありがとう

4

2 に答える 2

10

私はそれをどのように理解しているかを説明しようと思います。私を助けるヒントは、レゴのピースについて考えることです。

あなたの場合、2つのレゴピースがあります。1つは名前が付けられA、もう1つは名前が付けられています...しかし、ピースが2つのピースを取り付けて形成されたピースであるBと想像してください。一方のピースは、同じタイプです。BA

 A     B
+-+  +-+-+
|a|  |a|b|
+-+  +-+-+

次に、ポインターを使用して各レゴピースを参照しますが、各ピースには独自の形状があるため、想像してみてください。

A* pa =new A();
B* pb =new B();
A* paUpcast= new B();

A *pa -->       +-+   new A()
                |a|
                +-+
B* pb -->       +-+-+ new B()
                |a|b|
                +-+-+
A* paUpcast --> +-+-+ new B()
                |a|b|
                +-+-+

paUpcastポインタは型のポインタですが、型の一部Aを保持していることに注意してください。ご覧のとおり、その部分はベースよりもわずかに大きい部分です。BBA

これはあなたが話しているアップキャストです。ベースポインタは、継承ツリーで下向きに関連するものをすべて保持できるワイルドカードのようなものです。

A * paUpcast = new B();

上記の行は次の行と同等ですか?

A * paUpcast =(A *)B;

さて、あなたが本当にこれを書きたいと仮定すると:A* paUpcast = (A*) new B();はい、そうです。Bクラスの新しいインスタンスを作成し、それをAクラスへのポインターに格納します。ポインターに割り当てる前に新しいインスタンスを変換しても、基本クラスのポインターに格納されるという事実は変わりません。

次の2つのコードを使用できない理由。

B * pbDowncast = new A();

B * pbDowncast =(B *)A;

レゴのピースを覚えておいてください。やっている間に何が起こりB* pbDowncast=new A()ますか?:

B* pbDowncast --> +-+ new A()
                  |a|
                  +-+

新しいベースクラスインスタンスを作成し、それを派生クラスへのポインタに格納します。レゴピースが収まらない場合は、ベースを派生クラスとして処理しようとしています。作品は、その種と見なされるために必要な余分なものAを欠いています。これらすべてのものは、レゴピースの余分な部分に「保存」されます。BB = all the A stuff plus something more

  B
+-+-----------------------------------+
|a|extra stuff that only B pieces have|
+-+-----------------------------------+

Bクラスだけが持っているメソッドを呼び出そうとするとどうなりますか?すべてのメソッドをB呼び出すことができるポインターがBありますが、作成したインスタンスAは、メソッドを持たない種類のものでありB、これらすべての余分なものを使用して作成されたわけではありません。

ただし、入力すると

B * pbDowncast =(B *)pa;

pbDowncast-> f();

「B」の代わりに「A」を表示すると、矛盾が発生します。

それは私にとって矛盾に似ていません、レゴの部分を思い出して、paポインターはタイプの部分を指していますA

A *pa --> +-+
          |a|
          +-+

この作品にはすべてのBものが欠けています。実際には、標準の出力にf()印刷する方法がありません...しかし、出力に印刷する方法があります。Bf()A

お役に立てば幸いです。

編集:

ダウンキャストを使うのも不適切だということにも同意しているようですね。

いいえ、同意しません。ダウンキャスティングはまったく不適切ではありませんが、用途によっては不適切になります。すべてのC++ツールと同様に、ダウンキャスティングにはユーティリティと使用範囲があります。良い使い方を尊重するすべてのトリックは適切です。

では、ダウンキャスティングツールの良い使い方は何でしょうか?私見では、コードやプログラムフローを壊さず、コードを可能な限り読みやすく維持し、プログラマーが自分のしていることを知っていれば(私にとって最も重要です)。

継承の可能性のあるブランチを取得するダウンキャストは、結局のところ一般的な方法です。

A* paUpcast = new B();
static_cast<B*>(paUpcast)->f();

しかし、より複雑な継承ツリーでは面倒です。

#include<iostream>
using namespace std;
class A{
public:
    virtual void  f()
    {
        cout<<"A"<<endl;
    }
};
class B: public A{
public:
    virtual void   f()
    {
        cout<<"B"<<endl;
    }
};
class C: public A{
public:
    virtual void   f()
    {
        cout<<"C"<<endl;
    }
};

A* paUpcast = new C();
static_cast<B*>(paUpcast)->f(); // <--- OMG! C isn't B!

これに対処するために、あなたは利用することができますdynamic_cast

A* paUpcast = new C();

if (B* b = dynamic_cast<B*>(paUpcast))
{
    b->f();
}

if (C* c = dynamic_cast<C*>(paUpcast))
{
    c->f();
}

しかし、パフォーマンスの欠如でdynamic_castよく知られています。内部オブジェクト識別子や変換演算子など、いくつかの代替案を検討できますが、質問に固執するために、ダウンキャストは正しく使用されていればまったく悪くありません。dynamic_cast

于 2012-10-18T10:18:11.057 に答える
3

C ++ではCスタイルのキャストを使用しないでください!そうする理由はなく、それはあなたがやろうとしていることの意味を曖昧にし、さらに重要なことに、あなたがキャストしていると思うものをキャストしない場合、それは興味深い結果をもたらす可能性があります。上記の状況では、常に代わりに使用できますがstatic_cast<T*>()、代わりにすべてのダウンキャストを使用する方がよいでしょうdynamic_cast<T*>()。後者は、キャストが実際に機能することを意図している場合にのみ成功します。その場合、正しいオブジェクトへのポインターが返され、ポインター値が次のように調整されます。必要です(多重継承を伴う状況では、ポインター値が変わる可能性があります)。失敗した場合dynamic_cast<T*>(x)、つまり、x実際には型(または派生)のオブジェクトへのポインタではない場合、 Tnullを返します。

次に、質問に移ります。ポインタキャストは、ポインタにのみ影響し、オブジェクトには影響しません。つまり、ポイントされたオブジェクトのタイプは変更されません。質問1のシナリオでは、派生型へのポインターを作成し、それを基本型へのポインターに割り当てます。派生型は-基本型であるため、この変換は暗黙的ですが、

A* paUpcast = static_cast<A*>(pb);

2番目のシナリオでは、にキャストpaUpcastしますB*。を使用して作品に変換して戻すpaUpcast変換の結果であるため、を使用してポインタまたは参照をキャストする場合、暗黙の変換の効果を逆にすることができます。暗黙的な変換の効果を元に戻す以外の方法でクラス階層をナビゲートする場合は、を使用する必要があります。この状況でも使用できたはずですが、ある程度のコストがかかります。B*A*B*static_cast<T*>(x)static_cast<>()dynamic_cast<>()dynamic_cast<>()dynamic_cast<>()

ここで、3番目のシナリオでは、実際にはオブジェクトではないオブジェクトをpa指しています。コンパイラはにキャストすることを許可しますが、あなたはコンパイラに嘘をついています: aをanに暗黙的に変換して使用した結果ではありません(またはこの場合は同等ですが、Cスタイルのキャストは必ずしもsと同等ではありません)結果として未定義の動作が発生します。コンパイラーは、実行したいと思うことをすべて実行します。およびオブジェクトのレイアウトは類似しているため、の仮想関数を呼び出すことになりますが、これは多くの可能な結果の1つにすぎず、何が起こるかを保証するものではありません。使用した場合ABpaB*paB*A*static_cast<B*>(pa)(B*)pastatic_cast<>()ABAdyanmic_cast<B*>(pa)結果はnullポインターになり、toからのキャストが機能しないことを示しpaますB*

于 2012-10-18T07:01:11.027 に答える