题目:ATL Under the Hood - Part 2
原文链接:http://www.codeproject.com/Articles/1846/ATL-Under-the-Hood-Part-2
介绍
这个系列我们将讨论ATL的内部工作机制以及技术实现。这是第二部分。
现在,让我们揭开虚函数背后的那些有趣的东西。(为了与第一部分保持连续性,我延续前面的程序数)
下面我们看这段代码:
程序 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 和 像模板设计模式之类的设计模式等。现在,我们对程序稍作改动,来看看其行为。现在,我们在基类的构造函数而不是成员函数中调用虚函数。
程序 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; }
输出结果是:
Base::fun 这个程序显示,我们不可以在基类的构造函数中调用派生类的虚函数。为了看清楚内部原因,我们在两个构造函数中分别打印this指针(该类自身的指针)。为了简化,我们移除了类中其他的函数。
程序 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
结果表明:在内存中只有一个对象。现在我们来打印this指针的值,也就是虚指针的值,以及虚表的值(也就是虚指针值所指的对象的值)。
程序 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; }
The output of this program is
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 这个程序显示基类和派生类有着不同的虚表地址。为了更好地理解,让我们再添加一层的继承,然后构造一个该类的对象。
程序 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; }
The output of this program is
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 这个程序显示虚指针是在每个类的构造函数中初始化的。因此,虚表的地址在每个类的构造函数中是不同的,而主函数创建了继承链最下层的对象,并调用这个对象的虚表。 现在我们看看每个类的构造函数在虚表中放了什么。为此,我们要利用函数指针来存储虚表中的第一个元素,然后来执行它。
程序 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
这个程序显示每个类的构造函数利用自己的虚函数填充虚表。也就是说,基类用基类的虚函数地址填充虚表,当派生类构造函数执行时,他将创建另一个虚表然后将其自身的虚函数地址存入。
现在我们看看这种情况:基类有多个虚函数,而子类重写了其中的一部分。
程序 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; // try to execute first virtual function 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运行时库的错误消息。
#define _RT_PUREVIRT_TXT "R6025" EOL "- pure virtual function call" EOL
实际上,当我们定义一个纯虚函数时,编译器会将C运行时库的函数之一:_purecall放入虚表中。这个函数在PUREVIRT.C文件中定义,有如下的原型:
void __cdecl _purecall(void)
我们可以通过直接调用这个函数得到上述同样的结果。让我们看看这个非常袖珍的程序 :
程序 29
int main() { _purecall(); return 0; }
这个程序的输出不管在debug合适release模式下和前面的那个都完全一样。为了更好地理解其中的缘由,我们将继承链加深,再加一层继承,看看结果如何:
程序 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
这个程序结果显示,父类和子类都构造他们自己的虚表,并且用同样的值来初始化。如果继承链更加长,而且除了最底层的派生类外,其他派生类都没有重写任何的父类纯虚函数,这时会发生什么情况呢?这种情况其实在COM编程时就会遇到,因为COM编程中,接口就是只有纯虚函数的类,一个接口继承自另一个接口,他们都没有实现这些接口中的纯虚函数,直到实现类重写了这些接口中的纯虚函数。此时,每一个父类构造函数构造他们自己的虚表,并且放同样的值到其虚表入口,这意味着同样的代码在进行着大量的重复。
ATL主要的理念就是让COM组件尽可能的小,但是由于虚表的存在,接口类的构造函数含有大量不必要的代码。为解决这个问题,ATL引入一个宏ATL_NO_VTABLE
,定义在
ATLDEF.H
中:
#define ATL_NO_VTABLE __declspec(novtable)
__declspec(novtable) 是微软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类
程序 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() { // try call first virtual function 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类产生更多的代码量。
本部分完。