今天在看MFC的源代码时,发现基类CObject的定义是如下形式:
#ifdef _AFXDLL
class CObject
#else
class AFX_NOVTABLE CObject
#endif
_AFXDLL是什么东西呢,从网上搜索了一下,发现当新建工程时选择Use MFC in a Shared DLL,则在工程的预编译参数Preprocessor definitions中会自动加入_AFXDLL宏定义,而如果在新建工程时选择Use MFC in a Static Library则不会添加该宏定义,这时基类CObject的定义变为class AFX_NOVTABLE CObject,而AFX_NOVTABLE宏定义又有什么用途呢?继续追踪该宏的定义发现:
#ifndef AFX_NOVTABLE
#if _MSC_VER >= 1100 && !defined(_DEBUG)
#define AFX_NOVTABLE __declspec(novtable)
#else
#define AFX_NOVTABLE
#endif
#endif
_MSC_VER用于说明编译器的版本,如MS VC++ 6.0 _MSC_VER = 1200,MS VC++ 5.0 _MSC_VER = 1100,即当工程编译版本不是debug版本时,宏AFX_NOVTABLE相当于__declspec(novtable),__declspec(novtable)主要是为了减少release版本的最终目标文件的大小,总体上来说对CObject类定义时的一系列修饰是想在以Use MFC in a Static Library方式生成文件时尽量减小文件的大小。
(1)VTABLE(虚函数表)和VPTR(指向虚函数标的指针)的区别
编译器到底做了什么实现的虚函数的晚绑定呢?我们来探个究竟。
编译器对每个包含虚函数的类创建一个表(称为VTABLE)。在VTABLE中,编译器放置特定类的虚函数地址。在每个带有虚函数的类中,编译器秘密地置一指针,称为vpointer(缩写为VPTR),指向这个对象的VTABLE。通过基类指针做虚函数调用时(也就是做多态调用时),编译器静态地插入取得这个VPTR,并在VTABLE表中查找函数地址的代码,这样就能调用正确的函数使晚捆绑发生。为每个类设置VTABLE、初始化VPTR、为虚函数调用插入代码,所有这些都是自动发生的,所以我们不必担心这些。利用虚函数,这个对象的合适的函数就能被调用,哪怕在编译器还不知道这个对象的特定类型的情况下。(《C++编程思想》)
(2)微软的描述
This form of _declspec can be applied to any class declaration, but should only be applied to pure interface classes, that is classes that will never be instantiated on their own. The _declspec stops the compiler from generating code to initialize the vfptr in the constructor(s) and destructor of the class. In many cases, this removes the only references to the vtable that are associated with the class and, thus, the linker will remove it. Using this form of _declspec can result in a significant reduction in code size.
(纯虚类和减少文件大小是这短定义的精华)
(3)静态联编 和 动态联编 的区别
静态联编是指联编工作出现在编译连接阶段,这种联编又称早期联编,因为这种联编过程是在程序开始运行之前完成的。在编译时所进行的这种联编又称静态束定。在编译时就解决了程序中的操作调用与执行该操作代码间的关系,确定这种关系又称为束定,在编译时束定又称静态束定。
编译程序在编译阶段并不能确切知道将要调用的函数,只有在程序执行时才能确定将要调用的函数,为此要确切知道该调用的函数,要求联编工作要在程序运行时进行,这种在程序运行时进行联编工作被称为动态联编,或称动态束定,又叫晚期联编。
希望通过以上分析,能够比较好的理解关键字 “_declspec(novtable)”的用法。纯虚类是因为class不产生虚表,只能在派生类中实现虚表;C++中没有提供类似interface这样的关键字来定义接口,但是Mircrosoft c++中提供了__declspec(novtable)来修饰一个类,来表示该类没有虚函数表,也就是虚函数都(应该)是纯虚的。减小SIZE 是因为虚表是需要空间的,在不需要虚表的情况下,把虚表去掉就可以减少SIZE了。
程序0.
#include
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)。现在你可以修改一下这个程序来看看它的行为,我将要在基类的构造函数中调用虚函数,而不是普通的成员函数。
程序1.
#include
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;
}
程序的输出为:
Base::fun
这个程序表明,我们不能在基类的构造函数中调用派生类的虚函数。好了,那就让我们来看看着布幔之下到底做了什么。我将会把这些构造函数之中的指针值打印出来,为了简便起见,我移除了类中其它的函数。
程序2.
#include
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
这就表示,整个内存位置中,只有一个对象的存在。那么就让我们把这个指针指向的值打印出来,也就是虚函数表的指针vptr指向的值,VTable的地址。
程序3.
#include
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::f1" << 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类,然后构建一个它的对象。
程序4.
#include
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::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;
cout << endl;
}
virtual void f1() { cout << "MostDrive::f1" << 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函数则使用了继承链中的最底部的派生类来创建对象。
现在你可以看看虚函数表中各个类的构造函数的位置了,你可以将虚函数表中的第一个入口存入一个函数指针,并尝试运行它。
程序5.
#include
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类的构造函数执行它的时候会创建另外一个虚函数表,并存储自己的虚函数地址。
现在,你会发现在基类中含有多个虚函数的情况下,派生类并不能完全重写它们。
程序6.
#include
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
这个程序的输出表明基类的虚函数在派生类中并未被重写,然后,派生类的构造函数没有对虚函数的入口做任何的事情。
那么现在,让我邀请纯虚函数来加入这一游戏,再来看看它的行为吧。请看以下的程序:
程序7.
#include
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
为了更好地弄懂这一原理,我们需要对程序作少许的改动,并尝试使用函数指针来调用虚函数。
程序8.
#include
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模式下,它会显示一个运行时错误的对话框:
并且,当你按下“忽略”按钮之后,它会显示下面的对话框:
而在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
那么这里的R6025是什么?它定义于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); // 译注:原文此处无分号
我们可以在程序中直接调用这个函数来达到相同的效果,请看下面这个小程序:
程序9.
int main() {
_purecall();
return 0;
}
这个程序在debug模式和release模式下的输出和前一个是一样的。为了更好的理解这个问题,让我们把继承链弄得更深一些,并且从Drive类中再继承一个类来看看效果吧。
程序10.
#include
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++扩展的类属性。它会使编译器不产生初始化虚函数表指针和虚函数表的代码,这样一来就减少了生成代码的尺寸。
那么,我们来修改一下我们的代码,这样就能更好的明白这一属性究竟能为我们做什么了。
程序11.
#include
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类却不同。事实上,这就是由于我们没有对Base类使用__declspec(novtable)属性的缘故。现在,我们来对继承的Drive类也使用相同的属性,也就是__declspec(novtable)。
程序12.
#include
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)的解释是:它应该使用在纯虚类中。那么,让我们再做一个实验来更好地弄懂这一含义吧。
程序13.
#include
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)。
程序14.
#include
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类中省略,不过这就意味着这样会产生更多的代码。
注:由于我看到的文章也是转载的,没有标注原地址,所以此处无法标注