以前,知道了虚函数表的低效性之后,一直尽量避免使用之。所以,在最近的工程中,所有的析构函数都不是虚函数。
今天趁着还书的机会到图书馆,还书之后在 TP 分类下闲逛,偶然读到一本游戏编程书,里面说建议将存在派生的类的析构函数都设置为 virtual。例如 ParentClass 和 ChildClass(派生自 ParentClass),如果 ParentClass 的 ~ParentClass() 不是 virtual 的话,以下代码会产生潜在的问题:

 ParentClass *pClass = new ChildClass();
delete pClass;

有什么问题呢?~ChildClass() 此时不会被调用。
于是想起来,赶快回来改代码!
我觉得其实析构函数也遵循 virtual 修饰的规则嘛。之前的例子,delete 的时候其实调用的是 ~ParentClass(),因为该函数不是虚函数;而如果是 virtual ~ParentClass() 的话,~ParentClass() 实际上是在虚函数表里的,因此会调用覆盖(override)之的 ~ChildClass()。
实际情况是否是这样的呢?我写了一个小小的示例,展示析构函数修饰符的影响。其中,后缀“v”表示析构函数是虚函数。

 #include <stdio.h>

 class P
{
public:
P() {}
~P()
{
printf("P destruction\n");
}
}; class Pv
{
public:
Pv() {}
virtual ~Pv()
{
printf("Pv destruction\n");
}
}; class CP
: public P
{
public:
CP() {}
~CP()
{
printf("CP destruction\n");
}
}; class CPv
: public Pv
{
public:
CPv() {}
~CPv()
{
printf("CPv destruction\n");
}
}; class CvP
: public P
{
public:
CvP() {}
virtual ~CvP()
{
printf("CvP destruction\n");
}
}; class CvPv
: public Pv
{
public:
CvPv() {}
virtual ~CvPv()
{
printf("CvPv destruction\n");
}
}; int main(int argc, char *argv[])
{
P *p = new P();
Pv *pv = new Pv();
P *pc = new CP();
//P *pcv = new CvP(); // 析构时崩溃
Pv *pvc = new CPv();
Pv *pvcv = new CvPv();
CP *cp = new CP();
CPv *cpv = new CPv();
CvP *cvp = new CvP();
CvPv *cvpv = new CvPv(); printf("-----------------------------\n");
delete p;
printf("-----------------------------\n");
delete pv;
printf("-----------------------------\n");
delete pc;
printf("-----------------------------\n");
//delete pcv; // 父类析构调用没问题,然后崩溃
printf("-----------------------------\n");
delete pvc;
printf("-----------------------------\n");
delete pvcv;
printf("-----------------------------\n");
delete cp;
printf("-----------------------------\n");
delete cpv;
printf("-----------------------------\n");
delete cvp;
printf("-----------------------------\n");
delete cvpv;
printf("-----------------------------\n"); return ;
}

其中删除静态类型为 P * 动态类型为 CvP * 的 pcv 时会崩溃。
其余结果如下:

-----------------------------
P destruction
-----------------------------
Pv destruction
-----------------------------
P destruction
-----------------------------
-----------------------------
CPv destruction
Pv destruction
-----------------------------
CvPv destruction
Pv destruction
-----------------------------
CP destruction
P destruction
-----------------------------
CPv destruction
Pv destruction
-----------------------------
CvP destruction
P destruction
-----------------------------
CvPv destruction
Pv destruction
-----------------------------

可见,我的想法不是完全正确的。

总结一下,在10种使用方式中,有两种是不好的:

  1. 父类析构函数非虚函数,子类析构函数是虚函数,使用父类作为静态类型的析构(崩溃);
  2. 父类析构函数非虚函数,子类析构函数非虚函数,使用父类作为静态类型的析构(跳过了子类的析构函数)。

其余情况下,只要父类的析构函数是虚函数,就不需要关心指针的静态类型;统一指针的静态类型和动态类型(显式让运行时调用子类的析构函数)也可以避免意外。

