虚関数の背後にある秘密

27643 ワード

虚関数の背後にあるもっと面白い資料を探ってみましょう.
プログラム20.
#include <iostream>

using namespace std;



class Base {

public:

    virtual void fun() {

        cout << "Base::fun" << endl;

    }

    void show() {

        fun();

    }

};



class Drive : public Base {

public:

    virtual void fun() {

        cout << "Drive::fun" << endl;

    }

};



int main() {

    Drive d;

    d.show();



    return 0;

}

プログラムの出力は次のとおりです.
Drive::fun
このプログラムは、ベースクラスの関数が派生クラスの虚関数をどのように呼び出すかを明確に示す.この技術は、MFCおよび設計モード(例えば、Template Design Pattern)のような異なるフレームワークにおいて使用される.このプログラムを変更して、その動作を見ることができます.通常のメンバー関数ではなく、ベースクラスのコンストラクション関数で虚関数を呼び出します.
プログラム21.
#include <iostream>

using namespace std;



class Base {

public:

    Base() {

        fun();

    }

    virtual void fun() {

        cout << "Base::fun" << endl;

    }

};



class Drive : public Base {

public:

    virtual void fun() {

        cout << "Drive::fun" << endl;

    }

};



int main() {

    Drive d;



    return 0;

}

プログラムの出力は次のとおりです.
ase::fun
このプログラムは、ベースクラスの構造関数で派生クラスの虚関数を呼び出すことができないことを示しています.では、マントルの下で何をしたのか見てみましょう.これらのコンストラクション関数のポインタ値を印刷します.便宜上、クラスの他の関数を削除しました.
プログラム22.
#include <iostream>

using namespace std;



class Base {

public:

    Base() {

        cout << "In Base" << endl;

        cout << "This Pointer = " << (int*)this << endl;

        cout << endl;

    }

virtual void f() { cout << "Base::f" << endl; }

};



class Drive : public Base {

public:

    Drive() {

        cout << "In Drive" << endl;

        cout << "This Pointer = " << (int*)this << endl;

        cout << endl;

    }

virtual void f() { cout << "Drive::f" << endl; }

};



int main() {

    Drive d;

    cout << "In Main" << endl;

    cout << (int*)&d << endl;



    return 0;

}

プログラムの出力は次のとおりです.
In Base

This Pointer = 0012FF7C



In Drive

This Pointer = 0012FF7C



In Main

0012FF7C

これは、メモリ位置全体に1つのオブジェクトしか存在しないことを示します.では、このポインタが指す値、すなわち虚関数テーブルのポインタvptrが指す値、VTableのアドレスを印刷してみましょう.
プログラム23.
#include <iostream>

using namespace std;



class Base {

public:

    Base() {

        cout << "In Base" << endl;

        cout << "Virtual Pointer = " << (int*)this << endl;

        cout << "Address of Vtable = " << (int*)*(int*)this << endl;

        cout << "Value at Vtable = " << (int*)*(int*)*(int*)this << endl;

        cout << endl;

    }

    virtual void f1() { cout << "Base::f1" << endl; }

};



class Drive : public Base {

public:

    Drive() {

        cout << "In Drive" << endl;

        cout << "Virtual Pointer = " << (int*)this << endl;

        cout << "Address of Vtable = " << (int*)*(int*)this << endl;

        cout << "Value at Vtable = " << (int*)*(int*)*(int*)this << endl;

        cout << endl;

    }

    virtual void f1() { cout << "Drive::f2" << endl; }

};



int main() {

    Drive d;

    return 0;

}
プログラムの出力は次のとおりです.
In Base

Virtual Pointer = 0012FF7C

Address of Vtable = 0046C08C

Value at Vtable = 004010F0



In Drive

Virtual Pointer = 0012FF7C

Address of Vtable = 0046C07C

Value at Vtable = 00401217

このプログラムは、ベースクラスと派生クラスの異なる虚関数テーブルアドレスを例示する.この問題をよりよく理解するには、継承階層を深くし、Driveクラスに継承されたMostDriveクラスを追加し、そのオブジェクトを構築します.
プログラム24.
#include <iostream>

