4

ここで何が起こっているのか理解できませんでした。非常に奇妙だと思いました。その理由を理解した後、答えを共有することは誰かの時間にとって価値があると思いました。

したがって、次の単純なコードが与えられます。

    #include <iostream>
using namespace std;

class Shape {
public:
    int* a;
    Shape(){
        cout<<"Default Shape constructor"<<endl;
        a = new int(8); // default
    }
    Shape(int n){
        a = new int(n);
          cout<<"Shape(n) constructor"<<endl;
    }
    // copy constructor
    Shape(const Shape& s){
        cout<<"Shape copy constructor"<<endl;
        a = new int(*(s.a));
    }
    Shape& operator=(const Shape& s){
        cout<<"Shape operator="<<endl;

        if (&s == (this))
            return (*this);
//      this.clear();
        a = new int(*(s.a));

        return (*this);
    }


      virtual void draw(){
             cout<<"Print Shape the number is "<<*a<<endl;
      };
      virtual ~Shape(){
          delete a;
          cout<<"Shape distructor"<<endl;
      }
};

class Circle : public Shape {
public:
    int b;
  Circle() {
      cout<<"Default Circle constructor"<<endl;
      b=0;
  }
  virtual void draw() {
      cout<<"Printing Circle. The number is "<<b<<endl;
  }
   ~Circle(){
      cout<<"Circle distructor"<<endl;
    }
};

次の 2 つのテストで 2 つの異なる答えが得られるのはなぜですか。

static void test1(){
    Shape shape = Circle() ;
    shape.draw();
}

static void test2(){
    Shape* shape = new Circle() ;
    shape->draw();
            delete shape;
}

ま、仮想の仕組みはついつい知ってしまったので、どちらのテストでも同じ結果が得られると思いました(Circleを印刷)。これはtest2で発生することですが、 test1ではそうではありません。

その理由を理解するために、バックグラウンドで実際に何が起こっているかを書きました。

Test1: 1. プログラムは「Circle()」という行を実行します。1.1 Shape のデフォルト コンストラクタが呼び出されます (Circle は Shape から派生しているため)。1.2 Circle のデフォルト コンストラクタが呼び出されます。

  1. プログラムはアクション「Shape shape =」を実行します。これは実際に Shape のコピー コンストラクターを呼び出します。*ここで、コピー コンストラクターは Circle の非表示フィールドである _vptr をコピーしないことに注意してください。a の値をコピーして (*this) を返すだけです。これが Circle を印刷しない本当の理由です。

ここで、別の質問があります。test1 を実行すると、次の出力が得 られました。

コピー コンストラクターのシグネチャがShape(const Shape& s) の場合、この出力によると、シェイプを実際にShapeとして作成する前に、コピー コンストラクターの呼び出しがあります。これはどのように起こりますか?

Test2: 1. クラス Circle の新しいインスタンスがヒープ上に構築されています。(行new Circleが実行されます) 2. ヒープ上のメモリ内のそのアドレスへのポインターが返され、ポインターの形状に配置されます。このアドレスの最初の 4 バイトには、Circle の仮想テーブルへのポインターがあります。これが、test1 が test2 と異なる理由です。

テストの違いは、test1 がスタック上に Circle を構築し、test2 がヒープ上に Circle を構築するという事実とは何の関係もないことを理解することが重要です。いや、実は関係あるんです。しかし、本当の理由は、コピー コンストラクターが _vptr をコピーしないことです。

4

4 に答える 4

8

これは、基本型に (非ポリモーフィックに) コピーすることによってクラスを「スライスする」と呼ばれます

バックグラウンドについては、Thinking in C++を参照してください。

于 2011-04-11T13:21:20.733 に答える
6
Shape shape = Circle();

ここには代入がないため、代入演算子への呼び出しはありません。ここ=で使用されるのは初期化です。一時Circleオブジェクトが作成されCircle()、そのShape一時オブジェクトの一部が にコピー構築されshapeます。その後、一時オブジェクトは不要になるため破棄されます。

Shape* shape = new Circle();

Circleオブジェクトは (ヒープ上に) 動的に作成され、そのオブジェクトへのポインターが返されます。ポインターは、このオブジェクトの一部をshape指します。「vptr がコピーされない」というのは違いの原因ではなく、結果です。2 つのまったく異なることを行う 2 つのテストを作成したため、まったく異なる結果が得られます。「異なる vptr」は、異なる結果の 1 つにすぎません。ShapeCircle

C++ でプログラミングする場合、「vptr」などの低レベルの実装の詳細や関連事項について心配する必要はほとんどありません。言語レベルでコードについて推論することが可能であるべきであり、パフォーマンスを調査し、最も醜い問題をデバッグするときは、実装の詳細にのみ関心を持ってください。

于 2011-04-11T13:21:54.870 に答える
0

が構築さoperator=れる前に呼び出されると考える理由がわかりません-実際には決して呼び出されません。shapeoperator=

C++ 標準のどこにもvptrはありません。呼び出された仮想メンバーがあたかもaであり、a ではないかのようにShape shape振る舞う本当の理由は、実際には a ではなく、決してなかったからです。C++ 標準では、このようにする必要があります。 には のメンバーがなく、 のデータ メンバーに割り当てられたスペースがありません。また、データが存在しない場合に仮想関数を使用しようとするのはかなりおかしなことです。shapeShapeCircleshapeCircleShape shapeCircleCircle

Shape shape初期化方法に関係なく、 のインスタンスを作成しますShape

于 2011-04-11T13:21:26.663 に答える
0

他の人がコードの問題をすでに指摘しているように、両方のテスト関数で同じ結果が得られない理由については、仮想メカニズムがどのように機能するかをよりよく理解するために実験できることについて、他に言いたいことがあります。

すでにポインターを使用してランタイム ポリモーフィズムを実現しているので、ここで参照を試してみましょう。私の変更を参照してくださいtest1()

static void test1(){
    Circle circle;
    Shape & shape = circle;  //note &
    shape.draw();
}

static void test2(){
    Shape* shape = new Circle() ;
    shape->draw();
    delete shape;
}

これで、これらの関数は両方とも同じものを出力します。

要点は次のとおりです。C++ では、ランタイム ポリモーフィズムは、静的型が基本クラスであり、動的型がポイント/参照するオブジェクトであるポインター参照によってのみ実現されます。

もっと実験をしましょう:

  Circle circle;
  circle.draw();

  Shape & s1 = circle;
  s1.draw();

  Shape & s2 = s1;
  s2.draw();

  Shape & s3 = s2;
  s3.draw();

何をs2.draw()s3.draw()ますか?答えは: 彼らは同じことをするだろうしs1.draw()circle.draw()するだろう. つまり、それらすべてが を呼び出しCircle::draw()、誰も を呼び出しませんShape::draw()

于 2011-04-11T13:27:12.250 に答える