virtual 修饰符与继承对析构函数的影响(C++)
以前,知道了虚函数表的低效性之后,一直尽量避免使用之。所以,在最近的工程中,所有的析构函数都不是虚函数。
今天趁着还书的机会到图书馆,还书之后在 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种使用方式中,有两种是不好的:
- 父类析构函数非虚函数,子类析构函数是虚函数,使用父类作为静态类型的析构(崩溃);
- 父类析构函数非虚函数,子类析构函数非虚函数,使用父类作为静态类型的析构(跳过了子类的析构函数)。
其余情况下,只要父类的析构函数是虚函数,就不需要关心指针的静态类型;统一指针的静态类型和动态类型(显式让运行时调用子类的析构函数)也可以避免意外。
virtual 修饰符与继承对析构函数的影响(C++)的更多相关文章
- virtual 修饰符 C# .NET
virtual 关键字用于修饰方法.属性.索引器或事件声明,并且允许在派生类中重写这些对象. 例如,此方法可被任何继承它的类重写. (C#参考) public virtual double Area( ...
- virtual修饰符
virtual(C# 参考) virtual 关键字用于修饰方法.属性.索引器或事件声明,并使它们可以在派生类中被重写. 例如,此方法可被任何继承它的类重写. public virtual doubl ...
- 概述C# virtual修饰符
摘要:C#是继C++和Java语言后的又一面向对象的语言,在语法结构,C#有很多地方和C++及Java相似,但是又不同于它们,其中一些关键特别需要引起我们的注意. C# virtual修饰符用于修改方 ...
- C# abstract,virtual 修饰符
abstract(抽象):该abstract修饰符指示要修改的东西有缺失或不完整的实现.abstract修饰符可以与类,方法,属性,索引器和事件一起使用.abstract在类声明中使用修饰符来指示类仅 ...
- 面向对象_访问修饰符_构造与析构函数_this指针
1:面向对象 以codeblocks举例,在一个工程里面: File-->new -->Class可以建一个类,可以设置类的参数,是否有set get方法,有无构造函数等设置,.h文件主要 ...
- C#继承机制 继承与访问修饰符
继承与访问修饰符 访问修饰符是一些关键字,用于指定声明的成员或类型的可访问性.类的继承中有四个访问修饰符: public protected internal private.使用这些访问修饰符可指定 ...
- c# 访问修饰符的访问权限
1. 访问修饰符. 指定声明的类型和类型成员的可访问性. (1) public:是类型和类型成员的访问修饰符.公共访问是允许的最高访问级别.对访问公共成员没有限制. (2) private:是一个成员 ...
- C#修饰符讲解大全
1.修饰符是什么? 修饰符是用于限定类型以及类型成员的声明的一种符号.[百度百科] 2.修饰符分类 13种修饰符,按功能可分为三类:访问修饰符,类修饰符和成员修饰符.[百度百科] 作 用:限定类型以及 ...
- override 修饰符
override(C# 参考) 要扩展或修改继承的方法.属性.索引器或事件的抽象实现或虚实现,必须使用 override 修饰符. C# abstract class ShapesClass { ab ...
随机推荐
- MAC显示文件夹路径
MAC显示文件夹路径 终端命令 1.显示路径:defaults write com.apple.finder _FXShowPosixPathInTitle -bool TRUE;killall Fi ...
- Untiy3D开发小贴士 OnEnabled与OnDisabled
设某个组件为NewBehaviour OnEnabled被调用的情况: 1.GameObject添加组件的时候,即AddComponet<NewBehaviour> : 2.包含3.已添加 ...
- Unity自动寻路Navmesh之入门
实例 我们要实现一个功能:点击场景中的一个位置,角色可以自动寻路过去.角色会绕过各种复杂的障碍,找到一条理论上”最短路径“. 步骤 1.创建地形 2.添加角色 3.创建多个障碍物,尽量摆的复杂一点,来 ...
- CSS 重设文章
CSS 重设 http://blog.bingo929.com/css-reset-collection.html
- u盘写入Ubuntu后容量变小,恢复方式
具体请参考网址:http://jingyan.baidu.com/article/59703552e754e48fc00740ed.html 经过验证,方法是可以的
- Hadoop学习笔记—15.HBase框架学习(基础知识篇)
HBase是Apache Hadoop的数据库,能够对大型数据提供随机.实时的读写访问.HBase的目标是存储并处理大型的数据.HBase是一个开源的,分布式的,多版本的,面向列的存储模型,它存储的是 ...
- iOS开发系列--Objective-C之类和对象
概述 前面已经简单介绍过ObjC的基础知识,让大家对ObjC有个大致的印象,今天将重点解释ObjC面向对象的特性.ObjC相对于C语言多了面向对象特性,但是ObjC又没有其他面向对象语言那么多语法特性 ...
- MySQL HASH分区
200 ? "200px" : this.width)!important;} --> 介绍 基于给定的分区个数,将数据分配到不同的分区,HASH分区只能针对整数进行HASH ...
- Linux 查找已安装软件的方法
1.rpm 注意rpm区分大小写 查询已安装的以mysql开头的包 rpm -qa mysql* 查询已安装的mysql 包 rpm -qa|grep mysql rpm的方法有时候也所有已安装的包 ...
- Azure PowerShell (4) 使用PowerShell管理多个订阅
<Windows Azure Platform 系列文章目录> 笔者手上有两个Azure账户. - Azure Global (windowsazure.com)账户.有两个订阅. - 世 ...