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の な については の で します.