C ++コンパイラは、どの仮想関数の実装を呼び出すべきかをどのように知るのですか?
-
03-07-2019 - |
質問
http://www.cplusplus.com/docのポリモーフィズムの例を次に示します。 /tutorial/polymorphism.html (読みやすいように編集):
// abstract base class
#include <iostream>
using namespace std;
class Polygon {
protected:
int width;
int height;
public:
void set_values(int a, int b) { width = a; height = b; }
virtual int area(void) =0;
};
class Rectangle: public Polygon {
public:
int area(void) { return width * height; }
};
class Triangle: public Polygon {
public:
int area(void) { return width * height / 2; }
};
int main () {
Rectangle rect;
Triangle trgl;
Polygon * ppoly1 = ▭
Polygon * ppoly2 = &trgl;
ppoly1->set_values (4,5);
ppoly2->set_values (4,5);
cout << ppoly1->area() << endl; // outputs 20
cout << ppoly2->area() << endl; // outputs 10
return 0;
}
私の質問は、ppoly1がRectangleであり、ppoly2がTriangleであることをコンパイラがどのように認識して、正しいarea()関数を呼び出すことができるかということです。 &quot; Polygon * ppoly1 =&#9645;&quot;ラインと四角形が長方形であることを知っているが、それはすべての場合に機能しませんか?このようなことをしたらどうなりますか?
cout << ((Polygon *)0x12345678)->area() << endl;
メモリのランダムな領域へのアクセスが許可されていると仮定します。
これをテストしますが、現在使用中のコンピューターでは実行できません。
(明らかなものを見逃さないように...)
解決
各オブジェクト(少なくとも1つの仮想関数を持つクラスに属する)には、 vptr
と呼ばれるポインターがあります。実際のクラスの vtbl
を指します(仮想関数を持つ各クラスには、少なくとも1つがあり、複数の継承シナリオの場合は複数あります)。
vtbl
には、仮想関数ごとに1つのポインターの束が含まれています。そのため、実行時に、コードはオブジェクトの vptr
を使用して vtbl
を見つけ、そこから実際のオーバーライドされた関数のアドレスを見つけます。
特定の場合、 Polygon
、 Rectangle
、および Triangle
にはそれぞれ vtbl
があり、それぞれに1つの関連する area
メソッドを指すエントリ。 ppoly1
には、 Rectangle
の vtbl
と ppoly2
を指す vptr
があります。 Triangle
の vtbl
でも同様です。これがお役に立てば幸いです!
他のヒント
Chris Jester-Young がこの質問の基本的な答えを示しています。
ウィキペディアには、より詳細な処理があります。
このタイプのことの仕組み(および多重継承や仮想継承を含むすべての継承)の詳細を知りたい場合、Stan Lippmanの&quot; C ++オブジェクトモデルの内部&quot;。
バインディングの側面を無視して、実際にこれを決定するのはコンパイラではありません。
vtableおよびvpointerを介して、派生オブジェクトが実行時に実際に何であるかを評価するのは、C ++ランタイムです。
これがどのように行われるかについての良い説明のために、Scott Meyerの著書Effective C ++を強くお勧めします。
でも、派生クラスのメソッドのデフォルトパラメータがどのように無視され、基本クラスのデフォルトパラメータが引き続き使用されるかについて説明しています。それはバインディングです。
質問の2番目の部分に答えるには、そのアドレスにはおそらく適切な場所にvテーブルがなく、狂気が続きます。また、標準に従って未定義です。
cout << ((Polygon *)0x12345678)->area() << endl;
このコードは、発生を待っている災害です。コンパイラーはそれをすべてコンパイルしますが、実行時間に関しては、有効なv-tableを指すことはなく、運がよければプログラムはクラッシュします。
C ++では、このような古いCスタイルのキャストを使用しないでください。次のように dynamic_cast を使用する必要があります。
Polygon *obj = dynamic_cast<Polygon *>(0x12345678)->area();
ASSERT(obj != NULL);
cout << obj->area() << endl;
dynamic_castは、指定されたポインターが有効なPolygonオブジェクトでない場合にNULLを返すため、ASSERTにトラップされます。
仮想関数テーブル。つまり、両方のPolygon派生オブジェクトには、すべての(非静的)関数の実装への関数ポインターを含む仮想関数テーブルがあります。そして、三角形をインスタンス化すると、area()関数の仮想関数ポインターはTriangle :: area()関数を指します。 Rectangleをインスタンス化すると、area()関数はRectangle :: area()関数を指します。仮想関数ポインターはオブジェクトのデータとともにメモリに格納されるため、そのオブジェクトをポリゴンとして参照するたびに、そのオブジェクトの適切なarea()が使用されます。