using namespace std;



class Base {

public:

    Base() {

        cout << "In Base" << endl;

        cout << "Virtual Pointer = " << (int*)this << endl;

        cout << "Address of Vtable = " << (int*)*(int*)this << endl;

        cout << "Value at Vtable = " << (int*)*(int*)*(int*)this << endl;

        cout << endl;

    }

    virtual void f1() { cout << "Base::f1" << endl; }

};



class Drive : public Base {

public:

    Drive() {

        cout << "In Drive" << endl;

        cout << "Virtual Pointer = " << (int*)this << endl;

        cout << "Address of Vtable = " << (int*)*(int*)this << endl;

        cout << "Value at Vtable = " << (int*)*(int*)*(int*)this << endl;

        cout << endl;

    }

    virtual void f1() { cout << "Drive::f2" << endl; }

};



class MostDrive : public Drive {

public:

    MostDrive() {

        cout << "In MostDrive" << endl;

        cout << "Virtual Pointer = " << (int*)this << endl;

        cout << "Address of Vtable = " << (int*)*(int*)this << endl;

        cout << "Value at Vtable = " << (int*)*(int*)*(int*)this << endl;

        cout << endl;

    }

    virtual void f1() { cout << "MostDrive::f2" << endl; }

};



int main() {

    MostDrive d;

    return 0;

}

プログラムの出力は次のとおりです.
In Base

Virtual Pointer = 0012FF7C

Address of Vtable = 0046C0A0

Value at Vtable = 004010F5



In Drive

Virtual Pointer = 0012FF7C

Address of Vtable = 0046C090

Value at Vtable = 00401221



In MostDrive

Virtual Pointer = 0012FF7C

Address of Vtable = 0046C080

Value at Vtable = 00401186
このプログラムは、各クラスの構造関数における虚関数テーブルポインタの初期化プロセスを例示する.このように、クラスコンストラクション関数ごとに虚関数テーブルのアドレスが異なり、main関数は継承チェーンの最下部の派生クラスを使用してオブジェクトを作成します.
虚関数テーブルの各クラスの構造関数の位置を見ることができます.虚関数テーブルの最初のエントリに関数ポインタを格納し、実行してみます.
プログラム25.
#include <iostream>

using namespace std;



typedef void(*Fun)();



class Base {

public:

    Base() {

        cout << "In Base" << endl;

        cout << "Virtual Pointer = " << (int*)this << endl;

        cout << "Address of Vtable = " << (int*)*(int*)this << endl;

        cout << "Value at Vtable = " << (int*)*(int*)*(int*)this << endl;



        Fun pFun = (Fun)*(int*)*(int*)this;

        pFun();

        cout << endl;

    }

    virtual void f1() { cout << "Base::f1" << endl; }

};



class Drive : public Base {

public:

    Drive() {

        cout << "In Drive" << endl;

        cout << "Virtual Pointer = " << (int*)this << endl;

        cout << "Address of Vtable = " << (int*)*(int*)this << endl;

        cout << "Value at Vtable = " << (int*)*(int*)*(int*)this << endl;

        

        Fun pFun = (Fun)*(int*)*(int*)this;

        pFun();

        cout << endl;

    }

    virtual void f1() { cout << "Drive::f1" << endl; }

};



class MostDrive : public Drive {

public:

    MostDrive() {

        cout << "In MostDrive" << endl;

        cout << "Virtual Pointer = " << (int*)this << endl;

        cout << "Address of Vtable = " << (int*)*(int*)this << endl;

        cout << "Value at Vtable = " << (int*)*(int*)*(int*)this << endl;

        

        Fun pFun = (Fun)*(int*)*(int*)this;

        pFun();

        cout << endl;

    }

    virtual void f1() { cout << "MostDrive::f1" << endl; }

};



int main() {

    MostDrive d;

    return 0;

}

プログラムの出力は次のとおりです.
In Base

Virtual Pointer = 0012FF7C

Address of Vtable = 0046C098

Value at Vtable = 004010F5

Base::f1



In Drive

Virtual Pointer = 0012FF7C

Address of Vtable = 0046C088

Value at Vtable = 00401221

