C++のvirtual関数(コンパイラの観点から)

4488 ワード

いくつかの小さなプログラムを編んでテストして、考えを整理して、以下のようにまとめました.
(ゼロ)VTABLEメカニズム
クラスにvirtual関数がある場合、コンパイラはこのクラスのVTABLEを1つだけ確立します.
このVTABLEは配列のような感じで、その要素は「関数ポインタ」です.
次のコードは
 1 class A
 2 {
 3  public:
 4        virtual void f0() {cout<<"a0"<<endl};
 5 
 6        virtual void f1() {cout<<"a1"<<endl};
 7 
 8        virtual void f2() {cout<<"a2"<<endl};
 9 
10 };
11 
12  class B: public A
13 {
14  public:
15        virtual void f0() {cout<<"b0"<<endl};
16 
17        virtual void f1() {cout<<"b1"<<endl};
18 
19        virtual void f2() {cout<<"b2"<<endl};
20 
21 };

コンパイラはクラスAとクラスBにそれぞれVTABLEを作成します.
VTABLE_Aの0,1,2個の要素はそれぞれA::f 0(),A::f 1(),A::f 2()を指すポインタである.
VTABLE_Bの0,1,2個の要素はそれぞれB::f 0(),B::f 1(),B::f 2()を指すポインタである
実際のクラスオブジェクトごとにvptrフィールドが追加されます
(注:これも多くの筆経で言うsizeof(A)とsizeof(B)に4が多い問題の原因で、この4つのByteがvptrポインタに割り当てられた空間であり、これによって引き起こされたsizeof整列問題がよく聞かれる)
次のコードは次のとおりです.
A *a = new B;

a->f1();    //  ,         a->vptr[1]();  ,       VTABLE_B[1]     

このように見ると、以前はハードバックの詳細を死記していたことが多く、どこがoverrideなのか、どこがoverloadなのか、呼び出したのはいったいどの関数なのか、vptrとVTABLEの概念でよく理解されていました
(一)多層クラスにおけるvirtrual
下のコードを見てください.
class A
{
public:
    virtual void f(){cout<<"a"<<endl;}
    void g(){cout<<"ag"<<endl;}
};

class B: public A
{
public:
    void f(){cout<<"b"<<endl;}      // B::f()  virtual   !
};

class C: public B     // C    B, B      virtual

{
public:
    void f(){cout<<"c"<<endl;}        // C  f   virtual   !
    void g(){cout<<"cg"<<endl;}
};

void main()
{
 A *a = new C();
 a->f();  //      C::f(),    a->vptr[0]();
 a->g();  //       A::g(),  g A   virtual 

 delete a;
 a = new B();
 a->f();  // (          ,          )     B::f(),a->vptr[0]();
 delete a;
}

A::f()はvirtualですが、B::f()とC::f()はvirtualを明示的に宣言していません.
実際、C::f()は多態ですA::f()です.
簡単に言えば、コンパイラはAにVTABLEを構築し、Aの派生類ごとにVTABLEを構築する(孫世代のCでも)
一方、A,B,CクラスのVTABLEの要素の順序は同じである(この例では1つのf()しかなく、Aにvirtualのf 2,f 3があれば、BとCのVTABLEの対応する位置にもB,Cバージョンのf 2,f 3が格納される)
(二)virtual構造関数
基本的には、C++の多態は、すべて「虚」で構造関数を分析しなければならない(この1年間、C#が多く使われ、人が馬鹿になった.先日、電面の時、面接官が私と多態を話してくれたが、「虚析構」という場所は軽蔑された=.=)
 1 class A
 2 {
 3 public:
 4     A() { cout<<"A()"<<endl;ptra_ = new char[10];}
 5     virtual ~A() { cout<<"~A()"<<endl; delete[] ptra_;}        //   :     “ ”  ,main         A(),B(),~A()
 6 
 7                             // “ ”   ,main      A(),B(),~B(),~A()
 8 
 9 private:
10     char * ptra_;
11 };
12 
13 class B: public A
14 {
15 public:
16     B() { cout<<"B()"<<endl;ptrb_ = new char[20];}
17     ~B() { cout<<"~B()"<<endl; delete[] ptrb_;}
18 private:
19     char * ptrb_;
20 };
21 
22 void main()
23 {
24  A * a = new B;
25     delete a;
26 }

このように、C++の中で多態を使って、あなたのプログラムがとても土っぽくて、newが空間を積み上げたことがない限り、さもなくば“虚析構造”は必ず使います.
また、コンストラクション関数は虚しくできません.
(三)private虚関数
  
class A
{
public:
    void foo() { bar();} //          this->vptr[0],      ,  “this”    
private:
    virtual void bar() {cout<<"a"<<endl;}
};

class B: public A
{
private:
    virtual void bar() { cout<<"b"<<endl;}
};

void main()
{
 A *a = new B();
 a->foo();     //a    B,  a->vptr[0]    VTABLE_B[0]     , B::bar()
}

(四)コンストラクション関数とコンストラクション関数でvirtual関数を呼び出す
  
class A
{
public:
    A() { cout<<"A()"<<endl;foo();}        //    ,      A::foo()   !
    ~A() { cout<<"~A()"<<endl;foo();}       //   
    virtual void foo(){cout<<"a"<<endl;}
};

class B: public A
{
public:
    virtual void foo(){cout<<"b"<<endl;}
};

class C: public B
{
public:
 virtual void foo(){cout<<"c"<<endl;}
};

void main()
{
 A *a = new B;
    delete a;
 B *b = new C;
 delete b;
 cout<<sizeof(A)<<endl;
 cout<<sizeof(B)<<endl;
 // 2   、2       a::foo()
 //      ,           ,B vptr    ;         ,vptr    ,      B::vptr  B VTABLE
}


変換元:http://www.cnblogs.com/fte99923/archive/2011/04/24/2026517.html