virtual 修饰符与继承对析构函数的影响(C++)的更多相关文章

  1. virtual 修饰符 C# .NET

    virtual 关键字用于修饰方法.属性.索引器或事件声明,并且允许在派生类中重写这些对象. 例如,此方法可被任何继承它的类重写. (C#参考) public virtual double Area( ...

  2. virtual修饰符

    virtual(C# 参考) virtual 关键字用于修饰方法.属性.索引器或事件声明,并使它们可以在派生类中被重写. 例如,此方法可被任何继承它的类重写. public virtual doubl ...

  3. 概述C# virtual修饰符

    摘要:C#是继C++和Java语言后的又一面向对象的语言,在语法结构,C#有很多地方和C++及Java相似,但是又不同于它们,其中一些关键特别需要引起我们的注意. C# virtual修饰符用于修改方 ...

  4. C# abstract,virtual 修饰符

    abstract(抽象):该abstract修饰符指示要修改的东西有缺失或不完整的实现.abstract修饰符可以与类,方法,属性,索引器和事件一起使用.abstract在类声明中使用修饰符来指示类仅 ...

  5. 面向对象_访问修饰符_构造与析构函数_this指针

    1:面向对象 以codeblocks举例,在一个工程里面: File-->new -->Class可以建一个类,可以设置类的参数,是否有set get方法,有无构造函数等设置,.h文件主要 ...

  6. C#继承机制 继承与访问修饰符

    继承与访问修饰符 访问修饰符是一些关键字,用于指定声明的成员或类型的可访问性.类的继承中有四个访问修饰符: public protected internal private.使用这些访问修饰符可指定 ...

  7. c# 访问修饰符的访问权限

    1. 访问修饰符. 指定声明的类型和类型成员的可访问性. (1) public:是类型和类型成员的访问修饰符.公共访问是允许的最高访问级别.对访问公共成员没有限制. (2) private:是一个成员 ...

  8. C#修饰符讲解大全

    1.修饰符是什么? 修饰符是用于限定类型以及类型成员的声明的一种符号.[百度百科] 2.修饰符分类 13种修饰符,按功能可分为三类:访问修饰符,类修饰符和成员修饰符.[百度百科] 作 用:限定类型以及 ...

  9. override 修饰符

    override(C# 参考) 要扩展或修改继承的方法.属性.索引器或事件的抽象实现或虚实现,必须使用 override 修饰符. C# abstract class ShapesClass { ab ...

随机推荐

  1. java并发编程(十九)障碍器CyclicBarrier

    转载请注明出处:http://blog.csdn.net/ns_code/article/details/17512983 CyclicBarrier(又叫障碍器)同样是Java 5中加入的新特性,使 ...

  2. 谢欣伦 - OpenDev原创教程 - 蓝牙设备查找类CxBthRadio & CxBthRadioFind

    这是一个精练的蓝牙设备查找类,类名.函数名和变量名均采用匈牙利命名法.小写的x代表我的姓氏首字母(谢欣伦),个人习惯而已,如有雷同,纯属巧合. CxBthRadioFind的使用如下: void CU ...

  3. The Solution of UESTC 2016 Summer Training #1 Div.2 Problem C

    Link http://acm.hust.edu.cn/vjudge/contest/121539#problem/C Description standard input/output After ...

  4. ubuntu下 编译安装swftools

    1.下载:http://www.swftools.org/download.html2.安装: tar -zvxf swftools-0.x.x.tar cd swftools-0.x.x ./con ...

  5. 安卓初級教程(3):ContentProvider的運用原理

    package com.example.android.provider; import java.util.ArrayList; import java.util.HashMap; import j ...

  6. 《Linux内核设计与实现》读书笔记 第十八章 调试

    第十八章调试 18.1 准备开始          需要准备的东西: l  一个bug:大部分bug通常都不是行为可靠而且定义明确的 l  一个藏匿bug的内核版本:找出bug首先出现的版本 l  相 ...

  7. 性能计数器与profiler的组合性能诊断

    性能计数器和sql profiler都是常用的性能诊断工具和优化工具,最近和群友聊天发现很多人竟然不知道这两个可以“组合”使用,所以这篇算是一篇扫盲贴吧. 两种工具简述 通过计数器可以收集两部分内容: ...

  8. Hadoop学习笔记—14.ZooKeeper环境搭建

    从字面上来看,ZooKeeper表示动物园管理员,这是一个十分奇妙的名字,我们又想起了Hadoop生态系统中,许多项目的Logo都采用了动物,比如Hadoop采用了大象的形象,所以我们可以猜测ZooK ...

  9. MySQL 提高Insert性能

    插入一个记录需要的时间由下列因素组成,其中的数字表示大约比例: 连接:(3) 发送查询给服务器:(2) 分析查询:(2) 插入记录:(1x记录大小) 插入索引:(1x索引) 关闭:(1) 这不考虑打开 ...

  10. maven仓库信息分析站点推荐

    maven是java的一个依赖,打包管理的工具,稍微大一点的java项目都需要使用maven. 随着java的壮大,maven仓库越来越大,仓库中的jar包有60多万,各种group,各种构件,各种版 ...