Drive::f1



In MostDrive

Virtual Pointer = 0012FF7C

Address of Vtable = 0046C078

Value at Vtable = 00401186

MostDrive::f1

このプログラムは、各クラスの構造関数が、虚関数テーブルの各エントリをどのように自分の虚関数で満たすかを示す.したがって,BaseクラスはBaseクラスの虚関数アドレスを用いて自分の虚関数テーブルを埋め,Driveクラスのコンストラクション関数がそれを実行すると別の虚関数テーブルを作成し,自分の虚関数アドレスを格納する.
ベースクラスに複数の虚関数が含まれている場合、派生クラスは完全に書き換えることができません.
プログラム26.
#include <iostream>

using namespace std;



class Base {

public:

    Base() {

        cout << "In Base" << endl;

        cout << "Virtual Pointer = " << (int*)this << endl;

        cout << "Address of Vtable = " << (int*)*(int*)this << endl;

        cout << "Value at Vtable 1st entry = " << (int*)*((int*)*(int*)this+0) << endl;

        cout << "Value at Vtable 2nd entry = " << (int*)*((int*)*(int*)this+1) << endl;

        cout << "Value at Vtable 3rd entry = " << (int*)*((int*)*(int*)this+2) << endl;

cout << endl;

    }

    virtual void f1() { cout << "Base::f1" << endl; }

    virtual void f2() { cout << "Base::f2" << endl; }

};



class Drive : public Base {

public:

    Drive() {

        cout << "In Drive" << endl;

        cout << "Virtual Pointer = " << (int*)this << endl;

        cout << "Address of Vtable = " << (int*)*(int*)this << endl;

        cout << "Value at Vtable 1st entry = " << (int*)*((int*)*(int*)this+0) << endl;

        cout << "Value at Vtable 2nd entry = " << (int*)*((int*)*(int*)this+1) << endl;

        cout << "Value at Vtable 3rd entry = " << (int*)*((int*)*(int*)this+2) << endl;

        cout << endl;

    }

    virtual void f1() { cout << "Drive::f1" << endl; }

};



int main() {

    Drive d;

    return 0;

}

プログラムの出力は次のとおりです.
In Base

Virtual Pointer = 0012FF7C

Address of Vtable = 0046C0E0

Value at Vtable 1st entry = 004010F0

Value at Vtable 2nd entry = 00401145

Value at Vtable 3rd entry = 00000000



In Drive

Virtual Pointer = 0012FF7C

Address of Vtable = 0046C0C8

Value at Vtable 1st entry = 0040121C

Value at Vtable 2nd entry = 00401145

Value at Vtable 3rd entry = 00000000

このプログラムの出力は、ベースクラスの虚関数が派生クラスで書き換えられていないことを示し、その後、派生クラスの構造関数は虚関数のエントリに何もしていない.
では、今、純虚関数をこのゲームに招待して、その行為を見てみましょう.以下の手順を参照してください.
プログラム27.
#include <iostream>

using namespace std;



class Base {

public:

    Base() {

        cout << "In Base" << endl;

        cout << "Virtual Pointer = " << (int*)this << endl;

        cout << "Address of Vtable = " << (int*)*(int*)this << endl;

        cout << "Value at Vtable 1st entry = " << (int*)*((int*)*(int*)this+0) << endl;

        cout << "Value at Vtable 2nd entry = " << (int*)*((int*)*(int*)this+1) << endl;

        cout << endl;

    }

    virtual void f1() = 0;

    virtual void f2() = 0;

};



class Drive : public Base {

public:

    Drive() {

        cout << "In Drive" << endl;

        cout << "Virtual Pointer = " << (int*)this << endl;

        cout << "Address of Vtable = " << (int*)*(int*)this << endl;

        cout << "Value at Vtable 1st entry = " << (int*)*((int*)*(int*)this+0) << endl;

        cout << "Value at Vtable 2nd entry = " << (int*)*((int*)*(int*)this+1) << endl;

        cout << endl;

    }

    virtual void f1() { cout << "Drive::f1" << endl; }

    virtual void f2() { cout << "Drive::f2" << endl; }

};



