一. 虚析构函数

我们知道,为了能够正确的调用对象的析构函数,一般要求具有层次结构的顶级类定义其析构函数为虚函数。因为在delete一个抽象类指针时候,必须要通过虚函数找到真正的析构函数。

如:

class  Base
{
public :
   Base(){}
    virtual   ~ Base(){}
};

class  Derived:  public  Base
{
public :
   Derived(){ };
    ~ Derived(){ };
}

void  foo()
{
   Base  * pb;
   pb  =   new  Derived;
   delete pb;
}

这是正确的用法,会发生动态绑定,它会先调用Derived的析构函数,然后是Base的析构函数

如果析构函数不加virtual,delete pb只会执行Base的析构函数,而不是真正的Derived析构函数。
因为不是virtual函数,所以调用的函数依赖于指向静态类型,即Base

二. 纯虚析构函数
现在的问题是,我们想把Base做出抽象类,不能直接构造对象,需要在其中定义一个纯虚函数。如果其中没有其他合适的函数,可以把析构函数定义为纯虚的,即将前面的CObject定义改成:

class  Base
{
public :
   Base(){}
    virtual   ~ Base() =   0
};
可是,这段代码不能通过编译,通常是link错误,不能找到~Base()的引用 (gcc的错误报告)。这是因为,析构函数、构造函数和其他内部函数不一样,在调用时,编译器需要产生一个调用链。也就是,Derived的析构函数里面 隐含调用了Base的析构函数。而刚才的代码中,缺少~Base()的函数体,当然会出现错误。

这里面有一个误区,有人认为,virtual f()=0这种纯虚函数语法就是没有定义体的语义。
其实,这是不对的。这种语法只是表明这个函数是一个纯虚函数,因此这个类变成了抽象类,不能产生对象。我们完全可以为纯虚函数指定函数体 ( http://www.research.att.com/~bs/bs_faq2.html#pure-virtual )。 通常的纯虚函数不需要函数体,是因为我们一般不会调用抽象类的这个函数,只会调用派生类的对应函数。这样,我们就有了一个纯虚析构函数的函数体,上面的代码需要改成:

class  Base
{
public :
   Base()
   {
   }
    virtual   ~ Base()  =   0 ;  // pure virtual
};

Base:: ~Base () // function body
{
}

从语法角度来说,不可以将上面的析构函数直接写入类声明中(内联函数的写法)。这或许是一个不正交化的地方。但是这样做的确显得有点累赘

这个问题看起来有些学术化,因为一般我们完全可以在Base中找到一个更加适合的函数,通过将其定义为没有实现体的纯虚函数,而将整个类定义为抽象类。但这种技术也有一些应用,如这个例子:

class  Base   // abstract class
{
public :
    virtual   ~ Base(){}; // virtual, not pure
    virtual   void  Hiberarchy()  const   =   0 ; // pure virtual
};

void  Base::Hiberarchy()  const   // pure virtual also can have function body
{
   std::cout  << " Base::Hiberarchy " ;
}

class  Derived :  public  Base
{
public :
   Derived(){}
    virtual   void  Hiberarchy()  const
   {
       CB::Hiberarchy();
       std::cout  << " Derived::Hiberarchy " ;
   }
    virtual   void  foo(){}
};

int  main(){
   Base *  pb = new  Derived();
   pb -> Hiberarchy();
   pb -> Base::Hiberarchy();
    return   0 ;
}

在 这个例子中,我们试图打印出类的继承关系。在根基类中定义了虚函数Hiberarchy,然后在每个派生类中都重载此函数。我们再一次看到,由于想把 Base做成个抽象类,而这个类中没有其他合适的方法成员可以定义为纯虚的,我们还是只好将Hiberarchy定义为纯虚的。(当然,完全可以定 义~Base函数,这就和上面的讨论一样了。^_^)

另外,可以看到,在main中有两种调用方法,第一种是普通的方式,进行动态链接,执行虚函数,得到结果"Derived::Hiberarchy";第二种是指定类的方式,就不再执行虚函数的动态链接过程了,结果是"Base::Hiberarchy"。

通过上面的分析可以看出,定义纯虚函数的真正目的是为了定义抽象类 , 而并不是函数本身。与之对比,在java中,定义抽象类的语法是 abstract class,也就是在类的一级作指定(当然虚函数还是也要加上abstract关键字)。是不是这种方式更好一些呢?在Stroustrup的《C++语 言的设计与演化》中我找到这样一段话:

“我选择的是将个别的函数描述为纯虚的方式,没有采用将完整的类声明定义为抽象的形式,这是因为纯虚函数的概念更加灵活一些。我很看重能够分阶段定义类的能力;也就是说,我发现预先定义一些纯虚函数,并把另外一些留给进一步的派生类去定义也是很有用的”。

我 还没有完全理解后一句话,我想从另外一个角度来阐述这个概念。那就是,在一个多层复杂类结构中,中间层次的类应该具体化一些抽象函数,但很可能并不是所有 的。中间类没必要知道是否具体化了所有的虚函数,以及其祖先已经具体化了哪些函数,只要关注自己的职责就可以了。也就是说,中间类没必要知道自己是否是一 个真正的抽象类,设计者也就不用考虑是否需要在这个中间类的类级别上加上类似abstract的说明了。

当然,一个语言的设计有多种因素,好坏都是各个方面的。这只是一个解释而已。

最后,总结一下关于虚函数的一些常见问题:

1) 虚函数是动态绑定的,也就是说,使用虚函数的指针和引用能够正确找到实际类的对应函数,而不是执行定义类的函数。这是虚函数的基本功能,就不再解释了。

