虚関数の背後にある秘密
27643 ワード
虚関数の背後にあるもっと面白い資料を探ってみましょう.
プログラム20.
プログラム21.
プログラム22.
プログラム23.
プログラム24.
虚関数テーブルの各クラスの構造関数の位置を見ることができます.虚関数テーブルの最初のエントリに関数ポインタを格納し、実行してみます.
プログラム25.
ベースクラスに複数の虚関数が含まれている場合、派生クラスは完全に書き換えることができません.
プログラム26.
では、今、純虚関数をこのゲームに招待して、その行為を見てみましょう.以下の手順を参照してください.
プログラム27.
プログラム28.
また、[無視](Ignore)ボタンを押すと、次のダイアログボックスが表示されます.
releaseモードで実行すると、コンソールウィンドウにエラーメッセージのみが出力されます.
プログラム29.
プログラム30.
このプログラムは,BaseクラスとDriveクラスがそれぞれの虚関数テーブルを同じ値で初期化することを示している.では,より深く継承され,最下位の派生クラスだけが純虚関数を書き換えた場合,この場合何が起こるのでしょうか.これはCOMプログラム設計の場合に発生したものである.インタフェースは純虚関数のみを持つクラスであり、一つのインタフェースは別のインタフェースから継承され、実装クラスだけがインタフェースの純虚関数を書き換える.これにより、各ベースクラスのコンストラクション関数は、独自の虚関数テーブルエントリを同じ値で初期化します.したがって,同じコードが繰り返されることを意味する.ATLの主な考え方はCOMコンポーネントをできるだけ小さくすることであるが,この動作によりインタフェースクラスの構築関数には不要なコードが多く含まれる.この問題を解決するために、ATLはマクロATLを導入した.NO_VTABLEはATLDEFで定義されています.H中:
__declspec(novtable)は、Microsoft C++拡張のクラス属性です.コンパイラは、仮想関数テーブルポインタと仮想関数テーブルを初期化するコードを生成しません.これにより、生成コードのサイズが減少します.では、私たちのコードを修正して、この属性が私たちのために何ができるのかをよりよく理解することができます.プログラム31.
プログラム32.
プログラム33.
プログラム34.
原文:http://www.vckbase.com/document/viewdoc/?id=1351
プログラム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