int main() {

    Drive d;



    return 0;

}

debugモードとreleaseモードでは、プログラムの出力が異なります.次はdebugモードの出力です.
In Base

Virtual Pointer = 0012FF7C

Address of Vtable = 0046C0BC

Value at Vtable 1st entry = 00420CB0

Value at Vtable 2nd entry = 00420CB0



In Drive

Virtual Pointer = 0012FF7C

Address of Vtable = 0046C0A4

Value at Vtable 1st entry = 00401212

Value at Vtable 2nd entry = 0040128F

以下はreleaseモードの出力です.
In Base

Virtual Pointer = 0012FF80

Address of Vtable = 0042115C

Value at Vtable 1st entry = 0041245D

Value at Vtable 2nd entry = 0041245D



In Drive

Virtual Pointer = 0012FF80

Address of Vtable = 00421154

Value at Vtable 1st entry = 00401310

Value at Vtable 2nd entry = 00401380
この原理をよりよく理解するためには、プログラムを少し変更し、関数ポインタを使用して虚関数を呼び出すことを試みる必要があります.
プログラム28.
#include <iostream>

using namespace std;



typedef void(*Fun)();



class Base {

public:

    Base() {

        cout << "In Base" << endl;

        cout << "Virtual Pointer = " << (int*)this << endl;

        cout << "Address of Vtable = " << (int*)*(int*)this << endl;

        cout << "Value at Vtable 1st entry = " << (int*)*((int*)*(int*)this+0) << endl;

        cout << "Value at Vtable 2nd entry = " << (int*)*((int*)*(int*)this+1) << endl;

        

        //  

        Fun pFun = (Fun)*((int*)*(int*)this+0);

        pFun();



        cout << endl;

    }

    virtual void f1() = 0;

    virtual void f2() = 0;

};



class Drive : public Base {

public:

    Drive() {

        cout << "In Drive" << endl;

        cout << "Virtual Pointer = " << (int*)this << endl;

        cout << "Address of Vtable = " << (int*)*(int*)this << endl;

        cout << "Value at Vtable 1st entry = " << (int*)*((int*)*(int*)this+0) << endl;

        cout << "Value at Vtable 2nd entry = " << (int*)*((int*)*(int*)this+1) << endl;

        cout << endl;

    }

    virtual void f1() { cout << "Drive::f1" << endl; }

    virtual void f2() { cout << "Drive::f2" << endl; }

};



int main() {

    Drive d;



    return 0;

}

現在、プログラムの動作はdebugモードとreleaseモードで依然として異なる.Debugモードでは、実行時にエラーが発生したダイアログボックスが表示されます.
また、[無視](Ignore)ボタンを押すと、次のダイアログボックスが表示されます.
releaseモードで実行すると、コンソールウィンドウにエラーメッセージのみが出力されます.
In Base

Virtual Pointer = 0012FF80

Address of Vtable = 0042115C

Value at Vtable 1st entry = 0041245D

Value at Vtable 2nd entry = 0041245D



runtime error R6025

- pure virtual function call

ではここのR 6025は何ですか?CMSGSに定義されていますHヘッダファイルでは,このヘッダファイルはすべてのC Run Time Libraryのすべてのエラー情報を定義する.
#define _RT_PUREVIRT_TXT   "R6025" EOL "- pure virtual function call" EOL
実際には、純粋な虚関数を定義すると、コンパイラは_という名前のpurecallのC Run Time Libraryの関数アドレス.この関数はPUREVIRTで定義する.Cの中で、その原型は以下の通りである.
void __cdecl _purecall(void); //  : 
プログラムでは、この関数を直接呼び出して同じ効果を得ることができます.次のプログラムを見てください.
プログラム29.
int main() {

    _purecall();    

    return 0;

}
このプログラムのdebugモードとreleaseモードでの出力は、前のプログラムと同じです.この問題をよりよく理解するために、継承チェーンをより深くし、Driveクラスからもう一つのクラスを継承して効果を見てみましょう.
プログラム30.
#include <iostream>

using namespace std;



class Base {

public:

