我们可以注意到在用ATL编写COM组件时,在类前面都有一个宏ATL_NO_VTABLE,那么这个宏到底有什
么作用呢?这个宏的定义如下:
#define __declspec(novtable) ATL_NO_VTABLE;
下面这段是MSDN中对这个宏的描述:
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.
光看这段讲解可能不容易看懂,还是看个具体的例子:
- class __declspec(novtable) A
- {
- public:
- A(){}
- virtual Test(){}
- virtaul Test2(){}
- };
- class __declspec(novtable) B : public A
- {
- public:
- B(){}
- virtual Test(){}
- virtual Test3(){Test2();}
- };
- class C : public B
- {
- public:
- C(){}
- };
- void MyFunc()
- {
- C c;
- c.Test(); <span class="remark">//没错 </span>
- c.Test2();<span class="remark">//没错 </span>
- B b;
- b.Test3(); <span class="remark">//出错 </span>
- }
每个类A、B、C都可以产生自己的虚函数表,我们来看看类C的构造函数里虚函数表的产生过程。
- C::C()
- {
- ....
- call B::B();
- <span class="remark">//初始化虚函数表指针为C::vftable; </span>
- }
- B::B()
- {
- .....
- call A::A();
- <span class="remark">//如果没有用__declspec(novtable)的话,初始化虚函数指针为B::vtfable </span>
- }
- }
- A::A()
- {
- ....
- <span class="remark">//如果没有用__declspec(novtable)的话,初始化虚函数指针为A::vtfable </span>
- }
特别值得注意的是这三个构造函数中涉及到的虚函数表指针都是同一个指针,都是c的指
针!!!所以不管类A、类B有没有初始化这个指针,最后类C都会初始化它!!即使它们
初始了也没有用,最后指向的还是类C的虚函数表。
而对于类B来说,因为类A、类B都没有初始化虚函数表指针,所以在类的成员函数里调用
虚函数就会产生错误!!!
另外,如果我们把类A的__declspec(novtable)去掉,这样B b;这句话就不会出错,但是
调用b.Test()时会发现它调用的是A::Test(),而不是我们希望的B::Test();这是因为类B
没有自己初始化它的虚函数表指针为B::vftable,而是由类A初始化它的,并且初始它为
类A的虚函数表了,而在类A的虚函数表中,Test()函数当然是指向A::Test()了,所以虽
然编译没错,但实际执行的结果却是错的,这也是一个生动的例子。
结合到我们的ATL中,因为我们最后实际创建的类并不是我们在ATL中实现的类,而是从
从这个类继承的CComObject,CComAggObject,CComPolyObject,或CComContainedObject,
(参阅ATL接口映射宏详解)(这几个类都没有使用ATL_NO_VTABLE)。我们在上面也已经看到,
在基类中初始化虚函数表是没有用的,因为它会被派生类覆盖掉,所以我们可以对它使用
ATL_NO_VTABLE, 免得它产生虚函数表!! 因为ATL是通过多重继承来实现COM组件的,
继承层次中的每个类都有自己的虚函数表,所以在继承层次很深的情况下,虚函数表会变
得非常宏大,如果用ATL_NO_VTABLE宏来阻止生成虚函数表,就会有限的减少组件的长度。
事实上ATL中预先实现的接口类基本上都使用了这个宏。只要有继承树结构的最下层的几个类
CComObject、CComAggObject、CComPolyObject或CComContainedObject能够正确生成
VTABLE就可以了。也正因为这个原因,这几个类都没有使用ATL_NO_VTABLE.