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(); //没错
c.Test2();//没错
B b;
b.Test3(); //出错
}
每个类A、B、C都可以产生自己的虚函数表,我们来看看类C的构造函数里虚函数表的产生过程。
C::C()
{
....
call B::B();
//初始化虚函数表指针为C::vftable;
}
B::B()
{
.....
call A::A();
//如果没有用__declspec(novtable)的话,初始化虚函数指针为B::vtfable
}
A::A()
{
....
//如果没有用__declspec(novtable)的话,初始化虚函数指针为A::vtfable
}
特别值得注意的是这三个构造函数中涉及到的虚函数表指针都是同一个指针,都是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.