    Base() {

        cout << "In Base" << endl;

        cout << "Virtual Pointer = " << (int*)this << endl;

        cout << "Address of Vtable = " << (int*)*(int*)this << endl;

        cout << "Value at Vtable 1st entry = " << (int*)*((int*)*(int*)this+0) << endl;

        cout << "Value at Vtable 2nd entry = " << (int*)*((int*)*(int*)this+1) << endl;

        cout << endl;

    }

    virtual void f1() = 0;

    virtual void f2() = 0;

};



class Drive : public Base {

public:

    Drive() {

        cout << "In Drive" << endl;

        cout << "Virtual Pointer = " << (int*)this << endl;

        cout << "Address of Vtable = " << (int*)*(int*)this << endl;

        cout << "Value at Vtable 1st entry = " << (int*)*((int*)*(int*)this+0) << endl;

        cout << "Value at Vtable 2nd entry = " << (int*)*((int*)*(int*)this+1) << endl;

        cout << endl;

    }

};



class MostDrive : public Drive {

public:

    MostDrive() {

        cout << "In MostDrive" << endl;

        cout << "Virtual Pointer = " << (int*)this << endl;

        cout << "Address of Vtable = " << (int*)*(int*)this << endl;

        cout << "Value at Vtable 1st entry = " << (int*)*((int*)*(int*)this+0) << endl;

        cout << "Value at Vtable 2nd entry = " << (int*)*((int*)*(int*)this+1) << endl;

        cout << endl;

    }

    virtual void f1() { cout << "MostDrive::f1" << endl; }

    virtual void f2() { cout << "MostDrive::f2" << endl; }

};



int main() {

    MostDrive d;



    return 0;

}

プログラムの出力は次のとおりです.
In Base

Virtual Pointer = 0012FF7C

Address of Vtable = 0046C0D8

Value at Vtable 1st entry = 00420F40

Value at Vtable 2nd entry = 00420F40



In Drive

Virtual Pointer = 0012FF7C

Address of Vtable = 0046C0C0

Value at Vtable 1st entry = 00420F40

Value at Vtable 2nd entry = 00420F40



In MostDrive

Virtual Pointer = 0012FF7C

Address of Vtable = 0046C0A8

Value at Vtable 1st entry = 00401186

Value at Vtable 2nd entry = 004010F5

このプログラムは,BaseクラスとDriveクラスがそれぞれの虚関数テーブルを同じ値で初期化することを示している.では,より深く継承され,最下位の派生クラスだけが純虚関数を書き換えた場合,この場合何が起こるのでしょうか.これはCOMプログラム設計の場合に発生したものである.インタフェースは純虚関数のみを持つクラスであり、一つのインタフェースは別のインタフェースから継承され、実装クラスだけがインタフェースの純虚関数を書き換える.これにより、各ベースクラスのコンストラクション関数は、独自の虚関数テーブルエントリを同じ値で初期化します.したがって,同じコードが繰り返されることを意味する.ATLの主な考え方はCOMコンポーネントをできるだけ小さくすることであるが,この動作によりインタフェースクラスの構築関数には不要なコードが多く含まれる.この問題を解決するために、ATLはマクロATLを導入した.NO_VTABLEはATLDEFで定義されています.H中:
#define ATL_NO_VTABLE __declspec(novtable)

__declspec(novtable)は、Microsoft C++拡張のクラス属性です.コンパイラは、仮想関数テーブルポインタと仮想関数テーブルを初期化するコードを生成しません.これにより、生成コードのサイズが減少します.では、私たちのコードを修正して、この属性が私たちのために何ができるのかをよりよく理解することができます.プログラム31.
#include <iostream>

using namespace std;



class Base {

public:

    Base() {

        cout << "In Base" << endl;

        cout << "Virtual Pointer = " << (int*)this << endl;

        cout << "Address of Vtable = " << (int*)*(int*)this << endl;

        cout << "Value at Vtable 1st entry = " << (int*)*((int*)*(int*)this+0) << endl;

        cout << "Value at Vtable 2nd entry = " << (int*)*((int*)*(int*)this+1) << endl;

        cout << endl;

    }

    virtual void f1() = 0;

