C++虚関数は深く信じてペンの試験問題を探求します
9813 ワード
:#include <iostream>using namespace std;
class no_virtual
{
public:
void fun1() const{}
int fun2() const { return a; }
private:
int a;
};
class one_virtual
{
public:
virtual void fun1() const{}
int fun2() const { return a; }
private:
int a;
};
class two_virtual
{
public:
virtual void fun1() const{}
virtual int fun2() const { return a; }
private:
int a;
};
void main(){
cout<<"no_virtual:"<<sizeof(no_virtual)<<endl;
cout<<"one_virtual:"<<sizeof(one_virtual)<<endl;
cout<<"two_virtual:"<<sizeof(two_virtual)<<endl;
}
:
no_virtual:4
one_virtual:8
two_virtual:8
Press any key to continue
1.sizeofの から、クラスに が まれていないメンバー のオブジェクトがどのように を び しているかがわかります. はthisポインタを っています.
では、どうしてこのポインタを っていますか.
メモリ を てみましょう.
class A
{
int a;
public:
op(int value){a=value;};
int read(){return a ;}
public
virtual int as(){return a*a;}
};
のクラスの では、インスタンスを すると、メモリ は のようになります( なるC++インプリメンテーションはわずかに なります).
+0000:メンバー int a
+0004:$A$vtableポインタ-->+0000:メンバー ポインタas
なぜop とread はクラスに れる がないのですか? ないから.
C++が のクラスをコンパイルすると、 のようなメンバー が されます( なるC++ はわずかに なります).
//op
int $A$op@i_(A* this, int value) {this-> a = value;}
//read
int $A$read@_(A* this) {return this-> a;}
//as
int $A$as@_(A* this) {return this-> a * this-> a;}
すなわち、 は、 がどのクラスに しているか(パラメータタイプさえある)を すため、コンパイラはコードをコンパイルするときに、クラスのメンバー を び すことができ、クラス が を する はありません.
2.クラスが を つ 、 バインドが であるため、クラスのオブジェクトは に するアドレス を する の4バイトポインタを とする.
だからone virtual:8
3. no_virtualの さは、そのメンバー aの さである.
1つの と2つの のクラスの さには いはありませんが、 にはそれらの さはno_です.virtualの さにvoidポインタの さを えると,1つ の がある ,コンパイラはこの にポインタ(VPT R)を することを する.one_virtualとtwo_virtualの には いはありません.これは,V P T Rがアドレスを するテーブルを し,すべての アドレスがこのテーブルに まれているため,ポインタが1つしか としないためである.
だからtwo virtual:8
に、コンパイラがVTR テーブルをどのように したかを てみましょう. class base
{
public:
void bfun(){}
virtual void vfun1(){}
virtual int vfun2(){return 0;}
private:
int a;
}
class derived : public base
{
public:
void dfun(){}
virtual void vfun1(){}
virtual int vfun3(){return 0;}// return 0; 。 virtual int vfun3()=0;
private:
int b;
}
の2つのクラスVTRが す テーブル(VTABLE)はそれぞれ の りである.
ベースクラス
——————
VPTR——> |&base::vfun1 |
——————
|&base::vfun2 |
——————
derivedクラス
———————
VPTR——> |&derived::vfun1 |
———————
|&base::vfun2 |
———————
|&derived::vfun3 |
———————
コンパイラは、 の に すように、 を むクラスを するたびに、または を むクラスからクラスを させるたびに、このクラスにVTABLEを します.この では、コンパイラは、このクラスまたはそのベースクラスでvirtualとして されたすべての のアドレスを します.この クラスでvirtualとして された が されていない 、コンパイラはベースクラスのこの アドレスを します.(derivedのVTABLEでは、vfun 2の がこの です.) に、コンパイラはこのクラスにVTRを します. を する 、 オブジェクトに して1つのVTRしかありません.VTRは、コンストラクション で する するVTABLEを すように されなければならない.
VTRが するVTABLEを すように されると、オブジェクトはそれ がどのようなタイプであるかを「 る」.しかし、この は、 が び される にのみ である.
なまとめは の りです.
1、 を むクラスからクラスが すると、コンパイラはそのクラスにVTABLEを します. テーブル・アイテムは、クラスの アドレスです.
2. クラスオブジェクトを するときは、そのベースクラスのコンストラクタを び してからVTRを し、 クラスのコンストラクション を に び す(バイナリの から ると、ベースクラスサブクラスとは きな であり、thisポインタの の4バイトには ヘッダポインタが されている.サブクラスのコンストラクタを する には、まずベースクラスコンストラクタを び し、thisポインタをパラメータとしてベースクラスのvptrをベースクラスコンストラクタに め み、 にサブクラスのコンストラクタに り、サブクラスのvptrを め み、 きするベースクラスに め まれたvptr.これでvptrの が しました.)
3、 バインドを する 、クラスオブジェクトを するのではなく、ポインタまたは を ず しなければならない.クラスオブジェクトの を するため, ベースクラスオブジェクトの があり,ポインタを することは,ポインタによって の クラスオブジェクトのVTRにアクセスし, クラス にアクセスした である.
VTRはオブジェクトの に することが く、コンパイラはVTRの を に することができ、VTABLEの を することができる.VTRは にVTABLEの アドレスを し、すべてのベースクラスとそのサブクラスの アドレス(サブクラス に された を く)がVTABLEに される は に じであり、 えば のbaseクラスとderivedクラスのVTABLEにおけるvfun 1とvfun 2のアドレスは に じ に される.コンパイラは、vfun 1がVTRにあり、vfun 2がVTR+1にあることを っているので、ベースクラスポインタで を び すと、コンパイラはまずポインタがオブジェクトを すタイプ (VTR)を し、 を び す.ベースクラスポインタpBaseがderivedオブジェクトを すように、pBase->vfun 2
() vfun 2のアドレスは、VTABLEにおいてインデックスが1の にあるため、コンパイラによってVTR+1の び しに される. に、pBase->vfun 3()はコンパイラによってVTR+2の び しに される.これがいわゆる バインドです.
び しのアセンブリコードを て, を める.void test(base* pBase)
{
pBase->vfun2();
}
int main(int argc, char* argv[])
{
derived td;
test(&td);
return 0;
}
derived td;コンパイルによって されたアセンブリコードは のとおりです.
mov DWORD PTR _td$[esp+24], OFFSET FLAT:??_7derived@@6B@ ; derived::`vftable'
コンパイラのコメントから かるように、このときPTR_td$[esp+24]に されているのがderivedクラスのVTABLEアドレスである.
test(&td);コンパイルによって されたアセンブリコードは のとおりです.
lea eax, DWORD PTR _td$[esp+24]
mov DWORD PTR __$EHRec$[esp+32], 0
push eax
call ?test@@YAXPAVbase@@@Z ; test
test を び すと、オブジェクトtdのアドレスを り、スタックを さえてtestを び す が します.
pBase->vfun2();コンパイルによって されたアセンブリコードは のとおりです.
mov ecx, DWORD PTR _pBase$[esp-4]
mov eax, DWORD PTR [ecx]
jmp DWORD PTR [eax+4]
まずスタックからpBaseポインタが すオブジェクトアドレスを り してecxに り て, にオブジェクトの のポインタ のアドレスをeaxに り て,このときeaxの がVTRの ,すなわちVTABLEのアドレスである. に が び され、vfun 2はVTABLEの2 の にあり、VTR+1に し、 ポインタは4バイト であるため、 の び しはコンパイラによってjmp DWORD PTR[eax+4]に される.pBase->vfun 1()を び す 、この はjmp DWORD PTR[eax]にコンパイルされるべきである. 3 , , 。 , , , VPTR 。
, :
#include <iostream>
using namespace std;
class base
{
public:
void bfun(){}
virtual void vfun1(){}
virtual int vfun2(){return 0;}
base(){
cout<<"base constructor"<<endl;
}
private:
int a;
};
class derived : public base
{
public:
void dfun(){}
virtual void vfun1(){}
int vfun2(){cout<<"fun2()"<<endl;return 0;}
virtual int vfun3(){return 0;}
derived(){
cout<<"derived constructor"<<endl;
}
private:
int b;
};
void test(base *pBase)
{
pBase->vfun2();
//pBase.vfun2();
}
int main(int argc, char* argv[])
{
derived td;
test(&td);
return 0;
}
:base constructor
derived constructor
fun2()
Press any key to continue
ポインタをオブジェクトに した :void test(base pBase)
{
//pBase->vfun2();
pBase.vfun2();
}
int main(int argc, char* argv[])
{
derived td;
test(td);
return 0;
}
:base constructor
derived constructorポインタを に した :void test(base &pBase)
{
//pBase->vfun2();
pBase.vfun2();
}
int main(int argc, char* argv[])
{
derived td;
test(td);
return 0;
}
:base constructor
derived constructor
derived vfun2()
Press any key to continue//
2つのプログラムを すると、ポインタを してマルチステートを できることがわかります(test()を び すと ベースクラスが されるので、 にはthisはbaseクラス( baseオブジェクト))です.またいくつかの を います:1. base b1=(base)new derived; //cannot convert from 'class derived *' to 'class base'
b1.vfun2();
c++ではnewから てきたものをポインタでしか できない: えばint a[]=new int[4]; いを げる
cannot convert from'int*'to'int[]',int*a=new int[4];いいですよ.
ベースクラスがサブクラスを すにはポインタしか できない がjavaとは なり、javaではbase b 1=(base)new derivedが できます.(javaはポインタを しています)base *b=new derived;// new , b=&d ( derived d;)
b->vfun2();// b->vfun3();'vfun3' : is not a member of 'base' java 。
これは できます:base constructor
derived constructor
fun2()3. derived td;
td.vfun2();
これは であるが、ベースクラスポインタがサブクラスを すわけではない.したがって、マルチステート は、ベースクラスポインタがサブクラスを し、 に のオブジェクトタイプを することである.( び しでオブジェクトを うと ベースクラスが されるのもマルチステートではありません).vtableの な については の で します.