virtual 修饰符与继承对析构函数的影响(C++)

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


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

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


  1 #include <stdio.h>
2
3 class P
4 {
5 public:
6 P() {}
7 ~P()
8 {
9 printf("P destruction\n");
10 }
11 };
12
13 class Pv
14 {
15 public:
16 Pv() {}
17 virtual ~Pv()
18 {
19 printf("Pv destruction\n");
20 }
21 };
22
23 class CP
24 : public P
25 {
26 public:
27 CP() {}
28 ~CP()
29 {
30 printf("CP destruction\n");
31 }
32 };
33
34 class CPv
35 : public Pv
36 {
37 public:
38 CPv() {}
39 ~CPv()
40 {
41 printf("CPv destruction\n");
42 }
43 };
44
45 class CvP
46 : public P
47 {
48 public:
49 CvP() {}
50 virtual ~CvP()
51 {
52 printf("CvP destruction\n");
53 }
54 };
55
56 class CvPv
57 : public Pv
58 {
59 public:
60 CvPv() {}
61 virtual ~CvPv()
62 {
63 printf("CvPv destruction\n");
64 }
65 };
66
67 int main(int argc, char *argv[])
68 {
69 P *p = new P();
70 Pv *pv = new Pv();
71 P *pc = new CP();
72 //P *pcv = new CvP(); // 析构时崩溃
73 Pv *pvc = new CPv();
74 Pv *pvcv = new CvPv();
75 CP *cp = new CP();
76 CPv *cpv = new CPv();
77 CvP *cvp = new CvP();
78 CvPv *cvpv = new CvPv();
79
80 printf("-----------------------------\n");
81 delete p;
82 printf("-----------------------------\n");
83 delete pv;
84 printf("-----------------------------\n");
85 delete pc;
86 printf("-----------------------------\n");
87 //delete pcv; // 父类析构调用没问题,然后崩溃
88 printf("-----------------------------\n");
89 delete pvc;
90 printf("-----------------------------\n");
91 delete pvcv;
92 printf("-----------------------------\n");
93 delete cp;
94 printf("-----------------------------\n");
95 delete cpv;
96 printf("-----------------------------\n");
97 delete cvp;
98 printf("-----------------------------\n");
99 delete cvpv;
100 printf("-----------------------------\n");
101
102 return 0;
103 }

其中删除静态类型为 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++),布布扣,bubuko.com

时间: 05-08

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

virtual 修饰符 C# .NET

virtual 关键字用于修饰方法.属性.索引器或事件声明,并且允许在派生类中重写这些对象. 例如,此方法可被任何继承它的类重写. (C#参考) 1 public virtual double Area() 2 3 { 4 5 return x * y; 6 7 } 虚拟成员的实现可由派生类中的重写成员更改 调用虚方法时,将为重写成员检查该对象的运行时类型.将调用大部分派生类中的该重写成员, 如果没有派生类重写该成员,则它可能是原始成员. 默认情况下,方法是非虚拟的.不能重写非虚方法. virt

【2017-4-14】访问修饰符 封装 继承 多态

1.命名空间/程序集 2.访问修饰符public 公共的 - 访问权限是最高的private 私有的 - 只能在类的内部进行访问 internal 默认的 - 当前命名空间(程序集)下可以访问protected 被保护的 - 当前类及它的派生类中可以访问 3.封装private 数据类型 _名称; //成员变量,字段public 数据类型 名称 //属性{ get{ return _名称; } set{ _名称 = value; }} 还可以有方法 //成员方法 4.继承: 子类:父类一个子类只

访问修饰符和继承

访问修饰符: 访问修饰符是对于跨类访问的一个权限限制 public : 公共的,引用命名空间即可随意访问,最高访问权限.private : 私有的,只有在声明它的类和结构中才可以访问,访问权限最低. Internal : 内部的,同一个程序集中所有的类都可以访问,程序集就是命名空间.Protected : 受保护的,只能在他自己和自己的子类中才能访问. 继承: 父类有且允许子类继承的东西子类都会有,一个父类可以有多个子类,一个子类只能有一个父类. class Bird : Fly//前面是子类,

C#语言中的修饰符汇总

1. 访问修饰符 指定声明的类型和类型成员的可访问性. (1) public:对任何类和成员都公开, 无限制访问;是类型和类型成员的访问修饰符.公共访问是允许的最高访问级别.对访问公共成员没有限制. (2) private:仅仅对该类公开;是一个成员访问修饰符.私有访问是允许的最低访问级别.私有成员只有在声明它们的类和结 构体中才是可访问的. (3) internal:只能值包含该类的程序集中访问该类(只是单独的项目,而不是整个解决方案);是类型和类型成员的访问修饰符.只有在同一程序集的文件中,

第10讲:访问修饰符.重载.覆盖

类和成员变量和成员方法都有修饰 继承 冗余的代码 不想被继承的变量和方法用修饰符 不能继承多个父类由接口解决幼儿继承小学生,小学生继承学生,继承层次没有做限制 jdk 必须继承jdk原有的类如:弹窗 重载 允许一样的方法名,这样调用不同方法时,编译器会根据参数自动选用哪个方法,调用的代码量会大大减少 方法覆盖

《编程导论(Java)&#183;6.3 private修饰符》

修饰符private提供了最高的保护和最低的可见度:private修饰的域.方法和嵌套类型,只能在它的定义类中访问. 6.3.1 private 各类书籍中会有一些程序设计上的清规戒律,如: ★以private修饰所有的成员变量(Make all Member Variables private.). ★以private修饰任一助手方法.(Make any helper methods private). 这些启发式规则是一种自律条款.对于Java包的设计者而言,包级私有已经是良好的天然边界(不

【2017-04-14】命名空间、访问修饰符、封装、继承、多态

一.命名空间 namespace 命名空间名称 如果命名空间一致,才能在他下面访问那些不同的类,如果不一致是没有办法直接访问的. 不同命名空间下都是public访问修饰符,想要调用,得在using处引用命名空间 using .... 是引用的命名空间 二.访问修饰符(写在命名任何东西的最前面) public    公共的,访问权限最高 private    私有的,访问权限最低,只能在类的内部进行访问 protected   被保护的,当前类及他的派生类中进行访问 internal    默认的

20151024_004_C#基础知识(C#中的访问修饰符,继承,new关键字,里氏转换,is 和 as,多态,序列化与反序列化)

1:C#中的访问修饰符 public: 公共成员,完全公开,没有访问限制. private: 私有的,只能在当前类的内部访问. protected: 受保护的,只能在当前类的内部以及该类的子类中访问. internal: 只能在当前项目中访问,在同一个项目中,internal和public的权限是一样的. protected internal: protected + internal权限. 1.1:能够修饰类的访问修饰符只有两个: public / internal 1.2:接口中的成员不允许

四.OC基础--1.文档安装和方法重载,2.self和super&amp;static,3.继承和派生,4.实例变量修饰符 ,5.私有变量&amp;私有方法,6.description方法

四.OC基础--1.文档安装和方法重载, 1. 在线安装 xcode-> 系统偏好设置->DownLoads->Doucument->下载 2. 离线安装 百度xcode文档 3. 方法重载: 是指在一个类中定义多个同名的方法 在OC中没有重载 2.self和super&static, self和super: 1. self理解: 谁调用当前方法, self就代表谁. 比如: 在对象方法中,self代表的是对象, 因为只有对象才可以调用对象方法 在类方法中, self代表的