    virtual void f2() = 0;

};



class Drive : public Base {

public:

    Drive() {

        cout << "In Drive" << endl;

        cout << "Virtual Pointer = " << (int*)this << endl;

        cout << "Address of Vtable = " << (int*)*(int*)this << endl;

        cout << "Value at Vtable 1st entry = " << (int*)*((int*)*(int*)this+0) << endl;

        cout << "Value at Vtable 2nd entry = " << (int*)*((int*)*(int*)this+1) << endl;

        cout << endl;

    }

};



class __declspec(novtable) MostDrive : public Drive {

public:

    MostDrive() {

        cout << "In MostDrive" << endl;

        cout << "Virtual Pointer = " << (int*)this << endl;

        cout << "Address of Vtable = " << (int*)*(int*)this << endl;

        cout << "Value at Vtable 1st entry = " << (int*)*((int*)*(int*)this+0) << endl;

        cout << "Value at Vtable 2nd entry = " << (int*)*((int*)*(int*)this+1) << endl;

        cout << endl;

    }

    virtual void f1() { cout << "MostDrive::f1" << endl; }

    virtual void f2() { cout << "MostDrive::f2" << endl; }

};



int main() {

    MostDrive d;



    return 0;

}
プログラムの出力は次のとおりです.
In Base

Virtual Pointer = 0012FF7C

Address of Vtable = 0046C0CC

Value at Vtable 1st entry = 00420E60

Value at Vtable 2nd entry = 00420E60



In Drive

Virtual Pointer = 0012FF7C

Address of Vtable = 0046C0B4

Value at Vtable 1st entry = 00420E60

Value at Vtable 2nd entry = 00420E60



In MostDrive

Virtual Pointer = 0012FF7C

Address of Vtable = 0046C0B4

Value at Vtable 1st entry = 00420E60

Value at Vtable 2nd entry = 00420E60
このプログラムには、DriveクラスとMostDriveクラスは同じ虚函数表アドレスを持っているが、Baseクラスは異なるという別の結果がある.実は、これはベースクラスに使用されていないためです.declspec(novtable)プロパティのためです.次に、継承されたDriveクラスについても同じ属性、すなわち__を使用します.declspec(novtable).
プログラム32.
#include <iostream>

using namespace std;



class Base {

public:

    Base() {

        cout << "In Base" << endl;

        cout << "Virtual Pointer = " << (int*)this << endl;

        cout << "Address of Vtable = " << (int*)*(int*)this << endl;

        cout << "Value at Vtable 1st entry = " << (int*)*((int*)*(int*)this+0) << endl;

        cout << "Value at Vtable 2nd entry = " << (int*)*((int*)*(int*)this+1) << endl;

        cout << endl;

    }

    virtual void f1() = 0;

    virtual void f2() = 0;

};



class __declspec(novtable) Drive : public Base {

public:

    Drive() {

        cout << "In Drive" << endl;

        cout << "Virtual Pointer = " << (int*)this << endl;

        cout << "Address of Vtable = " << (int*)*(int*)this << endl;

        cout << "Value at Vtable 1st entry = " << (int*)*((int*)*(int*)this+0) << endl;

        cout << "Value at Vtable 2nd entry = " << (int*)*((int*)*(int*)this+1) << endl;

        cout << endl;

    }

};



class __declspec(novtable) MostDrive : public Drive {

public:

    MostDrive() {

        cout << "In MostDrive" << endl;

        cout << "Virtual Pointer = " << (int*)this << endl;

        cout << "Address of Vtable = " << (int*)*(int*)this << endl;

        cout << "Value at Vtable 1st entry = " << (int*)*((int*)*(int*)this+0) << endl;

        cout << "Value at Vtable 2nd entry = " << (int*)*((int*)*(int*)this+1) << endl;

        cout << endl;

    }

    virtual void f1() { cout << "MostDrive::f1" << endl; }

    virtual void f2() { cout << "MostDrive::f2" << endl; }

};



int main() {

    MostDrive d;



    return 0;

}

  
現在、プログラムの出力は:
In Base

Virtual Pointer = 0012FF7C

Address of Vtable = 0046C0C0

