[wtl学习]-[第二天]-关于ATL_NO_VTABLE一些困惑

注:本文是本人学习过程一些领悟和思路的记录。并不是什么授业解惑。若有不对之处,诚请指教。


第一个问题:ATL_NO_VTABLE是什么地干活
「转自」http://blog.csdn.net/oldmtn/article/details/7816962
       __declspec(novtable) 在C++中接口中广泛应用. 不容易看到它是因为在很多地方它都被定义成为了宏. 比如说ATL活动模板库中的ATL_NO_VTABLE, 其实就是__declspec(novtable).
       __declspec(novtable) 就是让类不要有虚函数表以及对虚函数表的初始化代码, 这样可以节省运行时间和空间. 但是这个类一定不允许生成实例, 因为没有虚函数表, 就无法对虚函数进行调用.「至于虚函数的一些概念可以看看上一篇文章C++中虚函数工作原理
       因此, __declspec(novtable)一般是应用于接口(其实就是包含纯虚函数的类), 因为接口包含的都是纯虚函数, 不可能生成实例. 我们把 __declspec(novtable)应用到接口类中, 这些接口类就不用包含虚函数表和初始化虚函数表的代码了. 它的派生类会自己包含自己的虚函数表和初始化代码.

了解了ATL_NO_VTABLE是什么后接下来当然想问,这个宏给我们带来了什么好处,为什么要这么用?

第二个问题: WHY ATL_NO_VTABLE

「摘自http://us.generation-nt.com/answer/question-about-atl-no-vtable-help-8068662.html
  When declspec(novtable) is used in a class declaration, it prevents the vtable pointer from being initialized in the class's constructor and destructor. The linker can thus eliminate the vtable and all the functions pointed to by the vtable, provided the most-derived class (usually CComObject, CComAggObject, or CComPolyObject) does not use declspec(novtable) and will thus initialize the vtable pointer correctly.
  The declspec(novtable) attribute should only be used with base classes that are not directly creatable. Further, it is unsafe to call virtual functions from the constructor of any object that uses declspec(novtable).
  You should move any such calls to the FinalConstruct method.
  [我的理解]当在申明一个类的时候使用了修饰符__declspec(novtable)时,实际上它的作用是告诉编译器不要为类创建虚函数表(VTABLE)以及对虚函数表的初使化工作(而这些工作都是编译阶段由编译器在类的构造函数中默默完成的)。因为没有虚函数表,自然也就不对虚函数进行调用。但是继承类中(没有__declspec(novtable)修饰,如COM组件中的 CComObject, CComAggObject, or CComPolyObject),重载了基类中的虚函数实现,编译器还是会为继承类创建一个虚函数表(VTABLE)保存该类所有的虚函数的地址。
     严格意义上来说,带有__declspec(novatable)修饰符的类应该是一个具有抽象概念的基类,它不允许直接用来实例化生成对象。(注:这里我认为更像是使用规范或者说一种使用约定,因为实际上使用__declspec(novatable)修饰的类是可以生成对象的,如下例1示,只是因为没有虚函数表vtable,对其虚函数的调用会导致程序崩溃。然而既然是做为抽象基类,一般做为接口使用。对其实例化对象也是没有意义的)。

例1:运行环境:Win7+VS2012

// atlnovtableStudy.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <iostream>
using namespace std;

#define ATL_NO_VTABLE __declspec(novtable)

class ATL_NO_VTABLE MyBase
{
public:
    MyBase()
    {
        cout << "MyBase::MyBase()" << endl;
    }
    virtual void Fun1()
    {
        cout << "MyBase::Fun1()" << endl;
    }
    virtual void Fun2()
    {
        cout << "MyBase::Fun2()" << endl;
    }
};
class ATL_NO_VTABLE Derive :public MyBase
{
public:
    Derive()
    {
        cout << "Derive::Derive()" << endl;
    }
    virtual void Fun1()
    {
		cout << "Derive::Fun1()" << endl;
	}
    virtual void Fun3()
    {
        cout << "Derive::Fun3()" << endl;
    }
	virtual void Fun4()
	{
		Fun2();
	}
};

class FinalClass : public Derive
{
public:
	FinalClass()
	{
		cout << "Final::Final()" << endl;
	}
};

int _tmain(int argc, _TCHAR* argv[])
{
    MyBase* p1 = new FinalClass();
    p1->Fun1();
    p1->Fun2();
    delete p1;
    // 输出结果:
    //MyBase::MyBase()
    //Derive::Derive()
    //Final::Final()
    //Derive::Fun1()
    //MyBase::Fun2()
    cout << "==========================" << endl;
    Derive* p2 = new FinalClass();
    p2->Fun1();
    p2->Fun2();
    p2->Fun3();
    p2->Fun4();
    //输出结果:
    //MyBase::MyBase()
    //Derive::Derive()
    //Final::Final()
    //Derive::Fun1()
    //MyBase::Fun2()
    //Derive::Fun3()
    //MyBase::Fun2()
    cout << "==========================" << endl;
    Derive* p3 = new Derive();
//  p3->Fun1();  //Oops.
//  p3->Fun3();  //Oops.
//  p3->Fun4();  //Oops.
    //输出结果:
    //MyBase::MyBase()
    //Derive::Derive()
    cout << "==========================" << endl;
    Derive d1;
    d1.Fun1();   //静态联编
    d1.Fun2();   //静态联编
    d1.Fun3();   //静态联编
//  d1.Fun4();   //Oops.
    //输出结果:
    //MyBase::MyBase()
    //Derive::Derive()
    //Derive::Fun1()
    //MyBase::Fun2()
    //Derive::Fun3()
    cout << "==========================" << endl;
    MyBase* p4 = new MyBase();
//  p4->Fun1();   //Oops
//  p4->Fun2();   //Oops
    //输出结果:
    //MyBase::MyBase()
    cout << "==========================" << endl;
    MyBase b;     
    b.Fun1();     //静态联编
    b.Fun2();     //静态联编
    //输出结果:
    //MyBase::MyBase()
    //MyBase::Fun1()
    //MyBase::Fun2()

    //当然。这是示例程序。旨在说明静态联编和虚函数调用。我就懒得去delete了
    //这是非常不好的编程习惯。会被鄙视的。不要学我!!为什么我就不多说啦。google去吧
    system("pause");
    return 0;
}

    其实从示例程序可以看出,基类实例化对象调用其虚函数由于没有虚函数表(VTABLE)的地址使得调用失败(程序Oops处)。至于为什么new的对象指针调用其虚函数失败而直接实例化的对象可以调用(程序静态联编处)。这又是编译器静态联编的原因。具体可以参考另一篇博文: C++中 _declspec(novtable) 的探讨

  

总结:

前面说道: The declspec(novtable) attribute should only be used with base classes that are not directly creatable.而COM接口类也确实无法被实例化。怎么做到这一点呢。其实上文也提出来了,那就是使其成为包含纯虚函数的类。使用__declspec(novtable)去掉虚函数表(VTABLE)的类在一两层的类继承中无法体现其实际性能。使用的意义也不太大。但在像WTL,COM这样的框架下。。可以想象如果不使用这样的技术每个类都将背负一个宠大无比的虚函数表,这也是为什么WTL编写的窗口程序比MFC程序在编译后Size明显减小的原因之一吧。

写了一个下午。我实在不善于把所想写成文字。一是懒,二是嘴拙。不善于言词。这也是我为什么又要重新写博的原因。写的过程其实又是一次思考和学习的机会。工作中慢慢开始接触文档编写。要是不多练练。以后肯定要写到吐。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值