• 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
    ");
     10     }
     11 };
     12 
     13 class Pv
     14 {
     15 public:
     16     Pv() {}
     17     virtual ~Pv()
     18     {
     19         printf("Pv destruction
    ");
     20     }
     21 };
     22 
     23 class CP
     24     : public P
     25 {
     26 public:
     27     CP() {}
     28     ~CP()
     29     {
     30         printf("CP destruction
    ");
     31     }
     32 };
     33 
     34 class CPv
     35     : public Pv
     36 {
     37 public:
     38     CPv() {}
     39     ~CPv()
     40     {
     41         printf("CPv destruction
    ");
     42     }
     43 };
     44 
     45 class CvP
     46     : public P
     47 {
     48 public:
     49     CvP() {}
     50     virtual ~CvP()
     51     {
     52         printf("CvP destruction
    ");
     53     }
     54 };
     55 
     56 class CvPv
     57     : public Pv
     58 {
     59 public:
     60     CvPv() {}
     61     virtual ~CvPv()
     62     {
     63         printf("CvPv destruction
    ");
     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("-----------------------------
    ");
     81     delete p;
     82     printf("-----------------------------
    ");
     83     delete pv;
     84     printf("-----------------------------
    ");
     85     delete pc;
     86     printf("-----------------------------
    ");
     87     //delete pcv; // 父类析构调用没问题,然后崩溃
     88     printf("-----------------------------
    ");
     89     delete pvc;
     90     printf("-----------------------------
    ");
     91     delete pvcv;
     92     printf("-----------------------------
    ");
     93     delete cp;
     94     printf("-----------------------------
    ");
     95     delete cpv;
     96     printf("-----------------------------
    ");
     97     delete cvp;
     98     printf("-----------------------------
    ");
     99     delete cvpv;
    100     printf("-----------------------------
    ");
    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. 父类析构函数非虚函数,子类析构函数非虚函数,使用父类作为静态类型的析构(跳过了子类的析构函数)。

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

  • 相关阅读:
    Google调试技巧总结
    Reorder List -- leetcode
    Java回合阵列List
    他们控制的定义(2.3):SurfaceView和SurfaceHolder
    ImageView建立selector在录音中遇到的小问题及解决方案
    cocos2d 简单的日常高仿酷跑游戏
    Xcode的小标记旁边的文件的名称的作用
    c++中的对象引用(object reference)与对象指针的区别
    UIColor深入研究(CGColor,CIColor)
    UIImage图片处理
  • 原文地址:https://www.cnblogs.com/GridScience/p/3716649.html
Copyright © 2020-2023  润新知