0

重複の可能性:
C++ のスライシングの問題は何ですか?

ポリモーフィズムと継承の例として簡単なコードを用意しました

class A
{
public:
    int fieldInA;

    void virtual overloadedFunc()
    {
        printf("You are in A\n");
    }
};

class B : public A
{
public:
    int fieldInB;

    void overloadedFunc()
    {
        printf("You are in B\n");
    }
};

void DoSmth(A* a)
{
    a->overloadedFunc();
}

void DoSmthElse(A a)
{
    a.overloadedFunc();
}
int _tmain(int argc, _TCHAR* argv[])
{
    B *b1 = new B();
    B b2;
    //Breakpoint here
    DoSmth(b1);
    DoSmthElse(b2);
    scanf("%*s");
    return 0;
}

ブレークポイントで停止すると、b1 の _vfptr[0] と b2 の _vfptr[0] の値は同じです (SomeAddr(B::overloadedFunc(void)))。DoSmth() でパラメーターとして b1 を渡した後、ローカル変数 a の _vfptr[0] はまだ someAddr(B::overloadedFunc(void)) ですが、DoSmthElse の a の _vfptr[0] は someAddr(A::overloadedFunc(void) になります。 )))。これは、関数のオーバーロードの概念に対する私の誤解であると確信していますが、最初のケースで「あなたはBにいます」と2番目に「あなたはAにいます」を見た理由を理解できませんでした。A *b1 = new B(); と同じです。DoSmth(b1); // あなたは B にいます。なぜですか?

4

2 に答える 2

5

まず、用語を正しく理解する必要があります。関数をオーバーロードせず、それらをオーバーライドしました。

  • オーバーロードとは、同じ関数名に異なるタイプの引数があることを意味します。正しいオーバーロードを選択することは、コンパイル時の操作です。
  • オーバーライドとは、ポリモーフィック(C ++virtualの場合)関数を使用したクラス階層があり、呼び出されている関数を、より特殊なクラスのオブジェクトに適用可能な関数に置き換えることを意味します。元の意味を上書きします。正しいオーバーライドの選択は、仮想関数テーブルに似たものを使用するC++での実行時操作です。

用語は十分に混乱しており、事態をさらに悪化させるために、これらは相互作用することさえあります。コンパイル時に正しいオーバーロードが選択され、最終的に仮想関数が呼び出され、オーバーライドされる可能性があります。また、派生クラスをオーバーライドすると、基本クラスから継承されたオーバーロードが非表示になる場合があります。ただし、用語を正しく理解できない場合は、これはすべて意味がない可能性があります。

さて、実際の問題については、呼び出すときに、型のオブジェクトを受け取る関数に値でDoSmthElse()オブジェクトを渡します。これにより、オブジェクトのサブオブジェクトをコピーして、タイプのオブジェクトが作成されます。ただし、から派生しているため、すべてが表されるわけではありません。つまり、に表示されるオブジェクトは、オブジェクトのように動作するのではなく、オブジェクトのように動作します。結局のところ、それオブジェクトであり、!ではありません。このプロセスは通常、スライスと呼ばれます。オブジェクトを特別なものにした部分をスライスします。b2AAABBABADoSmthElse()BAABB

于 2012-11-21T20:36:21.543 に答える
1

ポリモーフィックな動作を取得するには、基本クラスのインスタンスではなく、基本クラスへのポインターまたは参照で仮想関数を呼び出す必要があります。この関数を呼び出すと

void DoSmthElse(A a)

のインスタンスを渡しましたB。これは値渡しであるため、引数は渡すインスタンスのスライスされたコピーです。B本質的に、これは、Bに共通するのすべてのプロパティAがこのコピーに保存され、に固有でありB、 に固有BでないのすべてのプロパティAが失われることを意味します。DoSmthElse()このため、関数が呼び出されるオブジェクトoverloadedFunc()は今では専ら type でAあり (もう type ではありませんB)、もちろんA::overloadedFunc()が呼び出されます。

引数が基本クラスへのポインター型である最初のケースでDoSmthは、期待どおりに多態的な動作が得られます。B*引数が関数に渡され、このポインターのコピーが作成され、型が になりA*ます。コピーは にキャストされていますが、A*指しているオブジェクトの型は のままですB。指されたオブジェクトは、Bその基本クラスへのポインターを介してアクセスされるという事実のタイプであるためB::overloadedFunc()、行が実行されたときに関数が実際に呼び出されることを保証します

a->overloadedFunc();

仮想関数overloadFunc()が class によってオーバーライドされるBため、が実行されます。クラスBが実装されていない場合は、基底クラスの仮想関数の独自のバージョン (つまり、クラスBがクラスの機能をオーバーライドするA) があり、代わりに基底クラスのバージョンが呼び出されます。

于 2012-11-21T20:02:29.183 に答える