0



まず、次のコードを見てください (このコードの shape は基本クラスで、line は派生クラスです)。

void drawshapes(shape sarray[],int size)
{
    for(int i=0;i< size; i++)
        sarray[i].draw();
}

main()
{
   line larray[10];
   larray[0]=line(p1,p2);//assuming that we have a point class
   larray[1]=line(p2,p3);
   ..........
   drawshapes(larray,10);
}


このプログラムをコンパイルすると、最初に shape の draw メソッドが呼び出され、その後プログラムが終了します。なぜ終了するのですか?基本クラスのポインターまたは参照なしでポリモーフィズムを実装できないのはなぜですか?これの技術的な理由は何ですか? オブジェクトの配列でポリモーフィズムを実装しようとしている場合、コンパイラは何をしますか? 例を挙げてわかりやすく説明してください。とても感謝しています。

4

4 に答える 4

5

まず、ポリモーフィズムと、値と参照のセマンティクスという 2 つの概念が混在しています。

実行時ポリモーフィズム

ポリモーフィズムにはさまざまな形があります。使用するランタイムに応じて、他のオプションを使用できます。

インタープリター言語 (Ruby、Python、Javascript など) では、「ダック タイピング」が可能です。オブジェクトにと呼ばれるメソッドしかない場合foo、それを呼び出すことができます。通常、これらの言語はガベージ コレクションを行うため、ポインターとオブジェクトの概念はあまり関係ありません。

C++ には異なる視点があります。ポリモーフィズムは許可されていますが、より厳密な方法です。共通の基本クラス (abstract の場合もあります) を強制すると、コンパイラはコードのセマンティクスをチェックできます。このようにして、コンパイラはfoo、意図したインターフェイスを実装するメソッドを本当に意味していることを保証し、 foos の混乱ではありません。

このポリモーフィズムは、関数 (関数へのポインター) の使用によって実現virtualされます。関数へのポインターは、実装によって異なる場合があります。の呼び出し元は、fooまず関数ポインタの値を調べてから、その関数にジャンプする必要があります。

これまでのところ、ポリモーフィズムについてです。

封じ込め

封じ込めのために: lineC++ でオブジェクトの配列を作成すると、これらのオブジェクトはメモリ内で互いに隣り合っています。それらは値によって含まれています。配列を関数に渡す場合、呼び出された関数は同じ型の配列のみを受け取ることができます。そうsizeof(shape)しないと、配列に を 1 ステップ実行すると、 a の途中になってしまいlineます。

これを修正するために、「参照によって」オブジェクトを含めることができます。C++ では、そのためにポインターを使用します。

コンパイル時のポリモーフィズム

しかし、多態的な関数を実現する別の方法があります: テンプレートです。使用しているオブジェクトのタイプを示すテンプレート引数を使用して関数を作成できます。drawshapes

template< typename tShape, size_t N > 
void drawshapes( tShape (&aShapes)[N] ) {
    for( tShape* shape=aShapes; shape != aShapes+N; ++shape ) {
        shape->draw();
    }
}

(注: これを単純化するための stl 関数がありますが、それは問題の範囲外です。

std::for_each( shapes, shapes+10, mem_fun_ref( &shape::draw ) );

)

于 2010-02-03T08:24:59.063 に答える
5

あなたは質問をして、別の理由で失敗するコード例を提供しています。あなたの質問の文言から:

ポリモーフィズムに参照/ポインタが必要なのはなぜですか?

struct base {
   virtual void f();
};
struct derived : public base {
   virtual void f();
};
void call1( base b ) {
   b.f(); // base::f
}
void call2( base &b ) {
   b.f(); // derived::f
}
int main() {
   derived d;
   call1(d);
   call2(d);
}

値渡しセマンティクスを使用する (またはベース コンテナーに派生要素を格納する) 場合、 typebaseの要素のtype のコピーを作成していますderived。これは、オブジェクトがあり、そのサブオブジェクトのみをスライス/カットするという事実に似ているため、スライスと呼ばれます。この例では、 は mainのオブジェクトからではなく、 type の一時オブジェクトで動作し、呼び出されます。derivedbasecall1dbasebase::f

メソッドではcall2、オブジェクトへの参照を渡していbaseます。コンパイラがcall2(d)in main を確認すると、baseサブオブジェクト inへの参照が作成dされ、関数に渡されます。この関数は、 型baseのオブジェクトを指す型 の参照に対して操作を実行しderived、 を呼び出しますderived::f。ポインターでも同じことが起こります。オブジェクトに を取得するbase *derived、オブジェクトはまだderivedです。

derivedポインターのコンテナーを受け取る関数にポインターのコンテナーを渡すことができないのはなぜbaseですか?

_明らかderivedbase、 のコンテナderived のコンテナですbase

いいえ。 のコンテナは のコンテナでderivedはありませんbase。それは型システムを壊します。型システムを破るオブジェクトのderivedコンテナーとしてのコンテナーを使用する最も単純な例を以下に示します。base

void f( std::vector<base*> & v )
{
   v.push_back( new base );
   v.push_back( new another_derived );
}
int main() {
   std::vector<derived*> v;
   f( v ); // error!!!
}

エラーでマークされた行が言語で許可されている場合、アプリケーションは型ではない要素をderived*コンテナーに挿入することができ、それは多くの問題を意味します...

しかし、問題は値型のコンテナに関するものでした...

値型のコンテナーがある場合、要素はコンテナーにコピーされます。derivedtypeのコンテナにtype の要素を挿入すると、オブジェクト内baseに type のサブオブジェクトのコピーが作成されます。それは上記と同じスライスです。それは言語の制限であることに加えて、正当な理由があります。オブジェクトのコンテナーがある場合、要素だけを保持するスペースがあります。より大きなオブジェクトを同じコンテナーに格納することはできません。そうしないと、コンパイラは各要素にどれだけのスペースを確保するかさえ知りません (後でさらに大きな型で拡張するとどうなるでしょうか?)。basederivedbasebase

他の言語では、これが実際に許可されているように見えるかもしれませんが (Java)、そうではありません。唯一の変更点は構文にあります。Java を使用しているときは、実際には C++String array[]と同等のものを書いています。string *array[]すべての非プリミティブ型は言語の参照であり*、構文に を追加しないという事実は、コンテナーが String のインスタンスを保持することを意味しません。コンテナーは String への参照を保持します。これは、c++ 参照よりも c++ ポインターに関連しています。 .

于 2010-02-03T08:38:05.373 に答える
3

形状の配列の代わりに線の配列を渡すことはできません。ポインターの配列を使用する必要があります。これは、関数が 2 番目のメンバーにアクセスしようとすると、*(sarray + sizeof(line)) ではなく *(sarray + sizeof(shape)) を実行するために発生します。これは、配列の 2 番目の要素にアクセスする正しい方法です。行の。

于 2010-02-03T08:06:10.053 に答える
1

次のようなものが必要です。

void drawshapes(shape *sarray[],int size)
{
   for(int i=0;i< size; i++)
      sarray[i]->draw();
}

main()
{
   shape *larray[10];
   larray[0] = new line(p1,p2);//assuming that we have a point class
   larray[1] = new line(p2,p3);
   ..........
   drawshapes(larray, 10);
   // clean-up memory
   ...
}
于 2010-02-03T08:23:50.683 に答える