2) 构造函数不能是虚函数 。而且,在构造函数中调用虚函数,实际执行的是父类的对应函数 ,因为自己还没有构造好, 多态是被disable的。

3) 析构函数可以是虚函数 ,而且,在一个复杂类结构中,这往往是必须的。
 
4) 将一个函数定义为纯虚函数,实际上是将这个类定义为抽象类,不能实例化对象。

5) 纯虚函数通常没有定义体,但也完全可以拥有 。

6)  析构函数可以是纯虚的,但纯虚析构函数必须有定义体 ,因为析构函数的调用是在子类中隐含的。

7) 非纯的虚函数必须有定义体,不然是一个错误。

8) 派生类的override虚函数定义必须和父类完全一致。除了一个特例,如果父类中返回值是一个指针或引用,子类override时可以返回这个指针(或引用)的派生 。例如,在上面的例子中,在Base中定义了 virtual Base* clone(); 在Derived中可以定义为 virtual Derived* clone()。可以看到,这种放松对于Clone模式是非常有用的。
其他,有待补充。

摘自:http://www.cnblogs.com/chio/archive/2007/09/10/888260.html

http://www.cnblogs.com/chio/archive/2007/09/10/888260.html相关推荐

  1. C# 中的委托和事件[转自http://www.cnblogs.com/jimmyzhang/archive/2007/09/23/903360.html]

    PDF 浏览:http://www.tracefact.net/Document/Delegates-and-Events-in-CSharp.pdf 文中代码在VS2005下通过,由于VS2003( ...

  2. 归并排序(转载http://www.cnblogs.com/jillzhang/archive/2007/09/16/894936.html)

    归并排序是利用递归和分而治之的技术将数据序列划分成为越来越小的半子表,再对半子表排序,最后再用递归步骤将排好序的半子表合并成为越来越大的有序序列,归并排序包括两个步骤,分别为: 1)划分子表 2)合并 ...

  3. 技术创业需胆识 谈IT技术人员的创业 (http://www.cnblogs.com/dudu/archive/2007/05/27/761740.html)...

    技术创业需胆识 谈IT技术人员的创业 作者:李立辉 我自己介绍一下:本人96年西点毕业,学的是无线通信,柳传志是我的校友,后来分配到北京巨龙做交换机,98年去了深圳中兴,当时看到中兴日益强大,和华为的 ...

  4. Maverick.Net介绍 (来自http://www.cnblogs.com/RicCC/archive/2006/09/17/506890.html)

    Maverick.Net介绍 Maverick.Net是Java社区开源MVC Web框架Maverick的.Net版本,相关资料可以查看项目主页. 不管Maverick.Net的是非好坏,了解一下它 ...

  5. tcp协议与粘包现象【转http://www.cnblogs.com/wzd24/archive/2007/12/24/1011932.html】

    Socket开发之通讯协议及处理 在Socket应用开发中,还有一个话题是讨论的比较多的,那就是数据接收后如何处理的问题.这也是一个令刚接触Socket开发的人很头疼的问题. 因为Socket的TCP ...

  6. 六种异常处理的陋习(转自http://www.blogjava.net/freeman1984/archive/2007/09/27/148850.html)...

    六种异常处理的陋习 你觉得自己是一个Java专家吗?是否肯定自己已经全面掌握了Java的异常处理机制?在下面这段代码中,你能够迅速找出异常处理的六个问题吗? 1 OutputStreamWriter ...

  7. 设计模式网站 http://www.cnblogs.com/justinw/archive/2007/02/06/641414.html

    j2ee http://www.17edu8.cn/blog/health/297.html 张孝祥web开发 http://www.17edu8.cn/blog/health/302.html ht ...

  8. http://www.cnblogs.com/wayfarer/archive/2004/09/29/47896.html

    动态添加程序集 转载于:https://www.cnblogs.com/lovey/archive/2013/04/07/3006112.html

  9. SQL Server 视图创建点滴 (转http://www.cnblogs.com/fineboy/archive/2008/05/10/236731.html#1191527)...

    2008年6月22日      视图在数据库开发过程中是非常重要的,对提高查询速度有很大的提高.因此我们的学会创建视图,并且有效的使用视图. (1)表准的SQL视图         标准视图比较简单, ...

最新文章

  1. 股市币市:数据分析与交易所最新公告(20190302)
  2. 云从完成超过18亿元新一轮融资,加快上市步伐
  3. Science-2018-微生物群落的构建过程具有趋简性
  4. POJ 1426 Find The Multiple
  5. OAuth2.0学习(1-6)授权方式3-密码模式(Resource Owner Password Credentials Grant)
  6. 没用过这些 IDEA 插件?怪不得写代码头疼
  7. javascript 触发事件列表
  8. linux vg备份还原,Oracle Linux 6.4 误删VG之恢复过程
  9. 数值计算方法(高斯消元以及LU分解)
  10. CUDA编程之快速入门-----GPU加速原理和编程实现
  11. 关于Java的多线程Runnable的个人理解(基础,不讲概念)
  12. 2020-08-23 每日一句
  13. HCIE证书有用吗?
  14. 网购工具软件chrome扩展插件大推荐
  15. 【概念】椭球面在球面上的投影
  16. 后棱镜时代的个人信息黑洞,电影《绝对控制》在现实中存在吗?
  17. Leetcode|MySQL|数据库刷题记录(601~627)
  18. telnet不是内部或外部命令怎么办
  19. 数据结构——约瑟夫环(Joseph Circle)
  20. java用监听捕捉点_使用Robot类创建自己的Java版屏幕捕捉程序

热门文章

  1. java接口的继承是多继承吗
  2. 易语言html转移,易语言正则表达式总结
  3. 港科夜闻|香港科大陈凯教授设计下一代人工智能计算中心,助力构建香港智慧城市...
  4. JavaScript动态加载ul标签
  5. 【TZCOO】saas WMS 云仓库管理软件的问题
  6. 中关村科幻产业创新中心 “科技赋能文化旅游,洞见哈萨克斯坦”数字旅游展盛大开幕
  7. 电脑桌面云便签怎么更改便签内容字体?
  8. 金多多综述酒科技取代了金融三胖的地位
  9. 数据库 导入Excel数据
  10. 网络通信误码率测试软件,卫星通信系统误码率测试必要性分析