Value at Vtable 1st entry = 00420E50

Value at Vtable 2nd entry = 00420E50



In Drive

Virtual Pointer = 0012FF7C

Address of Vtable = 0046C0C0

Value at Vtable 1st entry = 00420E50

Value at Vtable 2nd entry = 00420E50



In MostDrive

Virtual Pointer = 0012FF7C

Address of Vtable = 0046C0C0

Value at Vtable 1st entry = 00420E50

Value at Vtable 2nd entry = 00420E50
MSDNで、対_declspec(novtable)の解釈は、純粋な虚クラスで使用すべきである.では、この意味をよりよく理解するために、もう一つの実験をしましょう.
プログラム33.
#include <iostream>

using namespace std;



class Base {

public:

    Base() {

        cout << "In Base" << endl;

        cout << "Virtual Pointer = " << (int*)this << endl;

        cout << "Address of Vtable = " << (int*)*(int*)this << endl;

        cout << "Value at Vtable 1st entry = " << (int*)*((int*)*(int*)this+0) << endl;

        cout << "Value at Vtable 2nd entry = " << (int*)*((int*)*(int*)this+1) << endl;

        cout << endl;

    }

    virtual void f1() = 0;

    virtual void f2() = 0;

};



class __declspec(novtable) Drive : public Base {

public:

    Drive() {

        cout << "In Drive" << endl;

        cout << "Virtual Pointer = " << (int*)this << endl;

        cout << "Address of Vtable = " << (int*)*(int*)this << endl;

        cout << "Value at Vtable 1st entry = " << (int*)*((int*)*(int*)this+0) << endl;

        cout << "Value at Vtable 2nd entry = " << (int*)*((int*)*(int*)this+1) << endl;

        cout << endl;

    }

};



class __declspec(novtable) MostDrive : public Drive {

public:

    MostDrive() {

        cout << "In MostDrive" << endl;

        cout << "Virtual Pointer = " << (int*)this << endl;

        cout << "Address of Vtable = " << (int*)*(int*)this << endl;

        cout << "Value at Vtable 1st entry = " << (int*)*((int*)*(int*)this+0) << endl;

        cout << "Value at Vtable 2nd entry = " << (int*)*((int*)*(int*)this+1) << endl;

        cout << endl;



        //  

        typedef void (*Fun)();

        Fun pFun = (Fun)*((int*)*(int*)this+0);

        pFun();



    }

    virtual void f1() { cout << "MostDrive::f1" << endl; }

    virtual void f2() { cout << "MostDrive::f2" << endl; }

};



int main() {

    MostDrive d;



    return 0;

}
プログラムに追加された新しいものは次のとおりです.
//  

typedef void (*Fun)();

Fun pFun = (Fun)*((int*)*(int*)this+0);

pFun();

そして、このアプリケーションを実行すると、前のプログラムと同じ問題、すなわち虚関数を呼び出そうとしたエラーに直面します.これは、虚関数テーブルが初期化されていないことを意味します.MostDriveクラスは抽象的なクラスではないので、クラスから削除する必要があります.declspec(novtable).
プログラム34.
#include <iostream>

using namespace std;



class Base {

public:

    virtual void f1() = 0;

    virtual void f2() = 0;

};



class __declspec(novtable) Drive : public Base {

};



class MostDrive : public Drive {

public:

    MostDrive() {



        //  

        typedef void (*Fun)();

        Fun pFun = (Fun)*((int*)*(int*)this+0);

        pFun();



    }

    virtual void f1() { cout << "MostDrive::f1" << endl; }

    virtual void f2() { cout << "MostDrive::f2" << endl; }

};



int main() {

    MostDrive d;



    return 0;

}
現在、プログラムは正常に動作します.出力は次のとおりです.
MostDrive::f1
このプロパティは、必ずしもATLのクラスにのみ使用されるわけではありません.オブジェクトを作成できないクラスで使用できます.同様に、必ずしもATLクラスに使用する必要はありません.つまり、ATLクラスから省略することができますが、これは、より多くのコードが生成されることを意味します.
原文:http://www.vckbase.com/document/viewdoc/?id=1351